Skip to content

Commit 01733cf

Browse files
committed
Merge branch 'master' of github.com:mongodb/mongo-python-driver
2 parents d952970 + 8034bae commit 01733cf

10 files changed

+280
-4
lines changed

doc/changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ PyMongo 4.11 brings a number of changes including:
1717
- :attr:`~pymongo.asynchronous.mongo_client.AsyncMongoClient.address` and
1818
:attr:`~pymongo.mongo_client.MongoClient.address` now correctly block when called on unconnected clients
1919
until either connection succeeds or a server selection timeout error is raised.
20+
- Added :func:`repr` support to :class:`pymongo.operations.IndexModel`.
21+
- Added :func:`repr` support to :class:`pymongo.operations.SearchIndexModel`.
2022

2123
Issues Resolved
2224
...............

pymongo/operations.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,13 @@ def document(self) -> dict[str, Any]:
773773
"""
774774
return self.__document
775775

776+
def __repr__(self) -> str:
777+
return "{}({}{})".format(
778+
self.__class__.__name__,
779+
self.document["key"],
780+
"".join([f", {key}={value!r}" for key, value in self.document.items() if key != "key"]),
781+
)
782+
776783

777784
class SearchIndexModel:
778785
"""Represents a search index to create."""
@@ -812,3 +819,9 @@ def __init__(
812819
def document(self) -> Mapping[str, Any]:
813820
"""The document for this index."""
814821
return self.__document
822+
823+
def __repr__(self) -> str:
824+
return "{}({})".format(
825+
self.__class__.__name__,
826+
", ".join([f"{key}={value!r}" for key, value in self.document.items()]),
827+
)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright 2021-present MongoDB, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Test the collection management unified spec tests."""
16+
from __future__ import annotations
17+
18+
import os
19+
import pathlib
20+
import sys
21+
22+
sys.path[0:0] = [""]
23+
24+
from test import unittest
25+
from test.asynchronous.unified_format import generate_test_classes
26+
27+
_IS_SYNC = False
28+
29+
# Location of JSON test specifications.
30+
if _IS_SYNC:
31+
_TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "collection_management")
32+
else:
33+
_TEST_PATH = os.path.join(
34+
pathlib.Path(__file__).resolve().parent.parent, "collection_management"
35+
)
36+
37+
# Generate unified tests.
38+
globals().update(generate_test_classes(_TEST_PATH, module=__name__))
39+
40+
if __name__ == "__main__":
41+
unittest.main()
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Copyright 2021-present MongoDB, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
from __future__ import annotations
15+
16+
import sys
17+
import unittest
18+
19+
sys.path[0:0] = [""]
20+
21+
from test.asynchronous import AsyncIntegrationTest
22+
from test.asynchronous.unified_format import UnifiedSpecTestMixinV1
23+
24+
_IS_SYNC = False
25+
26+
27+
class TestCreateEntities(AsyncIntegrationTest):
28+
async def test_store_events_as_entities(self):
29+
self.scenario_runner = UnifiedSpecTestMixinV1()
30+
spec = {
31+
"description": "blank",
32+
"schemaVersion": "1.2",
33+
"createEntities": [
34+
{
35+
"client": {
36+
"id": "client0",
37+
"storeEventsAsEntities": [
38+
{
39+
"id": "events1",
40+
"events": [
41+
"PoolCreatedEvent",
42+
],
43+
}
44+
],
45+
}
46+
},
47+
],
48+
"tests": [{"description": "foo", "operations": []}],
49+
}
50+
self.scenario_runner.TEST_SPEC = spec
51+
await self.scenario_runner.asyncSetUp()
52+
await self.scenario_runner.run_scenario(spec["tests"][0])
53+
await self.scenario_runner.entity_map["client0"].close()
54+
final_entity_map = self.scenario_runner.entity_map
55+
self.assertIn("events1", final_entity_map)
56+
self.assertGreater(len(final_entity_map["events1"]), 0)
57+
for event in final_entity_map["events1"]:
58+
self.assertIn("PoolCreatedEvent", event["name"])
59+
60+
async def test_store_all_others_as_entities(self):
61+
self.scenario_runner = UnifiedSpecTestMixinV1()
62+
spec = {
63+
"description": "Find",
64+
"schemaVersion": "1.2",
65+
"createEntities": [
66+
{
67+
"client": {
68+
"id": "client0",
69+
"uriOptions": {"retryReads": True},
70+
}
71+
},
72+
{"database": {"id": "database0", "client": "client0", "databaseName": "dat"}},
73+
{
74+
"collection": {
75+
"id": "collection0",
76+
"database": "database0",
77+
"collectionName": "dat",
78+
}
79+
},
80+
],
81+
"tests": [
82+
{
83+
"description": "test loops",
84+
"operations": [
85+
{
86+
"name": "loop",
87+
"object": "testRunner",
88+
"arguments": {
89+
"storeIterationsAsEntity": "iterations",
90+
"storeSuccessesAsEntity": "successes",
91+
"storeFailuresAsEntity": "failures",
92+
"storeErrorsAsEntity": "errors",
93+
"numIterations": 5,
94+
"operations": [
95+
{
96+
"name": "insertOne",
97+
"object": "collection0",
98+
"arguments": {"document": {"_id": 1, "x": 44}},
99+
},
100+
{
101+
"name": "insertOne",
102+
"object": "collection0",
103+
"arguments": {"document": {"_id": 2, "x": 44}},
104+
},
105+
],
106+
},
107+
}
108+
],
109+
}
110+
],
111+
}
112+
113+
await self.client.dat.dat.delete_many({})
114+
self.scenario_runner.TEST_SPEC = spec
115+
await self.scenario_runner.asyncSetUp()
116+
await self.scenario_runner.run_scenario(spec["tests"][0])
117+
await self.scenario_runner.entity_map["client0"].close()
118+
entity_map = self.scenario_runner.entity_map
119+
self.assertEqual(len(entity_map["errors"]), 4)
120+
for error in entity_map["errors"]:
121+
self.assertEqual(error["type"], "DuplicateKeyError")
122+
self.assertEqual(entity_map["failures"], [])
123+
self.assertEqual(entity_map["successes"], 2)
124+
self.assertEqual(entity_map["iterations"], 5)
125+
126+
127+
if __name__ == "__main__":
128+
unittest.main()

