elasticsearch 9.0.0__py3-none-any.whl → 9.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- elasticsearch/_async/client/__init__.py +5 -5
- elasticsearch/_async/client/cat.py +201 -7
- elasticsearch/_async/client/indices.py +16 -7
- elasticsearch/_async/client/inference.py +2 -72
- elasticsearch/_async/client/ml.py +3 -3
- elasticsearch/_sync/client/__init__.py +5 -5
- elasticsearch/_sync/client/cat.py +201 -7
- elasticsearch/_sync/client/indices.py +16 -7
- elasticsearch/_sync/client/inference.py +2 -72
- elasticsearch/_sync/client/ml.py +3 -3
- elasticsearch/_version.py +1 -1
- elasticsearch/dsl/_async/document.py +1 -1
- elasticsearch/dsl/_sync/_sync_check/__init__.py +16 -0
- elasticsearch/dsl/_sync/_sync_check/document.py +514 -0
- elasticsearch/dsl/_sync/_sync_check/faceted_search.py +50 -0
- elasticsearch/dsl/_sync/_sync_check/index.py +597 -0
- elasticsearch/dsl/_sync/_sync_check/mapping.py +49 -0
- elasticsearch/dsl/_sync/_sync_check/search.py +230 -0
- elasticsearch/dsl/_sync/_sync_check/update_by_query.py +45 -0
- elasticsearch/dsl/_sync/document.py +1 -1
- elasticsearch/dsl/field.py +11 -1
- elasticsearch/dsl/query.py +44 -2
- elasticsearch/dsl/types.py +76 -10
- elasticsearch/exceptions.py +2 -0
- {elasticsearch-9.0.0.dist-info → elasticsearch-9.0.2.dist-info}/METADATA +15 -16
- {elasticsearch-9.0.0.dist-info → elasticsearch-9.0.2.dist-info}/RECORD +29 -22
- {elasticsearch-9.0.0.dist-info → elasticsearch-9.0.2.dist-info}/WHEEL +0 -0
- {elasticsearch-9.0.0.dist-info → elasticsearch-9.0.2.dist-info}/licenses/LICENSE +0 -0
- {elasticsearch-9.0.0.dist-info → elasticsearch-9.0.2.dist-info}/licenses/NOTICE +0 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
|
3
|
+
# this work for additional information regarding copyright
|
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
|
6
|
+
# not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
|
12
|
+
# software distributed under the License is distributed on an
|
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
# KIND, either express or implied. See the License for the
|
|
15
|
+
# specific language governing permissions and limitations
|
|
16
|
+
# under the License.
|
|
17
|
+
|
|
18
|
+
import collections.abc
|
|
19
|
+
from typing import (
|
|
20
|
+
TYPE_CHECKING,
|
|
21
|
+
Any,
|
|
22
|
+
Dict,
|
|
23
|
+
Iterable,
|
|
24
|
+
List,
|
|
25
|
+
Optional,
|
|
26
|
+
Tuple,
|
|
27
|
+
Union,
|
|
28
|
+
cast,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
from typing_extensions import Self, dataclass_transform
|
|
32
|
+
|
|
33
|
+
from elasticsearch.exceptions import NotFoundError, RequestError
|
|
34
|
+
from elasticsearch.helpers import bulk
|
|
35
|
+
|
|
36
|
+
from .._sync.index import Index
|
|
37
|
+
from ..connections import get_connection
|
|
38
|
+
from ..document_base import DocumentBase, DocumentMeta, mapped_field
|
|
39
|
+
from ..exceptions import IllegalOperation
|
|
40
|
+
from ..utils import DOC_META_FIELDS, META_FIELDS, UsingType, merge
|
|
41
|
+
from .search import Search
|
|
42
|
+
|
|
43
|
+
if TYPE_CHECKING:
|
|
44
|
+
from elasticsearch import Elasticsearch
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class IndexMeta(DocumentMeta):
|
|
48
|
+
_index: Index
|
|
49
|
+
|
|
50
|
+
# global flag to guard us from associating an Index with the base Document
|
|
51
|
+
# class, only user defined subclasses should have an _index attr
|
|
52
|
+
_document_initialized = False
|
|
53
|
+
|
|
54
|
+
def __new__(
|
|
55
|
+
cls, name: str, bases: Tuple[type, ...], attrs: Dict[str, Any]
|
|
56
|
+
) -> "IndexMeta":
|
|
57
|
+
new_cls = super().__new__(cls, name, bases, attrs)
|
|
58
|
+
if cls._document_initialized:
|
|
59
|
+
index_opts = attrs.pop("Index", None)
|
|
60
|
+
index = cls.construct_index(index_opts, bases)
|
|
61
|
+
new_cls._index = index
|
|
62
|
+
index.document(new_cls)
|
|
63
|
+
cls._document_initialized = True
|
|
64
|
+
return cast(IndexMeta, new_cls)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def construct_index(cls, opts: Dict[str, Any], bases: Tuple[type, ...]) -> Index:
|
|
68
|
+
if opts is None:
|
|
69
|
+
for b in bases:
|
|
70
|
+
if hasattr(b, "_index"):
|
|
71
|
+
return b._index
|
|
72
|
+
|
|
73
|
+
# Set None as Index name so it will set _all while making the query
|
|
74
|
+
return Index(name=None)
|
|
75
|
+
|
|
76
|
+
i = Index(getattr(opts, "name", "*"), using=getattr(opts, "using", "default"))
|
|
77
|
+
i.settings(**getattr(opts, "settings", {}))
|
|
78
|
+
i.aliases(**getattr(opts, "aliases", {}))
|
|
79
|
+
for a in getattr(opts, "analyzers", ()):
|
|
80
|
+
i.analyzer(a)
|
|
81
|
+
return i
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass_transform(field_specifiers=(mapped_field,))
|
|
85
|
+
class Document(DocumentBase, metaclass=IndexMeta):
|
|
86
|
+
"""
|
|
87
|
+
Model-like class for persisting documents in elasticsearch.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
if TYPE_CHECKING:
|
|
91
|
+
_index: Index
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def _get_using(cls, using: Optional[UsingType] = None) -> UsingType:
|
|
95
|
+
return using or cls._index._using
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def _get_connection(cls, using: Optional[UsingType] = None) -> "Elasticsearch":
|
|
99
|
+
return get_connection(cls._get_using(using))
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def init(
|
|
103
|
+
cls, index: Optional[str] = None, using: Optional[UsingType] = None
|
|
104
|
+
) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Create the index and populate the mappings in elasticsearch.
|
|
107
|
+
"""
|
|
108
|
+
i = cls._index
|
|
109
|
+
if index:
|
|
110
|
+
i = i.clone(name=index)
|
|
111
|
+
i.save(using=using)
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def search(
|
|
115
|
+
cls, using: Optional[UsingType] = None, index: Optional[str] = None
|
|
116
|
+
) -> Search[Self]:
|
|
117
|
+
"""
|
|
118
|
+
Create an :class:`~elasticsearch.dsl.Search` instance that will search
|
|
119
|
+
over this ``Document``.
|
|
120
|
+
"""
|
|
121
|
+
return Search(
|
|
122
|
+
using=cls._get_using(using), index=cls._default_index(index), doc_type=[cls]
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def get(
|
|
127
|
+
cls,
|
|
128
|
+
id: str,
|
|
129
|
+
using: Optional[UsingType] = None,
|
|
130
|
+
index: Optional[str] = None,
|
|
131
|
+
**kwargs: Any,
|
|
132
|
+
) -> Optional[Self]:
|
|
133
|
+
"""
|
|
134
|
+
Retrieve a single document from elasticsearch using its ``id``.
|
|
135
|
+
|
|
136
|
+
:arg id: ``id`` of the document to be retrieved
|
|
137
|
+
:arg index: elasticsearch index to use, if the ``Document`` is
|
|
138
|
+
associated with an index this can be omitted.
|
|
139
|
+
:arg using: connection alias to use, defaults to ``'default'``
|
|
140
|
+
|
|
141
|
+
Any additional keyword arguments will be passed to
|
|
142
|
+
``Elasticsearch.get`` unchanged.
|
|
143
|
+
"""
|
|
144
|
+
es = cls._get_connection(using)
|
|
145
|
+
doc = es.get(index=cls._default_index(index), id=id, **kwargs)
|
|
146
|
+
if not doc.get("found", False):
|
|
147
|
+
return None
|
|
148
|
+
return cls.from_es(doc)
|
|
149
|
+
|
|
150
|
+
@classmethod
|
|
151
|
+
def exists(
|
|
152
|
+
cls,
|
|
153
|
+
id: str,
|
|
154
|
+
using: Optional[UsingType] = None,
|
|
155
|
+
index: Optional[str] = None,
|
|
156
|
+
**kwargs: Any,
|
|
157
|
+
) -> bool:
|
|
158
|
+
"""
|
|
159
|
+
check if exists a single document from elasticsearch using its ``id``.
|
|
160
|
+
|
|
161
|
+
:arg id: ``id`` of the document to check if exists
|
|
162
|
+
:arg index: elasticsearch index to use, if the ``Document`` is
|
|
163
|
+
associated with an index this can be omitted.
|
|
164
|
+
:arg using: connection alias to use, defaults to ``'default'``
|
|
165
|
+
|
|
166
|
+
Any additional keyword arguments will be passed to
|
|
167
|
+
``Elasticsearch.exists`` unchanged.
|
|
168
|
+
"""
|
|
169
|
+
es = cls._get_connection(using)
|
|
170
|
+
return bool(es.exists(index=cls._default_index(index), id=id, **kwargs))
|
|
171
|
+
|
|
172
|
+
@classmethod
|
|
173
|
+
def mget(
|
|
174
|
+
cls,
|
|
175
|
+
docs: List[Dict[str, Any]],
|
|
176
|
+
using: Optional[UsingType] = None,
|
|
177
|
+
index: Optional[str] = None,
|
|
178
|
+
raise_on_error: bool = True,
|
|
179
|
+
missing: str = "none",
|
|
180
|
+
**kwargs: Any,
|
|
181
|
+
) -> List[Optional[Self]]:
|
|
182
|
+
r"""
|
|
183
|
+
Retrieve multiple document by their ``id``\s. Returns a list of instances
|
|
184
|
+
in the same order as requested.
|
|
185
|
+
|
|
186
|
+
:arg docs: list of ``id``\s of the documents to be retrieved or a list
|
|
187
|
+
of document specifications as per
|
|
188
|
+
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-multi-get.html
|
|
189
|
+
:arg index: elasticsearch index to use, if the ``Document`` is
|
|
190
|
+
associated with an index this can be omitted.
|
|
191
|
+
:arg using: connection alias to use, defaults to ``'default'``
|
|
192
|
+
:arg missing: what to do when one of the documents requested is not
|
|
193
|
+
found. Valid options are ``'none'`` (use ``None``), ``'raise'`` (raise
|
|
194
|
+
``NotFoundError``) or ``'skip'`` (ignore the missing document).
|
|
195
|
+
|
|
196
|
+
Any additional keyword arguments will be passed to
|
|
197
|
+
``Elasticsearch.mget`` unchanged.
|
|
198
|
+
"""
|
|
199
|
+
if missing not in ("raise", "skip", "none"):
|
|
200
|
+
raise ValueError("'missing' must be 'raise', 'skip', or 'none'.")
|
|
201
|
+
es = cls._get_connection(using)
|
|
202
|
+
body = {
|
|
203
|
+
"docs": [
|
|
204
|
+
doc if isinstance(doc, collections.abc.Mapping) else {"_id": doc}
|
|
205
|
+
for doc in docs
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
results = es.mget(index=cls._default_index(index), body=body, **kwargs)
|
|
209
|
+
|
|
210
|
+
objs: List[Optional[Self]] = []
|
|
211
|
+
error_docs: List[Self] = []
|
|
212
|
+
missing_docs: List[Self] = []
|
|
213
|
+
for doc in results["docs"]:
|
|
214
|
+
if doc.get("found"):
|
|
215
|
+
if error_docs or missing_docs:
|
|
216
|
+
# We're going to raise an exception anyway, so avoid an
|
|
217
|
+
# expensive call to cls.from_es().
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
objs.append(cls.from_es(doc))
|
|
221
|
+
|
|
222
|
+
elif doc.get("error"):
|
|
223
|
+
if raise_on_error:
|
|
224
|
+
error_docs.append(doc)
|
|
225
|
+
if missing == "none":
|
|
226
|
+
objs.append(None)
|
|
227
|
+
|
|
228
|
+
# The doc didn't cause an error, but the doc also wasn't found.
|
|
229
|
+
elif missing == "raise":
|
|
230
|
+
missing_docs.append(doc)
|
|
231
|
+
elif missing == "none":
|
|
232
|
+
objs.append(None)
|
|
233
|
+
|
|
234
|
+
if error_docs:
|
|
235
|
+
error_ids = [doc["_id"] for doc in error_docs]
|
|
236
|
+
message = "Required routing not provided for documents %s."
|
|
237
|
+
message %= ", ".join(error_ids)
|
|
238
|
+
raise RequestError(400, message, error_docs) # type: ignore[arg-type]
|
|
239
|
+
if missing_docs:
|
|
240
|
+
missing_ids = [doc["_id"] for doc in missing_docs]
|
|
241
|
+
message = f"Documents {', '.join(missing_ids)} not found."
|
|
242
|
+
raise NotFoundError(404, message, {"docs": missing_docs}) # type: ignore[arg-type]
|
|
243
|
+
return objs
|
|
244
|
+
|
|
245
|
+
def delete(
|
|
246
|
+
self,
|
|
247
|
+
using: Optional[UsingType] = None,
|
|
248
|
+
index: Optional[str] = None,
|
|
249
|
+
**kwargs: Any,
|
|
250
|
+
) -> None:
|
|
251
|
+
"""
|
|
252
|
+
Delete the instance in elasticsearch.
|
|
253
|
+
|
|
254
|
+
:arg index: elasticsearch index to use, if the ``Document`` is
|
|
255
|
+
associated with an index this can be omitted.
|
|
256
|
+
:arg using: connection alias to use, defaults to ``'default'``
|
|
257
|
+
|
|
258
|
+
Any additional keyword arguments will be passed to
|
|
259
|
+
``Elasticsearch.delete`` unchanged.
|
|
260
|
+
"""
|
|
261
|
+
es = self._get_connection(using)
|
|
262
|
+
# extract routing etc from meta
|
|
263
|
+
doc_meta = {k: self.meta[k] for k in DOC_META_FIELDS if k in self.meta}
|
|
264
|
+
|
|
265
|
+
# Optimistic concurrency control
|
|
266
|
+
if "seq_no" in self.meta and "primary_term" in self.meta:
|
|
267
|
+
doc_meta["if_seq_no"] = self.meta["seq_no"]
|
|
268
|
+
doc_meta["if_primary_term"] = self.meta["primary_term"]
|
|
269
|
+
|
|
270
|
+
doc_meta.update(kwargs)
|
|
271
|
+
i = self._get_index(index)
|
|
272
|
+
assert i is not None
|
|
273
|
+
|
|
274
|
+
es.delete(index=i, **doc_meta)
|
|
275
|
+
|
|
276
|
+
def update(
|
|
277
|
+
self,
|
|
278
|
+
using: Optional[UsingType] = None,
|
|
279
|
+
index: Optional[str] = None,
|
|
280
|
+
detect_noop: bool = True,
|
|
281
|
+
doc_as_upsert: bool = False,
|
|
282
|
+
refresh: bool = False,
|
|
283
|
+
retry_on_conflict: Optional[int] = None,
|
|
284
|
+
script: Optional[Union[str, Dict[str, Any]]] = None,
|
|
285
|
+
script_id: Optional[str] = None,
|
|
286
|
+
scripted_upsert: bool = False,
|
|
287
|
+
upsert: Optional[Dict[str, Any]] = None,
|
|
288
|
+
return_doc_meta: bool = False,
|
|
289
|
+
**fields: Any,
|
|
290
|
+
) -> Any:
|
|
291
|
+
"""
|
|
292
|
+
Partial update of the document, specify fields you wish to update and
|
|
293
|
+
both the instance and the document in elasticsearch will be updated::
|
|
294
|
+
|
|
295
|
+
doc = MyDocument(title='Document Title!')
|
|
296
|
+
doc.save()
|
|
297
|
+
doc.update(title='New Document Title!')
|
|
298
|
+
|
|
299
|
+
:arg index: elasticsearch index to use, if the ``Document`` is
|
|
300
|
+
associated with an index this can be omitted.
|
|
301
|
+
:arg using: connection alias to use, defaults to ``'default'``
|
|
302
|
+
:arg detect_noop: Set to ``False`` to disable noop detection.
|
|
303
|
+
:arg refresh: Control when the changes made by this request are visible
|
|
304
|
+
to search. Set to ``True`` for immediate effect.
|
|
305
|
+
:arg retry_on_conflict: In between the get and indexing phases of the
|
|
306
|
+
update, it is possible that another process might have already
|
|
307
|
+
updated the same document. By default, the update will fail with a
|
|
308
|
+
version conflict exception. The retry_on_conflict parameter
|
|
309
|
+
controls how many times to retry the update before finally throwing
|
|
310
|
+
an exception.
|
|
311
|
+
:arg doc_as_upsert: Instead of sending a partial doc plus an upsert
|
|
312
|
+
doc, setting doc_as_upsert to true will use the contents of doc as
|
|
313
|
+
the upsert value
|
|
314
|
+
:arg script: the source code of the script as a string, or a dictionary
|
|
315
|
+
with script attributes to update.
|
|
316
|
+
:arg return_doc_meta: set to ``True`` to return all metadata from the
|
|
317
|
+
index API call instead of only the operation result
|
|
318
|
+
|
|
319
|
+
:return: operation result noop/updated
|
|
320
|
+
"""
|
|
321
|
+
body: Dict[str, Any] = {
|
|
322
|
+
"doc_as_upsert": doc_as_upsert,
|
|
323
|
+
"detect_noop": detect_noop,
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
# scripted update
|
|
327
|
+
if script or script_id:
|
|
328
|
+
if upsert is not None:
|
|
329
|
+
body["upsert"] = upsert
|
|
330
|
+
|
|
331
|
+
if script:
|
|
332
|
+
if isinstance(script, str):
|
|
333
|
+
script = {"source": script}
|
|
334
|
+
else:
|
|
335
|
+
script = {"id": script_id}
|
|
336
|
+
|
|
337
|
+
if "params" not in script:
|
|
338
|
+
script["params"] = fields
|
|
339
|
+
else:
|
|
340
|
+
script["params"].update(fields)
|
|
341
|
+
|
|
342
|
+
body["script"] = script
|
|
343
|
+
body["scripted_upsert"] = scripted_upsert
|
|
344
|
+
|
|
345
|
+
# partial document update
|
|
346
|
+
else:
|
|
347
|
+
if not fields:
|
|
348
|
+
raise IllegalOperation(
|
|
349
|
+
"You cannot call update() without updating individual fields or a script. "
|
|
350
|
+
"If you wish to update the entire object use save()."
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
# update given fields locally
|
|
354
|
+
merge(self, fields)
|
|
355
|
+
|
|
356
|
+
# prepare data for ES
|
|
357
|
+
values = self.to_dict(skip_empty=False)
|
|
358
|
+
|
|
359
|
+
# if fields were given: partial update
|
|
360
|
+
body["doc"] = {k: values.get(k) for k in fields.keys()}
|
|
361
|
+
|
|
362
|
+
# extract routing etc from meta
|
|
363
|
+
doc_meta = {k: self.meta[k] for k in DOC_META_FIELDS if k in self.meta}
|
|
364
|
+
|
|
365
|
+
if retry_on_conflict is not None:
|
|
366
|
+
doc_meta["retry_on_conflict"] = retry_on_conflict
|
|
367
|
+
|
|
368
|
+
# Optimistic concurrency control
|
|
369
|
+
if (
|
|
370
|
+
retry_on_conflict in (None, 0)
|
|
371
|
+
and "seq_no" in self.meta
|
|
372
|
+
and "primary_term" in self.meta
|
|
373
|
+
):
|
|
374
|
+
doc_meta["if_seq_no"] = self.meta["seq_no"]
|
|
375
|
+
doc_meta["if_primary_term"] = self.meta["primary_term"]
|
|
376
|
+
|
|
377
|
+
i = self._get_index(index)
|
|
378
|
+
assert i is not None
|
|
379
|
+
|
|
380
|
+
meta = self._get_connection(using).update(
|
|
381
|
+
index=i, body=body, refresh=refresh, **doc_meta
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# update meta information from ES
|
|
385
|
+
for k in META_FIELDS:
|
|
386
|
+
if "_" + k in meta:
|
|
387
|
+
setattr(self.meta, k, meta["_" + k])
|
|
388
|
+
|
|
389
|
+
return meta if return_doc_meta else meta["result"]
|
|
390
|
+
|
|
391
|
+
def save(
|
|
392
|
+
self,
|
|
393
|
+
using: Optional[UsingType] = None,
|
|
394
|
+
index: Optional[str] = None,
|
|
395
|
+
validate: bool = True,
|
|
396
|
+
skip_empty: bool = True,
|
|
397
|
+
return_doc_meta: bool = False,
|
|
398
|
+
**kwargs: Any,
|
|
399
|
+
) -> Any:
|
|
400
|
+
"""
|
|
401
|
+
Save the document into elasticsearch. If the document doesn't exist it
|
|
402
|
+
is created, it is overwritten otherwise. Returns ``True`` if this
|
|
403
|
+
operations resulted in new document being created.
|
|
404
|
+
|
|
405
|
+
:arg index: elasticsearch index to use, if the ``Document`` is
|
|
406
|
+
associated with an index this can be omitted.
|
|
407
|
+
:arg using: connection alias to use, defaults to ``'default'``
|
|
408
|
+
:arg validate: set to ``False`` to skip validating the document
|
|
409
|
+
:arg skip_empty: if set to ``False`` will cause empty values (``None``,
|
|
410
|
+
``[]``, ``{}``) to be left on the document. Those values will be
|
|
411
|
+
stripped out otherwise as they make no difference in elasticsearch.
|
|
412
|
+
:arg return_doc_meta: set to ``True`` to return all metadata from the
|
|
413
|
+
update API call instead of only the operation result
|
|
414
|
+
|
|
415
|
+
Any additional keyword arguments will be passed to
|
|
416
|
+
``Elasticsearch.index`` unchanged.
|
|
417
|
+
|
|
418
|
+
:return: operation result created/updated
|
|
419
|
+
"""
|
|
420
|
+
if validate:
|
|
421
|
+
self.full_clean()
|
|
422
|
+
|
|
423
|
+
es = self._get_connection(using)
|
|
424
|
+
# extract routing etc from meta
|
|
425
|
+
doc_meta = {k: self.meta[k] for k in DOC_META_FIELDS if k in self.meta}
|
|
426
|
+
|
|
427
|
+
# Optimistic concurrency control
|
|
428
|
+
if "seq_no" in self.meta and "primary_term" in self.meta:
|
|
429
|
+
doc_meta["if_seq_no"] = self.meta["seq_no"]
|
|
430
|
+
doc_meta["if_primary_term"] = self.meta["primary_term"]
|
|
431
|
+
|
|
432
|
+
doc_meta.update(kwargs)
|
|
433
|
+
i = self._get_index(index)
|
|
434
|
+
assert i is not None
|
|
435
|
+
|
|
436
|
+
meta = es.index(
|
|
437
|
+
index=i,
|
|
438
|
+
body=self.to_dict(skip_empty=skip_empty),
|
|
439
|
+
**doc_meta,
|
|
440
|
+
)
|
|
441
|
+
# update meta information from ES
|
|
442
|
+
for k in META_FIELDS:
|
|
443
|
+
if "_" + k in meta:
|
|
444
|
+
setattr(self.meta, k, meta["_" + k])
|
|
445
|
+
|
|
446
|
+
return meta if return_doc_meta else meta["result"]
|
|
447
|
+
|
|
448
|
+
@classmethod
|
|
449
|
+
def bulk(
|
|
450
|
+
cls,
|
|
451
|
+
actions: Iterable[Union[Self, Dict[str, Any]]],
|
|
452
|
+
using: Optional[UsingType] = None,
|
|
453
|
+
index: Optional[str] = None,
|
|
454
|
+
validate: bool = True,
|
|
455
|
+
skip_empty: bool = True,
|
|
456
|
+
**kwargs: Any,
|
|
457
|
+
) -> Tuple[int, Union[int, List[Any]]]:
|
|
458
|
+
"""
|
|
459
|
+
Allows to perform multiple indexing operations in a single request.
|
|
460
|
+
|
|
461
|
+
:arg actions: a generator that returns document instances to be indexed,
|
|
462
|
+
bulk operation dictionaries.
|
|
463
|
+
:arg using: connection alias to use, defaults to ``'default'``
|
|
464
|
+
:arg index: Elasticsearch index to use, if the ``Document`` is
|
|
465
|
+
associated with an index this can be omitted.
|
|
466
|
+
:arg validate: set to ``False`` to skip validating the documents
|
|
467
|
+
:arg skip_empty: if set to ``False`` will cause empty values (``None``,
|
|
468
|
+
``[]``, ``{}``) to be left on the document. Those values will be
|
|
469
|
+
stripped out otherwise as they make no difference in Elasticsearch.
|
|
470
|
+
|
|
471
|
+
Any additional keyword arguments will be passed to
|
|
472
|
+
``Elasticsearch.bulk`` unchanged.
|
|
473
|
+
|
|
474
|
+
:return: bulk operation results
|
|
475
|
+
"""
|
|
476
|
+
es = cls._get_connection(using)
|
|
477
|
+
|
|
478
|
+
i = cls._default_index(index)
|
|
479
|
+
assert i is not None
|
|
480
|
+
|
|
481
|
+
class Generate:
|
|
482
|
+
def __init__(
|
|
483
|
+
self,
|
|
484
|
+
doc_iterator: Iterable[Union[Document, Dict[str, Any]]],
|
|
485
|
+
):
|
|
486
|
+
self.doc_iterator = doc_iterator.__iter__()
|
|
487
|
+
|
|
488
|
+
def __iter__(self) -> Self:
|
|
489
|
+
return self
|
|
490
|
+
|
|
491
|
+
def __next__(self) -> Dict[str, Any]:
|
|
492
|
+
doc: Optional[Union[Document, Dict[str, Any]]] = (
|
|
493
|
+
self.doc_iterator.__next__()
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
if isinstance(doc, dict):
|
|
497
|
+
action = doc
|
|
498
|
+
doc = None
|
|
499
|
+
if "_source" in action and isinstance(action["_source"], Document):
|
|
500
|
+
doc = action["_source"]
|
|
501
|
+
if validate: # pragma: no cover
|
|
502
|
+
doc.full_clean()
|
|
503
|
+
action["_source"] = doc.to_dict(
|
|
504
|
+
include_meta=False, skip_empty=skip_empty
|
|
505
|
+
)
|
|
506
|
+
elif doc is not None:
|
|
507
|
+
if validate: # pragma: no cover
|
|
508
|
+
doc.full_clean()
|
|
509
|
+
action = doc.to_dict(include_meta=True, skip_empty=skip_empty)
|
|
510
|
+
if "_index" not in action:
|
|
511
|
+
action["_index"] = i
|
|
512
|
+
return action
|
|
513
|
+
|
|
514
|
+
return bulk(es, Generate(actions), **kwargs)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
|
3
|
+
# this work for additional information regarding copyright
|
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
|
6
|
+
# not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
|
12
|
+
# software distributed under the License is distributed on an
|
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
# KIND, either express or implied. See the License for the
|
|
15
|
+
# specific language governing permissions and limitations
|
|
16
|
+
# under the License.
|
|
17
|
+
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
from ..faceted_search_base import FacetedResponse, FacetedSearchBase
|
|
21
|
+
from ..utils import _R
|
|
22
|
+
from .search import Search
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from ..response import Response
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class FacetedSearch(FacetedSearchBase[_R]):
|
|
29
|
+
_s: Search[_R]
|
|
30
|
+
|
|
31
|
+
def count(self) -> int:
|
|
32
|
+
return self._s.count()
|
|
33
|
+
|
|
34
|
+
def search(self) -> Search[_R]:
|
|
35
|
+
"""
|
|
36
|
+
Returns the base Search object to which the facets are added.
|
|
37
|
+
|
|
38
|
+
You can customize the query by overriding this method and returning a
|
|
39
|
+
modified search object.
|
|
40
|
+
"""
|
|
41
|
+
s = Search[_R](doc_type=self.doc_types, index=self.index, using=self.using)
|
|
42
|
+
return s.response_class(FacetedResponse)
|
|
43
|
+
|
|
44
|
+
def execute(self) -> "Response[_R]":
|
|
45
|
+
"""
|
|
46
|
+
Execute the search and return the response.
|
|
47
|
+
"""
|
|
48
|
+
r = self._s.execute()
|
|
49
|
+
r._faceted_search = self
|
|
50
|
+
return r
|