elasticsearch9 9.0.2__py3-none-any.whl → 9.0.3__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.
Files changed (54) hide show
  1. elasticsearch9/_async/client/__init__.py +42 -198
  2. elasticsearch9/_async/client/cat.py +393 -25
  3. elasticsearch9/_async/client/cluster.py +14 -4
  4. elasticsearch9/_async/client/eql.py +10 -2
  5. elasticsearch9/_async/client/esql.py +17 -4
  6. elasticsearch9/_async/client/indices.py +87 -43
  7. elasticsearch9/_async/client/inference.py +108 -3
  8. elasticsearch9/_async/client/ingest.py +0 -7
  9. elasticsearch9/_async/client/license.py +4 -4
  10. elasticsearch9/_async/client/ml.py +6 -17
  11. elasticsearch9/_async/client/monitoring.py +1 -1
  12. elasticsearch9/_async/client/rollup.py +1 -22
  13. elasticsearch9/_async/client/security.py +11 -17
  14. elasticsearch9/_async/client/snapshot.py +6 -0
  15. elasticsearch9/_async/client/synonyms.py +1 -0
  16. elasticsearch9/_async/client/watcher.py +4 -2
  17. elasticsearch9/_sync/client/__init__.py +42 -198
  18. elasticsearch9/_sync/client/cat.py +393 -25
  19. elasticsearch9/_sync/client/cluster.py +14 -4
  20. elasticsearch9/_sync/client/eql.py +10 -2
  21. elasticsearch9/_sync/client/esql.py +17 -4
  22. elasticsearch9/_sync/client/indices.py +87 -43
  23. elasticsearch9/_sync/client/inference.py +108 -3
  24. elasticsearch9/_sync/client/ingest.py +0 -7
  25. elasticsearch9/_sync/client/license.py +4 -4
  26. elasticsearch9/_sync/client/ml.py +6 -17
  27. elasticsearch9/_sync/client/monitoring.py +1 -1
  28. elasticsearch9/_sync/client/rollup.py +1 -22
  29. elasticsearch9/_sync/client/security.py +11 -17
  30. elasticsearch9/_sync/client/snapshot.py +6 -0
  31. elasticsearch9/_sync/client/synonyms.py +1 -0
  32. elasticsearch9/_sync/client/watcher.py +4 -2
  33. elasticsearch9/_version.py +1 -1
  34. elasticsearch9/compat.py +5 -0
  35. elasticsearch9/dsl/__init__.py +2 -1
  36. elasticsearch9/dsl/document_base.py +176 -16
  37. elasticsearch9/dsl/field.py +222 -47
  38. elasticsearch9/dsl/query.py +7 -4
  39. elasticsearch9/dsl/types.py +105 -80
  40. elasticsearch9/dsl/utils.py +1 -1
  41. elasticsearch9/{dsl/_sync/_sync_check → esql}/__init__.py +2 -0
  42. elasticsearch9/esql/esql.py +1105 -0
  43. elasticsearch9/esql/functions.py +1738 -0
  44. {elasticsearch9-9.0.2.dist-info → elasticsearch9-9.0.3.dist-info}/METADATA +1 -1
  45. {elasticsearch9-9.0.2.dist-info → elasticsearch9-9.0.3.dist-info}/RECORD +48 -52
  46. elasticsearch9/dsl/_sync/_sync_check/document.py +0 -514
  47. elasticsearch9/dsl/_sync/_sync_check/faceted_search.py +0 -50
  48. elasticsearch9/dsl/_sync/_sync_check/index.py +0 -597
  49. elasticsearch9/dsl/_sync/_sync_check/mapping.py +0 -49
  50. elasticsearch9/dsl/_sync/_sync_check/search.py +0 -230
  51. elasticsearch9/dsl/_sync/_sync_check/update_by_query.py +0 -45
  52. {elasticsearch9-9.0.2.dist-info → elasticsearch9-9.0.3.dist-info}/WHEEL +0 -0
  53. {elasticsearch9-9.0.2.dist-info → elasticsearch9-9.0.3.dist-info}/licenses/LICENSE +0 -0
  54. {elasticsearch9-9.0.2.dist-info → elasticsearch9-9.0.3.dist-info}/licenses/NOTICE +0 -0