test/asynchronous/unified_format.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,7 @@ async def _databaseOperation_listCollections(self, target, *args, **kwargs):
773773
if "batch_size" in kwargs:
774774
kwargs["cursor"] = {"batchSize": kwargs.pop("batch_size")}
775775
cursor = await target.list_collections(*args, **kwargs)
776-
return list(cursor)
776+
return await cursor.to_list()
777777

778778
async def _databaseOperation_createCollection(self, target, *args, **kwargs):
779779
# PYTHON-1936 Ignore the listCollections event from create_collection.

test/test_collection_management.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,26 @@
1616
from __future__ import annotations
1717

1818
import os
19+
import pathlib
1920
import sys
2021

2122
sys.path[0:0] = [""]
2223

2324
from test import unittest
2425
from test.unified_format import generate_test_classes
2526

27+
_IS_SYNC = True
28+
2629
# Location of JSON test specifications.
27-
TEST_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "collection_management")
30+
if _IS_SYNC:
31+
_TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "collection_management")
32+
else:
33+
_TEST_PATH = os.path.join(
34+
pathlib.Path(__file__).resolve().parent.parent, "collection_management"
35+
)
2836

2937
# Generate unified tests.
30-
globals().update(generate_test_classes(TEST_PATH, module=__name__))
38+
globals().update(generate_test_classes(_TEST_PATH, module=__name__))
3139

3240
if __name__ == "__main__":
3341
unittest.main()

test/test_create_entities.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
from test import IntegrationTest
2222
from test.unified_format import UnifiedSpecTestMixinV1
2323

24+
_IS_SYNC = True
25+
2426

2527
class TestCreateEntities(IntegrationTest):
2628
def test_store_events_as_entities(self):

