Skip to content

Commit 4fe56f0

Browse files
committed
Merge remote-tracking branch 'origin/main' into trio-support
2 parents 07e4292 + fcdbbc0 commit 4fe56f0

File tree

10 files changed

+97
-16
lines changed

10 files changed

+97
-16
lines changed

.buildkite/functions/imports.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ if [[ -z $es_node_name ]]; then
4343

4444
fi
4545

46-
export script_path=$(dirname $(realpath -s $0))
46+
export script_path=$(dirname $(realpath $0))
4747
source $script_path/functions/cleanup.sh
4848
source $script_path/functions/wait-for-container.sh
4949
trap "cleanup_trap ${network_name}" EXIT

.buildkite/run-elasticsearch.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
# - Moved ELASTIC_PASSWORD and xpack.security.enabled to the base arguments for "Security On by default"
2222
# - Use https only when TEST_SUITE is "platinum", when "free" use http
2323

24-
script_path=$(dirname $(realpath -s $0))
24+
script_path=$(dirname $(realpath $0))
2525
source $script_path/functions/imports.sh
2626
set -euo pipefail
2727

.buildkite/run-tests

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export TEST_SUITE="${TEST_SUITE:=platinum}"
1010
export PYTHON_VERSION="${PYTHON_VERSION:=3.13}"
1111
export PYTHON_CONNECTION_CLASS="${PYTHON_CONNECTION_CLASS:=urllib3}"
1212

13-
script_path=$(dirname $(realpath -s $0))
13+
script_path=$(dirname $(realpath $0))
1414
source $script_path/functions/imports.sh
1515
set -euo pipefail
1616