@@ -2213,13 +2213,10 @@ class SecurityClient(NamespacedClient):
2213
2213
  def get_user_privileges(
2214
2214
  self,
2215
2215
  *,
2216
- application: t.Optional[str] = None,
2217
2216
  error_trace: t.Optional[bool] = None,
2218
2217
  filter_path: t.Optional[t.Union[str, t.Sequence[str]]] = None,
2219
2218
  human: t.Optional[bool] = None,
2220
2219
  pretty: t.Optional[bool] = None,
2221
- priviledge: t.Optional[str] = None,
2222
- username: t.Optional[t.Union[None, str]] = None,
2223
2220
  ) -> ObjectApiResponse[t.Any]:
2224
2221
  """
2225
2222
  .. raw:: html
@@ -2232,19 +2229,10 @@ class SecurityClient(NamespacedClient):
2232
2229
 
2233
2230
 
2234
2231
  `<https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-security-get-user-privileges>`_
2235
-
2236
- :param application: The name of the application. Application privileges are always
2237
- associated with exactly one application. If you do not specify this parameter,
2238
- the API returns information about all privileges for all applications.
2239
- :param priviledge: The name of the privilege. If you do not specify this parameter,
2240
- the API returns information about all privileges for the requested application.
2241
- :param username:
2242
2232
  """
2243
2233
  __path_parts: t.Dict[str, str] = {}
2244
2234
  __path = "/_security/user/_privileges"
2245
2235
  __query: t.Dict[str, t.Any] = {}
2246
- if application is not None:
2247
- __query["application"] = application
2248
2236
  if error_trace is not None:
2249
2237
  __query["error_trace"] = error_trace
2250
2238
  if filter_path is not None:
@@ -2253,10 +2241,6 @@ class SecurityClient(NamespacedClient):
2253
2241
  __query["human"] = human
2254
2242
  if pretty is not None:
2255
2243
  __query["pretty"] = pretty
2256
- if priviledge is not None:
2257
- __query["priviledge"] = priviledge
2258
- if username is not None:
2259
- __query["username"] = username
2260
2244
  __headers = {"accept": "application/json"}
2261
2245
  return self.perform_request( # type: ignore[return-value]
2262
2246
  "GET",
@@ -2345,6 +2329,9 @@ class SecurityClient(NamespacedClient):
2345
2329
  human: t.Optional[bool] = None,
2346
2330
  password: t.Optional[str] = None,
2347
2331
  pretty: t.Optional[bool] = None,
2332
+ refresh: t.Optional[
2333
+ t.Union[bool, str, t.Literal["false", "true", "wait_for"]]
2334
+ ] = None,
2348
2335
  run_as: t.Optional[str] = None,
2349
2336
  username: t.Optional[str] = None,
2350
2337
  body: t.Optional[t.Dict[str, t.Any]] = None,
@@ -2382,6 +2369,9 @@ class SecurityClient(NamespacedClient):
2382
2369
  types.
2383
2370
  :param password: The user's password. If you specify the `password` grant type,
2384
2371
  this parameter is required. It is not valid with other grant types.
2372
+ :param refresh: If 'true', Elasticsearch refreshes the affected shards to make
2373
+ this operation visible to search. If 'wait_for', it waits for a refresh to
2374
+ make this operation visible to search. If 'false', nothing is done with refreshes.
2385
2375
  :param run_as: The name of the user to be impersonated.
2386
2376
  :param username: The user name that identifies the user. If you specify the `password`
2387
2377
  grant type, this parameter is required. It is not valid with other grant
@@ -2403,6 +2393,8 @@ class SecurityClient(NamespacedClient):
2403
2393
  __query["human"] = human
2404
2394
  if pretty is not None:
2405
2395
  __query["pretty"] = pretty
2396
+ if refresh is not None:
2397
+ __query["refresh"] = refresh
2406
2398
  if not __body:
2407
2399
  if api_key is not None:
2408
2400
  __body["api_key"] = api_key
@@ -3553,7 +3545,8 @@ class SecurityClient(NamespacedClient):
3553
3545
  You can optionally filter the results with a query.</p>
3554
3546
  <p>To use this API, you must have at least the <code>manage_own_api_key</code> or the <code>read_security</code> cluster privileges.
3555
3547
  If you have only the <code>manage_own_api_key</code> privilege, this API returns only the API keys that you own.
3556
- If you have the <code>read_security</code>, <code>manage_api_key</code>, or greater privileges (including <code>manage_security</code>), this API returns all API keys regardless of ownership.</p>
3548
+ If you have the <code>read_security</code>, <code>manage_api_key</code>, or greater privileges (including <code>manage_security</code>), this API returns all API keys regardless of ownership.
3549
+ Refer to the linked documentation for examples of how to find API keys:</p>
3557
3550
 
3558
3551
 
3559
3552
  `<https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-security-query-api-keys>`_
@@ -4466,6 +4459,7 @@ class SecurityClient(NamespacedClient):
4466
4459
  <p>This API supports updates to an API key's access scope, metadata, and expiration.
4467
4460
  The owner user's information, such as the <code>username</code> and <code>realm</code>, is also updated automatically on every call.</p>
4468
4461
  <p>NOTE: This API cannot update REST API keys, which should be updated by either the update API key or bulk update API keys API.</p>
4462
+ <p>To learn more about how to use this API, refer to the <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/update-cc-api-key-examples">Update cross cluter API key API examples page</a>.</p>
4469
4463
 
4470
4464
 
4471
4465
  `<https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-security-update-cross-cluster-api-key>`_
@@ -403,6 +403,7 @@ class SnapshotClient(NamespacedClient):
403
403
  human: t.Optional[bool] = None,
404
404
  master_timeout: t.Optional[t.Union[str, t.Literal[-1], t.Literal[0]]] = None,
405
405
  pretty: t.Optional[bool] = None,
406
+ wait_for_completion: t.Optional[bool] = None,
406
407
  ) -> ObjectApiResponse[t.Any]:
407
408
  """
408
409
  .. raw:: html
@@ -418,6 +419,9 @@ class SnapshotClient(NamespacedClient):
418
419
  :param master_timeout: The period to wait for the master node. If the master
419
420
  node is not available before the timeout expires, the request fails and returns
420
421
  an error. To indicate that the request should never timeout, set it to `-1`.
422
+ :param wait_for_completion: If `true`, the request returns a response when the
423
+ matching snapshots are all deleted. If `false`, the request returns a response
424
+ as soon as the deletes are scheduled.
421
425
  """
422
426
  if repository in SKIP_IN_PATH:
423
427
  raise ValueError("Empty value passed for parameter 'repository'")
@@ -439,6 +443,8 @@ class SnapshotClient(NamespacedClient):
439
443
  __query["master_timeout"] = master_timeout
440
444
  if pretty is not None:
441
445
  __query["pretty"] = pretty
446
+ if wait_for_completion is not None:
447
+ __query["wait_for_completion"] = wait_for_completion
442
448
  __headers = {"accept": "application/json"}
443
449
  return self.perform_request( # type: ignore[return-value]
444
450
  "DELETE",
@@ -309,6 +309,7 @@ class SynonymsClient(NamespacedClient):
309
309
  If you need to manage more synonym rules, you can create multiple synonym sets.</p>
310
310
  <p>When an existing synonyms set is updated, the search analyzers that use the synonyms set are reloaded automatically for all indices.
311
311
  This is equivalent to invoking the reload search analyzers API for all indices that use the synonyms set.</p>
312
+ <p>For practical examples of how to create or update a synonyms set, refer to the External documentation.</p>
312
313
 
313
314
 
314
315
  `<https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-synonyms-put-synonym>`_
@@ -45,7 +45,8 @@ class WatcherClient(NamespacedClient):
45
45
  <p>IMPORTANT: If the specified watch is currently being executed, this API will return an error
46
46
  The reason for this behavior is to prevent overwriting the watch status from a watch execution.</p>
47
47
  <p>Acknowledging an action throttles further executions of that action until its <code>ack.state</code> is reset to <code>awaits_successful_execution</code>.
48
- This happens when the condition of the watch is not met (the condition evaluates to false).</p>
48
+ This happens when the condition of the watch is not met (the condition evaluates to false).
49
+ To demonstrate how throttling works in practice and how it can be configured for individual actions within a watch, refer to External documentation.</p>
49
50
 
50
51
 
51
52
  `<https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-watcher-ack-watch>`_
@@ -274,7 +275,8 @@ class WatcherClient(NamespacedClient):
274
275
  This serves as great tool for testing and debugging your watches prior to adding them to Watcher.</p>
275
276
  <p>When Elasticsearch security features are enabled on your cluster, watches are run with the privileges of the user that stored the watches.
276
277
  If your user is allowed to read index <code>a</code>, but not index <code>b</code>, then the exact same set of rules will apply during execution of a watch.</p>
277
- <p>When using the run watch API, the authorization data of the user that called the API will be used as a base, instead of the information who stored the watch.</p>
278
+ <p>When using the run watch API, the authorization data of the user that called the API will be used as a base, instead of the information who stored the watch.
279
+ Refer to the external documentation for examples of watch execution requests, including existing, customized, and inline watches.</p>
278
280
 
279
281
 
280
282
  `<https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-watcher-execute-watch>`_
@@ -15,4 +15,4 @@
15
15
  # specific language governing permissions and limitations
16
16
  # under the License.
17
17
 
18
- __versionstr__ = "9.0.2"
18
+ __versionstr__ = "9.0.3"
elasticsearch9/compat.py CHANGED
@@ -16,12 +16,15 @@
16
16
  # under the License.
17
17
 
18
18
  import inspect
19
+ import os
19
20
  import sys
20
21
  from pathlib import Path
21
22
  from typing import Tuple, Type, Union
22
23
 
23
24
  string_types: Tuple[Type[str], Type[bytes]] = (str, bytes)
24
25
 
26
+ DISABLE_WARN_STACKLEVEL_ENV_VAR = "DISABLE_WARN_STACKLEVEL"
27
+
25
28
 
26
29
  def to_str(x: Union[str, bytes], encoding: str = "ascii") -> str:
27
30
  if not isinstance(x, str):
@@ -37,6 +40,8 @@ def to_bytes(x: Union[str, bytes], encoding: str = "ascii") -> bytes:
37
40
 
38
41
  def warn_stacklevel() -> int:
39
42
  """Dynamically determine warning stacklevel for warnings based on the call stack"""
43
+ if os.environ.get(DISABLE_WARN_STACKLEVEL_ENV_VAR) in ["1", "true", "True"]:
44
+ return 0
40
45
  try:
41
46
  # Grab the root module from the current module '__name__'
42
47
  module_name = __name__.partition(".")[0]
@@ -19,7 +19,7 @@ from . import async_connections, connections
19
19
  from .aggs import A, Agg
20
20
  from .analysis import analyzer, char_filter, normalizer, token_filter, tokenizer
21
21
  from .document import AsyncDocument, Document
22
- from .document_base import InnerDoc, M, MetaField, mapped_field
22
+ from .document_base import E, InnerDoc, M, MetaField, mapped_field
23
23
  from .exceptions import (
24
24
  ElasticsearchDslException,
25
25
  IllegalOperation,
@@ -135,6 +135,7 @@ __all__ = [
135
135
  "Double",
136
136
  "DoubleRange",
137
137
  "DslBase",
138
+ "E",
138
139
  "ElasticsearchDslException",
139
140
  "EmptySearch",
140
141
  "Facet",
@@ -15,6 +15,7 @@
15
15
  # specific language governing permissions and limitations
16
16
  # under the License.
17
17
 
18
+ import json
18
19
  from datetime import date, datetime
19
20
  from fnmatch import fnmatch
20
21
  from typing import (
@@ -56,7 +57,163 @@ class MetaField:
56
57
  self.args, self.kwargs = args, kwargs
57
58
 
58
59
 
59
- class InstrumentedField:
60
+ class InstrumentedExpression:
61
+ """Proxy object for a ES|QL expression."""
62
+
63
+ def __init__(self, expr: str):
64
+ self._expr = expr
65
+
66
+ def _render_value(self, value: Any) -> str:
67
+ if isinstance(value, InstrumentedExpression):
68
+ return str(value)
69
+ return json.dumps(value)
70
+
71
+ def __str__(self) -> str:
72
+ return self._expr
73
+
74
+ def __repr__(self) -> str:
75
+ return f"InstrumentedExpression[{self._expr}]"
76
+
77
+ def __pos__(self) -> "InstrumentedExpression":
78
+ return self
79
+
80
+ def __neg__(self) -> "InstrumentedExpression":
81
+ return InstrumentedExpression(f"-({self._expr})")
82
+
83
+ def __eq__(self, value: Any) -> "InstrumentedExpression": # type: ignore[override]
84
+ return InstrumentedExpression(f"{self._expr} == {self._render_value(value)}")
85
+
86
+ def __ne__(self, value: Any) -> "InstrumentedExpression": # type: ignore[override]
87
+ return InstrumentedExpression(f"{self._expr} != {self._render_value(value)}")
88
+
89
+ def __lt__(self, value: Any) -> "InstrumentedExpression":
90
+ return InstrumentedExpression(f"{self._expr} < {self._render_value(value)}")
91
+
92
+ def __gt__(self, value: Any) -> "InstrumentedExpression":
93
+ return InstrumentedExpression(f"{self._expr} > {self._render_value(value)}")
94
+
95
+ def __le__(self, value: Any) -> "InstrumentedExpression":
96
+ return InstrumentedExpression(f"{self._expr} <= {self._render_value(value)}")
97
+
98
+ def __ge__(self, value: Any) -> "InstrumentedExpression":
99
+ return InstrumentedExpression(f"{self._expr} >= {self._render_value(value)}")
100
+
101
+ def __add__(self, value: Any) -> "InstrumentedExpression":
102
+ return InstrumentedExpression(f"{self._expr} + {self._render_value(value)}")
103
+
104
+ def __radd__(self, value: Any) -> "InstrumentedExpression":
105
+ return InstrumentedExpression(f"{self._render_value(value)} + {self._expr}")
106
+
107
+ def __sub__(self, value: Any) -> "InstrumentedExpression":
108
+ return InstrumentedExpression(f"{self._expr} - {self._render_value(value)}")
109
+
110
+ def __rsub__(self, value: Any) -> "InstrumentedExpression":
111
+ return InstrumentedExpression(f"{self._render_value(value)} - {self._expr}")
112
+
113
+ def __mul__(self, value: Any) -> "InstrumentedExpression":
114
+ return InstrumentedExpression(f"{self._expr} * {self._render_value(value)}")
115
+
116
+ def __rmul__(self, value: Any) -> "InstrumentedExpression":
117
+ return InstrumentedExpression(f"{self._render_value(value)} * {self._expr}")
118
+
119
+ def __truediv__(self, value: Any) -> "InstrumentedExpression":
120
+ return InstrumentedExpression(f"{self._expr} / {self._render_value(value)}")
121
+
122
+ def __rtruediv__(self, value: Any) -> "InstrumentedExpression":
123
+ return InstrumentedExpression(f"{self._render_value(value)} / {self._expr}")
124
+
125
+ def __mod__(self, value: Any) -> "InstrumentedExpression":
126
+ return InstrumentedExpression(f"{self._expr} % {self._render_value(value)}")
127
+
128
+ def __rmod__(self, value: Any) -> "InstrumentedExpression":
129
+ return InstrumentedExpression(f"{self._render_value(value)} % {self._expr}")
130
+
131
+ def is_null(self) -> "InstrumentedExpression":
132
+ """Compare the expression against NULL."""
133
+ return InstrumentedExpression(f"{self._expr} IS NULL")
134
+
135
+ def is_not_null(self) -> "InstrumentedExpression":
136
+ """Compare the expression against NOT NULL."""
137
+ return InstrumentedExpression(f"{self._expr} IS NOT NULL")
138
+
139
+ def in_(self, *values: Any) -> "InstrumentedExpression":
140
+ """Test if the expression equals one of the given values."""
141
+ rendered_values = ", ".join([f"{value}" for value in values])
142
+ return InstrumentedExpression(f"{self._expr} IN ({rendered_values})")
143
+
144
+ def like(self, *patterns: str) -> "InstrumentedExpression":
145
+ """Filter the expression using a string pattern."""
146
+ if len(patterns) == 1:
147
+ return InstrumentedExpression(
148
+ f"{self._expr} LIKE {self._render_value(patterns[0])}"
149
+ )
150
+ else:
151
+ return InstrumentedExpression(
152
+ f'{self._expr} LIKE ({", ".join([self._render_value(p) for p in patterns])})'
153
+ )
154
+
155
+ def rlike(self, *patterns: str) -> "InstrumentedExpression":
156
+ """Filter the expression using a regular expression."""
157
+ if len(patterns) == 1:
158
+ return InstrumentedExpression(
159
+ f"{self._expr} RLIKE {self._render_value(patterns[0])}"
160
+ )
161
+ else:
162
+ return InstrumentedExpression(
163
+ f'{self._expr} RLIKE ({", ".join([self._render_value(p) for p in patterns])})'
164
+ )
165
+
166
+ def match(self, query: str) -> "InstrumentedExpression":
167
+ """Perform a match query on the field."""
168
+ return InstrumentedExpression(f"{self._expr}:{self._render_value(query)}")
169
+
170
+ def asc(self) -> "InstrumentedExpression":
171
+ """Return the field name representation for ascending sort order.
172
+
173
+ For use in ES|QL queries only.
174
+ """
175
+ return InstrumentedExpression(f"{self._expr} ASC")
176
+
177
+ def desc(self) -> "InstrumentedExpression":
178
+ """Return the field name representation for descending sort order.
179
+
180
+ For use in ES|QL queries only.
181
+ """
182
+ return InstrumentedExpression(f"{self._expr} DESC")
183
+
184
+ def nulls_first(self) -> "InstrumentedExpression":
185
+ """Return the field name representation for nulls first sort order.
186
+
187
+ For use in ES|QL queries only.
188
+ """
189
+ return InstrumentedExpression(f"{self._expr} NULLS FIRST")
190
+
191
+ def nulls_last(self) -> "InstrumentedExpression":
192
+ """Return the field name representation for nulls last sort order.
193
+
194
+ For use in ES|QL queries only.
195
+ """
196
+ return InstrumentedExpression(f"{self._expr} NULLS LAST")
197
+
198
+ def where(
199
+ self, *expressions: Union[str, "InstrumentedExpression"]
200
+ ) -> "InstrumentedExpression":
201
+ """Add a condition to be met for the row to be included.
202
+
203
+ Use only in expressions given in the ``STATS`` command.
204
+ """
205
+ if len(expressions) == 1:
206
+ return InstrumentedExpression(f"{self._expr} WHERE {expressions[0]}")
207
+ else:
208
+ return InstrumentedExpression(
209
+ f'{self._expr} WHERE {" AND ".join([f"({expr})" for expr in expressions])}'
210
+ )
211
+
212
+
213
+ E = InstrumentedExpression
214
+
215
+
216
+ class InstrumentedField(InstrumentedExpression):
60
217
  """Proxy object for a mapped document field.
61
218
 
62
219
  An object of this instance is returned when a field is accessed as a class
@@ -71,8 +228,8 @@ class InstrumentedField:
71
228
  s = s.sort(-MyDocument.name) # sort by name in descending order
72
229
  """
73
230
 
74
- def __init__(self, name: str, field: Field):
75
- self._name = name
231
+ def __init__(self, name: str, field: Optional[Field]):
232
+ super().__init__(name)
76
233
  self._field = field
77
234
 
78
235
  # note that the return value type here assumes classes will only be used to
@@ -83,26 +240,29 @@ class InstrumentedField:
83
240
  # first let's see if this is an attribute of this object
84
241
  return super().__getattribute__(attr) # type: ignore[no-any-return]
85
242
  except AttributeError:
86
- try:
87
- # next we see if we have a sub-field with this name
88
- return InstrumentedField(f"{self._name}.{attr}", self._field[attr])
89
- except KeyError:
90
- # lastly we let the wrapped field resolve this attribute
91
- return getattr(self._field, attr) # type: ignore[no-any-return]
92
-
93
- def __pos__(self) -> str:
243
+ if self._field:
244
+ try:
245
+ # next we see if we have a sub-field with this name
246
+ return InstrumentedField(f"{self._expr}.{attr}", self._field[attr])
247
+ except KeyError:
248
+ # lastly we let the wrapped field resolve this attribute
249
+ return getattr(self._field, attr) # type: ignore[no-any-return]
250
+ else:
251
+ raise
252
+
253
+ def __pos__(self) -> str: # type: ignore[override]
94
254
  """Return the field name representation for ascending sort order"""
95
- return f"{self._name}"
255
+ return f"{self._expr}"
96
256
 
97
- def __neg__(self) -> str:
257
+ def __neg__(self) -> str: # type: ignore[override]
98
258
  """Return the field name representation for descending sort order"""
99
- return f"-{self._name}"
259
+ return f"-{self._expr}"
100
260
 
101
261
  def __str__(self) -> str:
102
- return self._name
262
+ return self._expr
103
263
 
104
264
  def __repr__(self) -> str:
105
- return f"InstrumentedField[{self._name}]"
265
+ return f"InstrumentedField[{self._expr}]"
106
266
 
107
267
 
108
268
  class DocumentMeta(type):