test/test_operations.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Copyright 2024-present MongoDB, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Test the operations module."""
16+
from __future__ import annotations
17+
18+
from test import UnitTest, unittest
19+
20+
from pymongo import ASCENDING, DESCENDING
21+
from pymongo.collation import Collation
22+
from pymongo.errors import OperationFailure
23+
from pymongo.operations import IndexModel, SearchIndexModel
24+
25+
26+
class TestOperationsBase(UnitTest):
27+
"""Base class for testing operations module."""
28+
29+
def assertRepr(self, obj):
30+
new_obj = eval(repr(obj))
31+
self.assertEqual(type(new_obj), type(obj))
32+
self.assertEqual(repr(new_obj), repr(obj))
33+
34+
35+
class TestIndexModel(TestOperationsBase):
36+
"""Test IndexModel features."""
37+
38+
def test_repr(self):
39+
# Based on examples in test_collection.py
40+
self.assertRepr(IndexModel("hello"))
41+
self.assertRepr(IndexModel([("hello", DESCENDING), ("world", ASCENDING)]))
42+
self.assertRepr(
43+
IndexModel([("hello", DESCENDING), ("world", ASCENDING)], name="hello_world")
44+
)
45+
# Test all the kwargs
46+
self.assertRepr(IndexModel("name", name="name"))
47+
self.assertRepr(IndexModel("unique", unique=False))
48+
self.assertRepr(IndexModel("background", background=True))
49+
self.assertRepr(IndexModel("sparse", sparse=True))
50+
self.assertRepr(IndexModel("bucketSize", bucketSize=1))
51+
self.assertRepr(IndexModel("min", min=1))
52+
self.assertRepr(IndexModel("max", max=1))
53+
self.assertRepr(IndexModel("expireAfterSeconds", expireAfterSeconds=1))
54+
self.assertRepr(
55+
IndexModel("partialFilterExpression", partialFilterExpression={"hello": "world"})
56+
)
57+
self.assertRepr(IndexModel("collation", collation=Collation(locale="en_US")))
58+
self.assertRepr(IndexModel("wildcardProjection", wildcardProjection={"$**": 1}))
59+
self.assertRepr(IndexModel("hidden", hidden=False))
60+
# Test string literal
61+
self.assertEqual(repr(IndexModel("hello")), "IndexModel({'hello': 1}, name='hello_1')")
62+
self.assertEqual(
63+
repr(IndexModel({"hello": 1, "world": -1})),
64+
"IndexModel({'hello': 1, 'world': -1}, name='hello_1_world_-1')",
65+
)
66+
67+
68+
class TestSearchIndexModel(TestOperationsBase):
69+
"""Test SearchIndexModel features."""
70+
71+
def test_repr(self):
72+
self.assertRepr(SearchIndexModel({"hello": "hello"}, key=1))
73+
self.assertEqual(
74+
repr(SearchIndexModel({"hello": "hello"}, key=1)),
75+
"SearchIndexModel(definition={'hello': 'hello'}, key=1)",
76+
)
77+
78+
79+
if __name__ == "__main__":
80+
unittest.main()

test/unified_format.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,7 @@ def _databaseOperation_listCollections(self, target, *args, **kwargs):
769769
if "batch_size" in kwargs:
770770
kwargs["cursor"] = {"batchSize": kwargs.pop("batch_size")}
771771
cursor = target.list_collections(*args, **kwargs)
772-
return list(cursor)
772+
return cursor.to_list()
773773

774774
def _databaseOperation_createCollection(self, target, *args, **kwargs):
775775
# PYTHON-1936 Ignore the listCollections event from create_collection.

tools/synchro.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,13 +192,15 @@ def async_only_test(f: str) -> bool:
192192
"test_client_context.py",
193193
"test_collation.py",
194194
"test_collection.py",
195+
"test_collection_management.py",
195196
"test_command_logging.py",
196197
"test_command_logging.py",
197198
"test_command_monitoring.py",
198199
"test_comment.py",
199200
"test_common.py",
200201
"test_connection_logging.py",
201202
"test_connections_survive_primary_stepdown_spec.py",
203+
"test_create_entities.py",
202204
"test_crud_unified.py",
203205
"test_cursor.py",
204206
"test_database.py",

0 commit comments

Comments
 (0)