docs/reference/connecting.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -277,13 +277,6 @@ client = Elasticsearch(
277277
```
278278

279279

280-
## Enabling the Compatibility Mode [compatibility-mode]
281-
282-
The {{es}} server version 8.0 is introducing a new compatibility mode that allows you a smoother upgrade experience from 7 to 8. In a nutshell, you can use the latest 7.x Python {{es}} {{es}} client with an 8.x {{es}} server, giving more room to coordinate the upgrade of your codebase to the next major version.
283-
284-
If you want to leverage this functionality, please make sure that you are using the latest 7.x Python {{es}} client and set the environment variable `ELASTIC_CLIENT_APIVERSIONING` to `true`. The client is handling the rest internally. For every 8.0 and beyond Python {{es}} client, you’re all set! The compatibility mode is enabled by default.
285-
286-
287280
## Using the Client in a Function-as-a-Service Environment [connecting-faas]
288281

289282
This section illustrates the best practices for leveraging the {{es}} client in a Function-as-a-Service (FaaS) environment.

docs/reference/index.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,14 @@ Compatibility does not imply full feature parity. New {{es}} features are suppor
6969

7070
{{es}} language clients are also _backward compatible_ across minor versions — with default distributions and without guarantees.
7171

72+
### Major version upgrades
73+
7274
:::{tip}
7375
To upgrade to a new major version, first upgrade {{es}}, then upgrade the Python {{es}} client.
7476
:::
7577

76-
If you need to work with multiple client versions, note that older versions are also released as `elasticsearch7` and `elasticsearch8`.
78+
Since version 8.0, the {{es}} server supports a compatibility mode that allows smoother upgrade experiences. In a nutshell, this makes it possible to upgrade the {{es}} server to the next major version, while continuing to use the same client. This gives more room to coordinate the upgrade of your codebase to the next major version.
79+
80+
For example, to upgrade a system that uses {{es}} 8.x you can upgrade the {{es}} server to 9.x first, and the 8.x Python {{es}} client will continue to work (aside from any breaking changes, which should be listed in the server release notes). You can continue using the 8.x client during the server migration, and only upgrade it once the server migration is complete. The process is described in detail in the [REST API compatibility workflow](https://www.elastic.co/docs/reference/elasticsearch/rest-apis/compatibility#_rest_api_compatibility_workflow) section of the {{es}} documentation.
81+
82+
If you need to work with multiple client versions, note that older versions are also released with the `elasticsearch8` and `elasticsearch9` package names so that they can be installed together.

elasticsearch/dsl/field.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,11 @@ def _serialize(
572572
if isinstance(data, collections.abc.Mapping):
573573
return data
574574

575-
return data.to_dict(skip_empty=skip_empty)
575+
try:
576+
return data.to_dict(skip_empty=skip_empty)
577+
except TypeError:
578+
# this would only happen if an AttrDict was given instead of an InnerDoc
579+
return data.to_dict()
576580

577581
def clean(self, data: Any) -> Any:
578582
data = super().clean(data)

test_elasticsearch/test_dsl/test_field.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,14 @@
2424
from dateutil import tz
2525

2626
from elasticsearch import dsl
27-
from elasticsearch.dsl import InnerDoc, Range, ValidationException, field
27+
from elasticsearch.dsl import (
28+
AttrDict,
29+
AttrList,
30+
InnerDoc,
31+
Range,
32+
ValidationException,
33+
field,
34+
)
2835

2936

3037
def test_date_range_deserialization() -> None:
@@ -235,6 +242,33 @@ class Inner(InnerDoc):
235242
field.Object(doc_class=Inner, dynamic=False)
236243

237244

245+
def test_dynamic_object() -> None:
246+
f = field.Object(dynamic=True)
247+
assert f.deserialize({"a": "b"}).to_dict() == {"a": "b"}
248+
assert f.deserialize(AttrDict({"a": "b"})).to_dict() == {"a": "b"}
249+
assert f.serialize({"a": "b"}) == {"a": "b"}
250+
assert f.serialize(AttrDict({"a": "b"})) == {"a": "b"}
251+
252+
253+
def test_dynamic_nested() -> None:
254+
f = field.Nested(dynamic=True)
255+
assert f.deserialize([{"a": "b"}, {"c": "d"}]) == [{"a": "b"}, {"c": "d"}]
256+
assert f.deserialize([AttrDict({"a": "b"}), {"c": "d"}]) == [
257+
{"a": "b"},
258+
{"c": "d"},
259+
]
260+
assert f.deserialize(AttrList([AttrDict({"a": "b"}), {"c": "d"}])) == [
261+
{"a": "b"},
262+
{"c": "d"},
263+
]
264+
assert f.serialize([{"a": "b"}, {"c": "d"}]) == [{"a": "b"}, {"c": "d"}]
265+
assert f.serialize([AttrDict({"a": "b"}), {"c": "d"}]) == [{"a": "b"}, {"c": "d"}]
266+
assert f.serialize(AttrList([AttrDict({"a": "b"}), {"c": "d"}])) == [
267+
{"a": "b"},
268+
{"c": "d"},
269+
]
270+
271+
238272
def test_all_fields_exported() -> None:
239273
"""Make sure that all the generated field classes are exported at the top-level"""
240274
fields = [

test_elasticsearch/test_dsl/test_integration/_async/test_document.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from elasticsearch.dsl import (
3434
AsyncDocument,
3535
AsyncSearch,
36+
AttrDict,
3637
Binary,
3738
Boolean,
3839
Date,
@@ -627,13 +628,17 @@ async def test_can_save_to_different_index(
627628

628629

629630
@pytest.mark.anyio
631+
@pytest.mark.parametrize("validate", (True, False))
630632
async def test_save_without_skip_empty_will_include_empty_fields(
631633
async_write_client: AsyncElasticsearch,
634+
validate: bool,
632635
) -> None:
633636
test_repo = Repository(
634637
field_1=[], field_2=None, field_3={}, owner={"name": None}, meta={"id": 42}
635638
)
636-
assert await test_repo.save(index="test-document", skip_empty=False)
639+
assert await test_repo.save(
640+
index="test-document", skip_empty=False, validate=validate
641+
)
637642

638643
assert_doc_equals(
639644
{
@@ -650,6 +655,23 @@ async def test_save_without_skip_empty_will_include_empty_fields(
650655
await async_write_client.get(index="test-document", id=42),
651656
)
652657

658+
test_repo = Repository(owner=AttrDict({"name": None}), meta={"id": 43})
659+
assert await test_repo.save(
660+
index="test-document", skip_empty=False, validate=validate
661+
)
662+
663+
assert_doc_equals(
664+
{
665+
"found": True,
666+
"_index": "test-document",
667+
"_id": "43",
668+
"_source": {
669+
"owner": {"name": None},
670+
},
671+
},
672+
await async_write_client.get(index="test-document", id=43),
673+
)
674+
653675

654676
@pytest.mark.anyio
655677
async def test_delete(async_write_client: AsyncElasticsearch) -> None:

test_elasticsearch/test_dsl/test_integration/_sync/test_document.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
from elasticsearch import ConflictError, Elasticsearch, NotFoundError
3333
from elasticsearch.dsl import (
34+
AttrDict,
3435
Binary,
3536
Boolean,
3637
Date,
@@ -621,13 +622,15 @@ def test_can_save_to_different_index(
621622

622623

623624
@pytest.mark.sync
625+
@pytest.mark.parametrize("validate", (True, False))
624626
def test_save_without_skip_empty_will_include_empty_fields(
625627
write_client: Elasticsearch,
628+
validate: bool,
626629
) -> None:
627630
test_repo = Repository(
628631
field_1=[], field_2=None, field_3={}, owner={"name": None}, meta={"id": 42}
629632
)
630-
assert test_repo.save(index="test-document", skip_empty=False)
633+
assert test_repo.save(index="test-document", skip_empty=False, validate=validate)
631634

632635
assert_doc_equals(
633636
{
@@ -644,6 +647,21 @@ def test_save_without_skip_empty_will_include_empty_fields(
644647
write_client.get(index="test-document", id=42),
645648
)
646649

650+
test_repo = Repository(owner=AttrDict({"name": None}), meta={"id": 43})
651+
assert test_repo.save(index="test-document", skip_empty=False, validate=validate)
652+
653+
assert_doc_equals(
654+
{
655+
"found": True,
656+
"_index": "test-document",
657+
"_id": "43",
658+
"_source": {
659+
"owner": {"name": None},
660+
},
661+
},
662+
write_client.get(index="test-document", id=43),
663+
)
664+
647665

648666
@pytest.mark.sync
649667
def test_delete(write_client: Elasticsearch) -> None:

utils/templates/field.py.tpl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,11 @@ class {{ k.name }}({{ k.parent }}):
334334
if isinstance(data, collections.abc.Mapping):
335335
return data
336336

337-
return data.to_dict(skip_empty=skip_empty)
337+
try:
338+
return data.to_dict(skip_empty=skip_empty)
339+
except TypeError:
340+
# this would only happen if an AttrDict was given instead of an InnerDoc
341+
return data.to_dict()
338342

339343
def clean(self, data: Any) -> Any:
340344
data = super().clean(data)

0 commit comments

Comments
 (0)