elasticsearch 9.1.2__py3-none-any.whl → 9.2.0__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 +94 -44
- elasticsearch/_async/client/async_search.py +7 -0
- elasticsearch/_async/client/cat.py +8 -1
- elasticsearch/_async/client/cluster.py +9 -8
- elasticsearch/_async/client/eql.py +7 -0
- elasticsearch/_async/client/esql.py +26 -3
- elasticsearch/_async/client/fleet.py +1 -5
- elasticsearch/_async/client/graph.py +1 -5
- elasticsearch/_async/client/ilm.py +2 -10
- elasticsearch/_async/client/indices.py +158 -28
- elasticsearch/_async/client/inference.py +280 -123
- elasticsearch/_async/client/ingest.py +8 -0
- elasticsearch/_async/client/license.py +4 -2
- elasticsearch/_async/client/ml.py +2 -2
- elasticsearch/_async/client/nodes.py +1 -3
- elasticsearch/_async/client/project.py +67 -0
- elasticsearch/_async/client/security.py +39 -0
- elasticsearch/_async/client/simulate.py +8 -0
- elasticsearch/_async/client/slm.py +1 -5
- elasticsearch/_async/client/snapshot.py +20 -10
- elasticsearch/_async/client/sql.py +7 -0
- elasticsearch/_async/client/streams.py +2 -3
- elasticsearch/_async/helpers.py +28 -15
- elasticsearch/_sync/client/__init__.py +94 -44
- elasticsearch/_sync/client/async_search.py +7 -0
- elasticsearch/_sync/client/cat.py +8 -1
- elasticsearch/_sync/client/cluster.py +9 -8
- elasticsearch/_sync/client/eql.py +7 -0
- elasticsearch/_sync/client/esql.py +26 -3
- elasticsearch/_sync/client/fleet.py +1 -5
- elasticsearch/_sync/client/graph.py +1 -5
- elasticsearch/_sync/client/ilm.py +2 -10
- elasticsearch/_sync/client/indices.py +158 -28
- elasticsearch/_sync/client/inference.py +280 -123
- elasticsearch/_sync/client/ingest.py +8 -0
- elasticsearch/_sync/client/license.py +4 -2
- elasticsearch/_sync/client/ml.py +2 -2
- elasticsearch/_sync/client/nodes.py +1 -3
- elasticsearch/_sync/client/project.py +67 -0
- elasticsearch/_sync/client/security.py +39 -0
- elasticsearch/_sync/client/simulate.py +8 -0
- elasticsearch/_sync/client/slm.py +1 -5
- elasticsearch/_sync/client/snapshot.py +20 -10
- elasticsearch/_sync/client/sql.py +7 -0
- elasticsearch/_sync/client/streams.py +2 -3
- elasticsearch/_version.py +2 -2
- elasticsearch/client.py +2 -0
- elasticsearch/compat.py +2 -15
- elasticsearch/dsl/_async/document.py +2 -1
- elasticsearch/dsl/_sync/document.py +2 -1
- elasticsearch/dsl/document_base.py +38 -13
- elasticsearch/dsl/pydantic.py +152 -0
- elasticsearch/dsl/search_base.py +5 -1
- elasticsearch/esql/esql.py +331 -41
- elasticsearch/esql/functions.py +88 -0
- elasticsearch/helpers/actions.py +1 -1
- {elasticsearch-9.1.2.dist-info → elasticsearch-9.2.0.dist-info}/METADATA +26 -4
- {elasticsearch-9.1.2.dist-info → elasticsearch-9.2.0.dist-info}/RECORD +61 -58
- {elasticsearch-9.1.2.dist-info → elasticsearch-9.2.0.dist-info}/WHEEL +0 -0
- {elasticsearch-9.1.2.dist-info → elasticsearch-9.2.0.dist-info}/licenses/LICENSE +0 -0
- {elasticsearch-9.1.2.dist-info → elasticsearch-9.2.0.dist-info}/licenses/NOTICE +0 -0
elasticsearch/_sync/client/ml.py
CHANGED
|
@@ -2390,7 +2390,7 @@ class MlClient(NamespacedClient):
|
|
|
2390
2390
|
exclude_interim: t.Optional[bool] = None,
|
|
2391
2391
|
filter_path: t.Optional[t.Union[str, t.Sequence[str]]] = None,
|
|
2392
2392
|
human: t.Optional[bool] = None,
|
|
2393
|
-
overall_score: t.Optional[
|
|
2393
|
+
overall_score: t.Optional[float] = None,
|
|
2394
2394
|
pretty: t.Optional[bool] = None,
|
|
2395
2395
|
start: t.Optional[t.Union[str, t.Any]] = None,
|
|
2396
2396
|
top_n: t.Optional[int] = None,
|
|
@@ -5716,7 +5716,7 @@ class MlClient(NamespacedClient):
|
|
|
5716
5716
|
<p>Validate an anomaly detection job.</p>
|
|
5717
5717
|
|
|
5718
5718
|
|
|
5719
|
-
`<https://www.elastic.co/guide/en/machine-learning/9.
|
|
5719
|
+
`<https://www.elastic.co/guide/en/machine-learning/9.2/ml-jobs.html>`_
|
|
5720
5720
|
|
|
5721
5721
|
:param analysis_config:
|
|
5722
5722
|
:param analysis_limits:
|
|
@@ -368,9 +368,7 @@ class NodesClient(NamespacedClient):
|
|
|
368
368
|
human: t.Optional[bool] = None,
|
|
369
369
|
include_segment_file_sizes: t.Optional[bool] = None,
|
|
370
370
|
include_unloaded_segments: t.Optional[bool] = None,
|
|
371
|
-
level: t.Optional[
|
|
372
|
-
t.Union[str, t.Literal["cluster", "indices", "shards"]]
|
|
373
|
-
] = None,
|
|
371
|
+
level: t.Optional[t.Union[str, t.Literal["indices", "node", "shards"]]] = None,
|
|
374
372
|
pretty: t.Optional[bool] = None,
|
|
375
373
|
timeout: t.Optional[t.Union[str, t.Literal[-1], t.Literal[0]]] = None,
|
|
376
374
|
types: t.Optional[t.Sequence[str]] = None,
|
|
@@ -0,0 +1,67 @@
|
|
|
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 typing as t
|
|
19
|
+
|
|
20
|
+
from elastic_transport import ObjectApiResponse
|
|
21
|
+
|
|
22
|
+
from ._base import NamespacedClient
|
|
23
|
+
from .utils import (
|
|
24
|
+
Stability,
|
|
25
|
+
_rewrite_parameters,
|
|
26
|
+
_stability_warning,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ProjectClient(NamespacedClient):
|
|
31
|
+
|
|
32
|
+
@_rewrite_parameters()
|
|
33
|
+
@_stability_warning(Stability.EXPERIMENTAL)
|
|
34
|
+
def tags(
|
|
35
|
+
self,
|
|
36
|
+
*,
|
|
37
|
+
error_trace: t.Optional[bool] = None,
|
|
38
|
+
filter_path: t.Optional[t.Union[str, t.Sequence[str]]] = None,
|
|
39
|
+
human: t.Optional[bool] = None,
|
|
40
|
+
pretty: t.Optional[bool] = None,
|
|
41
|
+
) -> ObjectApiResponse[t.Any]:
|
|
42
|
+
"""
|
|
43
|
+
.. raw:: html
|
|
44
|
+
|
|
45
|
+
<p>Return tags defined for the project</p>
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
__path_parts: t.Dict[str, str] = {}
|
|
49
|
+
__path = "/_project/tags"
|
|
50
|
+
__query: t.Dict[str, t.Any] = {}
|
|
51
|
+
if error_trace is not None:
|
|
52
|
+
__query["error_trace"] = error_trace
|
|
53
|
+
if filter_path is not None:
|
|
54
|
+
__query["filter_path"] = filter_path
|
|
55
|
+
if human is not None:
|
|
56
|
+
__query["human"] = human
|
|
57
|
+
if pretty is not None:
|
|
58
|
+
__query["pretty"] = pretty
|
|
59
|
+
__headers = {"accept": "application/json"}
|
|
60
|
+
return self.perform_request( # type: ignore[return-value]
|
|
61
|
+
"GET",
|
|
62
|
+
__path,
|
|
63
|
+
params=__query,
|
|
64
|
+
headers=__headers,
|
|
65
|
+
endpoint_id="project.tags",
|
|
66
|
+
path_parts=__path_parts,
|
|
67
|
+
)
|
|
@@ -2052,6 +2052,45 @@ class SecurityClient(NamespacedClient):
|
|
|
2052
2052
|
path_parts=__path_parts,
|
|
2053
2053
|
)
|
|
2054
2054
|
|
|
2055
|
+
@_rewrite_parameters()
|
|
2056
|
+
def get_stats(
|
|
2057
|
+
self,
|
|
2058
|
+
*,
|
|
2059
|
+
error_trace: t.Optional[bool] = None,
|
|
2060
|
+
filter_path: t.Optional[t.Union[str, t.Sequence[str]]] = None,
|
|
2061
|
+
human: t.Optional[bool] = None,
|
|
2062
|
+
pretty: t.Optional[bool] = None,
|
|
2063
|
+
) -> ObjectApiResponse[t.Any]:
|
|
2064
|
+
"""
|
|
2065
|
+
.. raw:: html
|
|
2066
|
+
|
|
2067
|
+
<p>Get security stats.</p>
|
|
2068
|
+
<p>Gather security usage statistics from all node(s) within the cluster.</p>
|
|
2069
|
+
|
|
2070
|
+
|
|
2071
|
+
`<https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-security-get-stats>`_
|
|
2072
|
+
"""
|
|
2073
|
+
__path_parts: t.Dict[str, str] = {}
|
|
2074
|
+
__path = "/_security/stats"
|
|
2075
|
+
__query: t.Dict[str, t.Any] = {}
|
|
2076
|
+
if error_trace is not None:
|
|
2077
|
+
__query["error_trace"] = error_trace
|
|
2078
|
+
if filter_path is not None:
|
|
2079
|
+
__query["filter_path"] = filter_path
|
|
2080
|
+
if human is not None:
|
|
2081
|
+
__query["human"] = human
|
|
2082
|
+
if pretty is not None:
|
|
2083
|
+
__query["pretty"] = pretty
|
|
2084
|
+
__headers = {"accept": "application/json"}
|
|
2085
|
+
return self.perform_request( # type: ignore[return-value]
|
|
2086
|
+
"GET",
|
|
2087
|
+
__path,
|
|
2088
|
+
params=__query,
|
|
2089
|
+
headers=__headers,
|
|
2090
|
+
endpoint_id="security.get_stats",
|
|
2091
|
+
path_parts=__path_parts,
|
|
2092
|
+
)
|
|
2093
|
+
|
|
2055
2094
|
@_rewrite_parameters(
|
|
2056
2095
|
body_fields=(
|
|
2057
2096
|
"grant_type",
|
|
@@ -56,6 +56,7 @@ class SimulateClient(NamespacedClient):
|
|
|
56
56
|
t.Mapping[str, t.Mapping[str, t.Any]]
|
|
57
57
|
] = None,
|
|
58
58
|
mapping_addition: t.Optional[t.Mapping[str, t.Any]] = None,
|
|
59
|
+
merge_type: t.Optional[t.Union[str, t.Literal["index", "template"]]] = None,
|
|
59
60
|
pipeline: t.Optional[str] = None,
|
|
60
61
|
pipeline_substitutions: t.Optional[
|
|
61
62
|
t.Mapping[str, t.Mapping[str, t.Any]]
|
|
@@ -93,6 +94,11 @@ class SimulateClient(NamespacedClient):
|
|
|
93
94
|
:param index_template_substitutions: A map of index template names to substitute
|
|
94
95
|
index template definition objects.
|
|
95
96
|
:param mapping_addition:
|
|
97
|
+
:param merge_type: The mapping merge type if mapping overrides are being provided
|
|
98
|
+
in mapping_addition. The allowed values are one of index or template. The
|
|
99
|
+
index option merges mappings the way they would be merged into an existing
|
|
100
|
+
index. The template option merges mappings the way they would be merged into
|
|
101
|
+
a template.
|
|
96
102
|
:param pipeline: The pipeline to use as the default pipeline. This value can
|
|
97
103
|
be used to override the default pipeline of the index.
|
|
98
104
|
:param pipeline_substitutions: Pipelines to test. If you don’t specify the `pipeline`
|
|
@@ -116,6 +122,8 @@ class SimulateClient(NamespacedClient):
|
|
|
116
122
|
__query["filter_path"] = filter_path
|
|
117
123
|
if human is not None:
|
|
118
124
|
__query["human"] = human
|
|
125
|
+
if merge_type is not None:
|
|
126
|
+
__query["merge_type"] = merge_type
|
|
119
127
|
if pipeline is not None:
|
|
120
128
|
__query["pipeline"] = pipeline
|
|
121
129
|
if pretty is not None:
|
|
@@ -431,11 +431,7 @@ class SlmClient(NamespacedClient):
|
|
|
431
431
|
__body["retention"] = retention
|
|
432
432
|
if schedule is not None:
|
|
433
433
|
__body["schedule"] = schedule
|
|
434
|
-
|
|
435
|
-
__body = None # type: ignore[assignment]
|
|
436
|
-
__headers = {"accept": "application/json"}
|
|
437
|
-
if __body is not None:
|
|
438
|
-
__headers["content-type"] = "application/json"
|
|
434
|
+
__headers = {"accept": "application/json", "content-type": "application/json"}
|
|
439
435
|
return self.perform_request( # type: ignore[return-value]
|
|
440
436
|
"PUT",
|
|
441
437
|
__path,
|
|
@@ -872,35 +872,40 @@ class SnapshotClient(NamespacedClient):
|
|
|
872
872
|
|
|
873
873
|
:param name: The name of the repository.
|
|
874
874
|
:param blob_count: The total number of blobs to write to the repository during
|
|
875
|
-
the test. For realistic experiments,
|
|
875
|
+
the test. For realistic experiments, set this parameter to at least `2000`.
|
|
876
876
|
:param concurrency: The number of operations to run concurrently during the test.
|
|
877
|
+
For realistic experiments, leave this parameter unset.
|
|
877
878
|
:param detailed: Indicates whether to return detailed results, including timing
|
|
878
879
|
information for every operation performed during the analysis. If false,
|
|
879
880
|
it returns only a summary of the analysis.
|
|
880
881
|
:param early_read_node_count: The number of nodes on which to perform an early
|
|
881
882
|
read operation while writing each blob. Early read operations are only rarely
|
|
882
|
-
performed.
|
|
883
|
+
performed. For realistic experiments, leave this parameter unset.
|
|
883
884
|
:param max_blob_size: The maximum size of a blob to be written during the test.
|
|
884
|
-
For realistic experiments,
|
|
885
|
+
For realistic experiments, set this parameter to at least `2gb`.
|
|
885
886
|
:param max_total_data_size: An upper limit on the total size of all the blobs
|
|
886
|
-
written during the test. For realistic experiments,
|
|
887
|
+
written during the test. For realistic experiments, set this parameter to
|
|
887
888
|
at least `1tb`.
|
|
888
889
|
:param rare_action_probability: The probability of performing a rare action such
|
|
889
|
-
as an early read, an overwrite, or an aborted write on each blob.
|
|
890
|
+
as an early read, an overwrite, or an aborted write on each blob. For realistic
|
|
891
|
+
experiments, leave this parameter unset.
|
|
890
892
|
:param rarely_abort_writes: Indicates whether to rarely cancel writes before
|
|
891
|
-
they complete.
|
|
893
|
+
they complete. For realistic experiments, leave this parameter unset.
|
|
892
894
|
:param read_node_count: The number of nodes on which to read a blob after writing.
|
|
895
|
+
For realistic experiments, leave this parameter unset.
|
|
893
896
|
:param register_operation_count: The minimum number of linearizable register
|
|
894
|
-
operations to perform in total. For realistic experiments,
|
|
895
|
-
|
|
897
|
+
operations to perform in total. For realistic experiments, set this parameter
|
|
898
|
+
to at least `100`.
|
|
896
899
|
:param seed: The seed for the pseudo-random number generator used to generate
|
|
897
900
|
the list of operations performed during the test. To repeat the same set
|
|
898
901
|
of operations in multiple experiments, use the same seed in each experiment.
|
|
899
902
|
Note that the operations are performed concurrently so might not always happen
|
|
900
|
-
in the same order on each run.
|
|
903
|
+
in the same order on each run. For realistic experiments, leave this parameter
|
|
904
|
+
unset.
|
|
901
905
|
:param timeout: The period of time to wait for the test to complete. If no response
|
|
902
906
|
is received before the timeout expires, the test is cancelled and returns
|
|
903
|
-
an error.
|
|
907
|
+
an error. For realistic experiments, set this parameter sufficiently long
|
|
908
|
+
to allow the test to complete.
|
|
904
909
|
"""
|
|
905
910
|
if name in SKIP_IN_PATH:
|
|
906
911
|
raise ValueError("Empty value passed for parameter 'name'")
|
|
@@ -1266,6 +1271,11 @@ class SnapshotClient(NamespacedClient):
|
|
|
1266
1271
|
<p>If you omit the <code><snapshot></code> request path parameter, the request retrieves information only for currently running snapshots.
|
|
1267
1272
|
This usage is preferred.
|
|
1268
1273
|
If needed, you can specify <code><repository></code> and <code><snapshot></code> to retrieve information for specific snapshots, even if they're not currently running.</p>
|
|
1274
|
+
<p>Note that the stats will not be available for any shard snapshots in an ongoing snapshot completed by a node that (even momentarily) left the cluster.
|
|
1275
|
+
Loading the stats from the repository is an expensive operation (see the WARNING below).
|
|
1276
|
+
Therefore the stats values for such shards will be -1 even though the "stage" value will be "DONE", in order to minimize latency.
|
|
1277
|
+
A "description" field will be present for a shard snapshot completed by a departed node explaining why the shard snapshot's stats results are invalid.
|
|
1278
|
+
Consequently, the total stats for the index will be less than expected due to the missing values from these shards.</p>
|
|
1269
1279
|
<p>WARNING: Using the API to return the status of any snapshots other than currently running snapshots can be expensive.
|
|
1270
1280
|
The API requires a read from the repository for each shard in each snapshot.
|
|
1271
1281
|
For example, if you have 100 snapshots with 1,000 shards each, an API request that includes all snapshots will require 100,000 reads (100 snapshots x 1,000 shards).</p>
|
|
@@ -285,6 +285,7 @@ class SqlClient(NamespacedClient):
|
|
|
285
285
|
page_timeout: t.Optional[t.Union[str, t.Literal[-1], t.Literal[0]]] = None,
|
|
286
286
|
params: t.Optional[t.Sequence[t.Any]] = None,
|
|
287
287
|
pretty: t.Optional[bool] = None,
|
|
288
|
+
project_routing: t.Optional[str] = None,
|
|
288
289
|
query: t.Optional[str] = None,
|
|
289
290
|
request_timeout: t.Optional[t.Union[str, t.Literal[-1], t.Literal[0]]] = None,
|
|
290
291
|
runtime_mappings: t.Optional[t.Mapping[str, t.Mapping[str, t.Any]]] = None,
|
|
@@ -332,6 +333,10 @@ class SqlClient(NamespacedClient):
|
|
|
332
333
|
is no longer available. Subsequent scroll requests prolong the lifetime of
|
|
333
334
|
the scroll cursor by the duration of `page_timeout` in the scroll request.
|
|
334
335
|
:param params: The values for parameters in the query.
|
|
336
|
+
:param project_routing: Specifies a subset of projects to target for the search
|
|
337
|
+
using project metadata tags in a subset of Lucene query syntax. Allowed Lucene
|
|
338
|
+
queries: the _alias tag and a single value (possibly wildcarded). Examples:
|
|
339
|
+
_alias:my-project _alias:_origin _alias:*pr* Supported in serverless only.
|
|
335
340
|
:param query: The SQL query to run.
|
|
336
341
|
:param request_timeout: The timeout before the request fails.
|
|
337
342
|
:param runtime_mappings: One or more runtime fields for the search request. These
|
|
@@ -357,6 +362,8 @@ class SqlClient(NamespacedClient):
|
|
|
357
362
|
__query["human"] = human
|
|
358
363
|
if pretty is not None:
|
|
359
364
|
__query["pretty"] = pretty
|
|
365
|
+
if project_routing is not None:
|
|
366
|
+
__query["project_routing"] = project_routing
|
|
360
367
|
if not __body:
|
|
361
368
|
if allow_partial_search_results is not None:
|
|
362
369
|
__body["allow_partial_search_results"] = allow_partial_search_results
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
# specific language governing permissions and limitations
|
|
16
16
|
# under the License.
|
|
17
17
|
|
|
18
|
+
|
|
18
19
|
import typing as t
|
|
19
20
|
|
|
20
21
|
from elastic_transport import ObjectApiResponse, TextApiResponse
|
|
@@ -144,9 +145,7 @@ class StreamsClient(NamespacedClient):
|
|
|
144
145
|
error_trace: t.Optional[bool] = None,
|
|
145
146
|
filter_path: t.Optional[t.Union[str, t.Sequence[str]]] = None,
|
|
146
147
|
human: t.Optional[bool] = None,
|
|
147
|
-
master_timeout: t.Optional[
|
|
148
|
-
t.Union[str, t.Literal["d", "h", "m", "micros", "ms", "nanos", "s"]]
|
|
149
|
-
] = None,
|
|
148
|
+
master_timeout: t.Optional[t.Union[str, t.Literal[-1], t.Literal[0]]] = None,
|
|
150
149
|
pretty: t.Optional[bool] = None,
|
|
151
150
|
) -> ObjectApiResponse[t.Any]:
|
|
152
151
|
"""
|
elasticsearch/_version.py
CHANGED
|
@@ -15,5 +15,5 @@
|
|
|
15
15
|
# specific language governing permissions and limitations
|
|
16
16
|
# under the License.
|
|
17
17
|
|
|
18
|
-
__versionstr__ = "9.
|
|
19
|
-
__es_specification_commit__ = "
|
|
18
|
+
__versionstr__ = "9.2.0"
|
|
19
|
+
__es_specification_commit__ = "2f74c26e0a1d66c42232ce2830652c01e8717f00"
|
elasticsearch/client.py
CHANGED
|
@@ -47,6 +47,7 @@ from ._sync.client.migration import MigrationClient as MigrationClient # noqa:
|
|
|
47
47
|
from ._sync.client.ml import MlClient as MlClient # noqa: F401
|
|
48
48
|
from ._sync.client.monitoring import MonitoringClient as MonitoringClient # noqa: F401
|
|
49
49
|
from ._sync.client.nodes import NodesClient as NodesClient # noqa: F401
|
|
50
|
+
from ._sync.client.project import ProjectClient as ProjectClient # noqa: F401
|
|
50
51
|
from ._sync.client.query_rules import QueryRulesClient as QueryRulesClient # noqa: F401
|
|
51
52
|
from ._sync.client.rollup import RollupClient as RollupClient # noqa: F401
|
|
52
53
|
from ._sync.client.search_application import ( # noqa: F401
|
|
@@ -106,6 +107,7 @@ __all__ = [
|
|
|
106
107
|
"MlClient",
|
|
107
108
|
"MonitoringClient",
|
|
108
109
|
"NodesClient",
|
|
110
|
+
"ProjectClient",
|
|
109
111
|
"RollupClient",
|
|
110
112
|
"SearchApplicationClient",
|
|
111
113
|
"SearchableSnapshotsClient",
|
elasticsearch/compat.py
CHANGED
|
@@ -15,14 +15,13 @@
|
|
|
15
15
|
# specific language governing permissions and limitations
|
|
16
16
|
# under the License.
|
|
17
17
|
|
|
18
|
-
import asyncio
|
|
19
18
|
import inspect
|
|
20
19
|
import os
|
|
21
20
|
import sys
|
|
22
|
-
from contextlib import
|
|
21
|
+
from contextlib import contextmanager
|
|
23
22
|
from pathlib import Path
|
|
24
23
|
from threading import Thread
|
|
25
|
-
from typing import Any,
|
|
24
|
+
from typing import Any, Callable, Iterator, Tuple, Type, Union
|
|
26
25
|
|
|
27
26
|
string_types: Tuple[Type[str], Type[bytes]] = (str, bytes)
|
|
28
27
|
|
|
@@ -105,22 +104,10 @@ def safe_thread(
|
|
|
105
104
|
raise captured_exception
|
|
106
105
|
|
|
107
106
|
|
|
108
|
-
@asynccontextmanager
|
|
109
|
-
async def safe_task(coro: Coroutine[Any, Any, Any]) -> AsyncIterator[asyncio.Task[Any]]:
|
|
110
|
-
"""Run a background task within a context manager block.
|
|
111
|
-
|
|
112
|
-
The task is awaited when the block ends.
|
|
113
|
-
"""
|
|
114
|
-
task = asyncio.create_task(coro)
|
|
115
|
-
yield task
|
|
116
|
-
await task
|
|
117
|
-
|
|
118
|
-
|
|
119
107
|
__all__ = [
|
|
120
108
|
"string_types",
|
|
121
109
|
"to_str",
|
|
122
110
|
"to_bytes",
|
|
123
111
|
"warn_stacklevel",
|
|
124
112
|
"safe_thread",
|
|
125
|
-
"safe_task",
|
|
126
113
|
]
|
|
@@ -126,9 +126,10 @@ class AsyncDocument(DocumentBase, metaclass=AsyncIndexMeta):
|
|
|
126
126
|
Create an :class:`~elasticsearch.dsl.Search` instance that will search
|
|
127
127
|
over this ``Document``.
|
|
128
128
|
"""
|
|
129
|
-
|
|
129
|
+
s = AsyncSearch[Self](
|
|
130
130
|
using=cls._get_using(using), index=cls._default_index(index), doc_type=[cls]
|
|
131
131
|
)
|
|
132
|
+
return s.source(exclude_vectors=False)
|
|
132
133
|
|
|
133
134
|
@classmethod
|
|
134
135
|
async def get(
|
|
@@ -120,9 +120,10 @@ class Document(DocumentBase, metaclass=IndexMeta):
|
|
|
120
120
|
Create an :class:`~elasticsearch.dsl.Search` instance that will search
|
|
121
121
|
over this ``Document``.
|
|
122
122
|
"""
|
|
123
|
-
|
|
123
|
+
s = Search[Self](
|
|
124
124
|
using=cls._get_using(using), index=cls._default_index(index), doc_type=[cls]
|
|
125
125
|
)
|
|
126
|
+
return s.source(exclude_vectors=False)
|
|
126
127
|
|
|
127
128
|
@classmethod
|
|
128
129
|
def get(
|
|
@@ -34,6 +34,8 @@ from typing import (
|
|
|
34
34
|
overload,
|
|
35
35
|
)
|
|
36
36
|
|
|
37
|
+
from typing_extensions import _AnnotatedAlias
|
|
38
|
+
|
|
37
39
|
try:
|
|
38
40
|
import annotationlib
|
|
39
41
|
except ImportError:
|
|
@@ -358,6 +360,10 @@ class DocumentOptions:
|
|
|
358
360
|
# the field has a type annotation, so next we try to figure out
|
|
359
361
|
# what field type we can use
|
|
360
362
|
type_ = annotations[name]
|
|
363
|
+
type_metadata = []
|
|
364
|
+
if isinstance(type_, _AnnotatedAlias):
|
|
365
|
+
type_metadata = type_.__metadata__
|
|
366
|
+
type_ = type_.__origin__
|
|
361
367
|
skip = False
|
|
362
368
|
required = True
|
|
363
369
|
multi = False
|
|
@@ -404,6 +410,12 @@ class DocumentOptions:
|
|
|
404
410
|
# use best field type for the type hint provided
|
|
405
411
|
field, field_kwargs = self.type_annotation_map[type_] # type: ignore[assignment]
|
|
406
412
|
|
|
413
|
+
# if this field does not have a right-hand value, we look in the metadata
|
|
414
|
+
# of the annotation to see if we find it there
|
|
415
|
+
for md in type_metadata:
|
|
416
|
+
if isinstance(md, (_FieldMetadataDict, Field)):
|
|
417
|
+
attrs[name] = md
|
|
418
|
+
|
|
407
419
|
if field:
|
|
408
420
|
field_kwargs = {
|
|
409
421
|
"multi": multi,
|
|
@@ -416,17 +428,20 @@ class DocumentOptions:
|
|
|
416
428
|
# this field has a right-side value, which can be field
|
|
417
429
|
# instance on its own or wrapped with mapped_field()
|
|
418
430
|
attr_value = attrs[name]
|
|
419
|
-
if isinstance(attr_value,
|
|
431
|
+
if isinstance(attr_value, _FieldMetadataDict):
|
|
420
432
|
# the mapped_field() wrapper function was used so we need
|
|
421
433
|
# to look for the field instance and also record any
|
|
422
434
|
# dataclass-style defaults
|
|
435
|
+
if attr_value.get("exclude"):
|
|
436
|
+
# skip this field
|
|
437
|
+
continue
|
|
423
438
|
attr_value = attrs[name].get("_field")
|
|
424
439
|
default_value = attrs[name].get("default") or attrs[name].get(
|
|
425
440
|
"default_factory"
|
|
426
441
|
)
|
|
427
442
|
if default_value:
|
|
428
443
|
field_defaults[name] = default_value
|
|
429
|
-
if attr_value:
|
|
444
|
+
if isinstance(attr_value, Field):
|
|
430
445
|
value = attr_value
|
|
431
446
|
if required is not None:
|
|
432
447
|
value._required = required
|
|
@@ -505,12 +520,19 @@ class Mapped(Generic[_FieldType]):
|
|
|
505
520
|
M = Mapped
|
|
506
521
|
|
|
507
522
|
|
|
523
|
+
class _FieldMetadataDict(dict[str, Any]):
|
|
524
|
+
"""This class is used to identify metadata returned by the `mapped_field()` function."""
|
|
525
|
+
|
|
526
|
+
pass
|
|
527
|
+
|
|
528
|
+
|
|
508
529
|
def mapped_field(
|
|
509
530
|
field: Optional[Field] = None,
|
|
510
531
|
*,
|
|
511
532
|
init: bool = True,
|
|
512
533
|
default: Any = None,
|
|
513
534
|
default_factory: Optional[Callable[[], Any]] = None,
|
|
535
|
+
exclude: bool = False,
|
|
514
536
|
**kwargs: Any,
|
|
515
537
|
) -> Any:
|
|
516
538
|
"""Construct a field using dataclass behaviors
|
|
@@ -520,22 +542,25 @@ def mapped_field(
|
|
|
520
542
|
options.
|
|
521
543
|
|
|
522
544
|
:param field: The instance of ``Field`` to use for this field. If not provided,
|
|
523
|
-
|
|
545
|
+
an instance that is appropriate for the type given to the field is used.
|
|
524
546
|
:param init: a value of ``True`` adds this field to the constructor, and a
|
|
525
|
-
|
|
547
|
+
value of ``False`` omits it from it. The default is ``True``.
|
|
526
548
|
:param default: a default value to use for this field when one is not provided
|
|
527
|
-
|
|
549
|
+
explicitly.
|
|
528
550
|
:param default_factory: a callable that returns a default value for the field,
|
|
529
|
-
|
|
530
|
-
|
|
551
|
+
when one isn't provided explicitly. Only one of ``factory`` and
|
|
552
|
+
``default_factory`` can be used.
|
|
553
|
+
:param exclude: Set to ``True`` to exclude this field from the Elasticsearch
|
|
554
|
+
index.
|
|
531
555
|
"""
|
|
532
|
-
return
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
556
|
+
return _FieldMetadataDict(
|
|
557
|
+
_field=field,
|
|
558
|
+
init=init,
|
|
559
|
+
default=default,
|
|
560
|
+
default_factory=default_factory,
|
|
561
|
+
exclude=exclude,
|
|
537
562
|
**kwargs,
|
|
538
|
-
|
|
563
|
+
)
|
|
539
564
|
|
|
540
565
|
|
|
541
566
|
@dataclass_transform(field_specifiers=(mapped_field,))
|
|
@@ -0,0 +1,152 @@
|
|
|
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 Any, ClassVar, Dict, List, Optional, Tuple, Type
|
|
19
|
+
|
|
20
|
+
from pydantic import BaseModel, Field, PrivateAttr
|
|
21
|
+
from typing_extensions import Annotated, Self, dataclass_transform
|
|
22
|
+
|
|
23
|
+
from elasticsearch import dsl
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ESMeta(BaseModel):
|
|
27
|
+
"""Metadata items associated with Elasticsearch documents."""
|
|
28
|
+
|
|
29
|
+
id: str = ""
|
|
30
|
+
index: str = ""
|
|
31
|
+
primary_term: int = 0
|
|
32
|
+
seq_no: int = 0
|
|
33
|
+
version: int = 0
|
|
34
|
+
score: float = 0
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class _BaseModel(BaseModel):
|
|
38
|
+
meta: Annotated[ESMeta, dsl.mapped_field(exclude=True)] = Field(
|
|
39
|
+
default=ESMeta(),
|
|
40
|
+
init=False,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class _BaseESModelMetaclass(type(BaseModel)): # type: ignore[misc]
|
|
45
|
+
"""Generic metaclass methods for BaseEsModel and AsyncBaseESModel."""
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def process_annotations(
|
|
49
|
+
metacls: Type["_BaseESModelMetaclass"], annotations: Dict[str, Any]
|
|
50
|
+
) -> Dict[str, Any]:
|
|
51
|
+
"""Process Pydantic typing annotations and adapt them so that they can
|
|
52
|
+
be used to create the Elasticsearch document.
|
|
53
|
+
"""
|
|
54
|
+
updated_annotations = {}
|
|
55
|
+
for var, ann in annotations.items():
|
|
56
|
+
if isinstance(ann, type(BaseModel)):
|
|
57
|
+
# an inner Pydantic model is transformed into an Object field
|
|
58
|
+
updated_annotations[var] = metacls.make_dsl_class(
|
|
59
|
+
metacls, dsl.InnerDoc, ann
|
|
60
|
+
)
|
|
61
|
+
elif (
|
|
62
|
+
hasattr(ann, "__origin__")
|
|
63
|
+
and ann.__origin__ in [list, List]
|
|
64
|
+
and isinstance(ann.__args__[0], type(BaseModel))
|
|
65
|
+
):
|
|
66
|
+
# an inner list of Pydantic models is transformed into a Nested field
|
|
67
|
+
updated_annotations[var] = List[ # type: ignore[assignment,misc]
|
|
68
|
+
metacls.make_dsl_class(metacls, dsl.InnerDoc, ann.__args__[0])
|
|
69
|
+
]
|
|
70
|
+
else:
|
|
71
|
+
updated_annotations[var] = ann
|
|
72
|
+
return updated_annotations
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def make_dsl_class(
|
|
76
|
+
metacls: Type["_BaseESModelMetaclass"],
|
|
77
|
+
dsl_class: type,
|
|
78
|
+
pydantic_model: type,
|
|
79
|
+
pydantic_attrs: Optional[Dict[str, Any]] = None,
|
|
80
|
+
) -> type:
|
|
81
|
+
"""Create a DSL document class dynamically, using the structure of a
|
|
82
|
+
Pydantic model."""
|
|
83
|
+
dsl_attrs = {
|
|
84
|
+
attr: value
|
|
85
|
+
for attr, value in dsl_class.__dict__.items()
|
|
86
|
+
if not attr.startswith("__")
|
|
87
|
+
}
|
|
88
|
+
pydantic_attrs = {
|
|
89
|
+
**(pydantic_attrs or {}),
|
|
90
|
+
"__annotations__": metacls.process_annotations(
|
|
91
|
+
metacls, pydantic_model.__annotations__
|
|
92
|
+
),
|
|
93
|
+
}
|
|
94
|
+
return type(dsl_class)(
|
|
95
|
+
f"_ES{pydantic_model.__name__}",
|
|
96
|
+
(dsl_class,),
|
|
97
|
+
{
|
|
98
|
+
**pydantic_attrs,
|
|
99
|
+
**dsl_attrs,
|
|
100
|
+
"__qualname__": f"_ES{pydantic_model.__name__}",
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class BaseESModelMetaclass(_BaseESModelMetaclass):
|
|
106
|
+
"""Metaclass for the BaseESModel class."""
|
|
107
|
+
|
|
108
|
+
def __new__(cls, name: str, bases: Tuple[type, ...], attrs: Dict[str, Any]) -> Any:
|
|
109
|
+
model = super().__new__(cls, name, bases, attrs)
|
|
110
|
+
model._doc = cls.make_dsl_class(cls, dsl.Document, model, attrs)
|
|
111
|
+
return model
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class AsyncBaseESModelMetaclass(_BaseESModelMetaclass):
|
|
115
|
+
"""Metaclass for the AsyncBaseESModel class."""
|
|
116
|
+
|
|
117
|
+
def __new__(cls, name: str, bases: Tuple[type, ...], attrs: Dict[str, Any]) -> Any:
|
|
118
|
+
model = super().__new__(cls, name, bases, attrs)
|
|
119
|
+
model._doc = cls.make_dsl_class(cls, dsl.AsyncDocument, model, attrs)
|
|
120
|
+
return model
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@dataclass_transform(kw_only_default=True, field_specifiers=(Field, PrivateAttr))
|
|
124
|
+
class BaseESModel(_BaseModel, metaclass=BaseESModelMetaclass):
|
|
125
|
+
_doc: ClassVar[Type[dsl.Document]]
|
|
126
|
+
|
|
127
|
+
def to_doc(self) -> dsl.Document:
|
|
128
|
+
"""Convert this model to an Elasticsearch document."""
|
|
129
|
+
data = self.model_dump()
|
|
130
|
+
meta = {f"_{k}": v for k, v in data.pop("meta", {}).items() if v}
|
|
131
|
+
return self._doc(**meta, **data)
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def from_doc(cls, dsl_obj: dsl.Document) -> Self:
|
|
135
|
+
"""Create a model from the given Elasticsearch document."""
|
|
136
|
+
return cls(meta=ESMeta(**dsl_obj.meta.to_dict()), **dsl_obj.to_dict())
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass_transform(kw_only_default=True, field_specifiers=(Field, PrivateAttr))
|
|
140
|
+
class AsyncBaseESModel(_BaseModel, metaclass=AsyncBaseESModelMetaclass):
|
|
141
|
+
_doc: ClassVar[Type[dsl.AsyncDocument]]
|
|
142
|
+
|
|
143
|
+
def to_doc(self) -> dsl.AsyncDocument:
|
|
144
|
+
"""Convert this model to an Elasticsearch document."""
|
|
145
|
+
data = self.model_dump()
|
|
146
|
+
meta = {f"_{k}": v for k, v in data.pop("meta", {}).items() if v}
|
|
147
|
+
return self._doc(**meta, **data)
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def from_doc(cls, dsl_obj: dsl.AsyncDocument) -> Self:
|
|
151
|
+
"""Create a model from the given Elasticsearch document."""
|
|
152
|
+
return cls(meta=ESMeta(**dsl_obj.meta.to_dict()), **dsl_obj.to_dict())
|