Skip to content

Commit d952970

Browse files
committed
Merge branch 'master' of github.com:mongodb/mongo-python-driver
2 parents 7ad6215 + 1b6c0d3 commit d952970

File tree

10 files changed

+620
-111
lines changed

10 files changed

+620
-111
lines changed

.evergreen/config.yml

Lines changed: 475 additions & 73 deletions
Large diffs are not rendered by default.

.evergreen/hatch.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ else # Set up virtualenv before installing hatch
3434
fi
3535
export HATCH_CONFIG
3636
hatch config restore
37-
hatch config set dirs.data ".hatch/data"
38-
hatch config set dirs.cache ".hatch/cache"
37+
hatch config set dirs.data "$(pwd)/.hatch/data"
38+
hatch config set dirs.cache "$(pwd)/.hatch/cache"
3939

4040
run_hatch() {
4141
python -m hatch run "$@"

.evergreen/run-tests.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ set -o xtrace
3030

3131
AUTH=${AUTH:-noauth}
3232
SSL=${SSL:-nossl}
33-
TEST_SUITES=""
33+
TEST_SUITES=${TEST_SUITES:-}
3434
TEST_ARGS="${*:1}"
3535

3636
export PIP_QUIET=1 # Quiet by default

.evergreen/scripts/generate_config.py

Lines changed: 105 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,17 @@
2626
CPYTHONS = ["3.9", "3.10", "3.11", "3.12", "3.13"]
2727
PYPYS = ["pypy3.9", "pypy3.10"]
2828
ALL_PYTHONS = CPYTHONS + PYPYS
29+
MIN_MAX_PYTHON = [CPYTHONS[0], CPYTHONS[-1]]
2930
BATCHTIME_WEEK = 10080
31+
AUTH_SSLS = [("auth", "ssl"), ("noauth", "ssl"), ("noauth", "nossl")]
32+
TOPOLOGIES = ["standalone", "replica_set", "sharded_cluster"]
33+
SYNCS = ["sync", "async"]
34+
DISPLAY_LOOKUP = dict(
35+
ssl=dict(ssl="SSL", nossl="NoSSL"),
36+
auth=dict(auth="Auth", noauth="NoAuth"),
37+
test_suites=dict(default="Sync", default_async="Async"),
38+
coverage=dict(coverage="cov"),
39+
)
3040
HOSTS = dict()
3141

3242

@@ -35,11 +45,18 @@ class Host:
3545
name: str
3646
run_on: str
3747
display_name: str
48+
expansions: dict[str, str]
3849

3950

40-
HOSTS["rhel8"] = Host("rhel8", "rhel87-small", "RHEL8")
41-
HOSTS["win64"] = Host("win64", "windows-64-vsMulti-small", "Win64")
42-
HOSTS["macos"] = Host("macos", "macos-14", "macOS")
51+
_macos_expansions = dict( # CSOT tests are unreliable on slow hosts.
52+
SKIP_CSOT_TESTS="true"
53+
)
54+
55+
HOSTS["rhel8"] = Host("rhel8", "rhel87-small", "RHEL8", dict())
56+
HOSTS["win64"] = Host("win64", "windows-64-vsMulti-small", "Win64", _macos_expansions)
57+
HOSTS["win32"] = Host("win32", "windows-64-vsMulti-small", "Win32", _macos_expansions)
58+
HOSTS["macos"] = Host("macos", "macos-14", "macOS", _macos_expansions)
59+
HOSTS["macos-arm64"] = Host("macos-arm64", "macos-14-arm64", "macOS Arm64", _macos_expansions)
4360

4461

4562
##############
@@ -67,6 +84,7 @@ def create_variant(
6784
expansions["PYTHON_BINARY"] = get_python_binary(python, host)
6885
if version:
6986
expansions["VERSION"] = version
87+
expansions.update(HOSTS[host].expansions)
7088
expansions = expansions or None
7189
return BuildVariant(
7290
name=name,
@@ -80,10 +98,8 @@ def create_variant(
8098

8199
def get_python_binary(python: str, host: str) -> str:
82100
"""Get the appropriate python binary given a python version and host."""
83-
if host == "win64":
84-
is_32 = python.startswith("32-bit")
85-
if is_32:
86-
_, python = python.split()
101+
if host in ["win64", "win32"]:
102+
if host == "win32":
87103
base = "C:/python/32"
88104
else:
89105
base = "C:/python"
@@ -93,19 +109,29 @@ def get_python_binary(python: str, host: str) -> str:
93109
if host == "rhel8":
94110
return f"/opt/python/{python}/bin/python3"
95111

96-
if host == "macos":
112+
if host in ["macos", "macos-arm64"]:
97113
return f"/Library/Frameworks/Python.Framework/Versions/{python}/bin/python3"
98114

99115
raise ValueError(f"no match found for python {python} on {host}")
100116

101117

102-
def get_display_name(base: str, host: str, version: str, python: str) -> str:
118+
def get_display_name(base: str, host: str, **kwargs) -> str:
103119
"""Get the display name of a variant."""
104-
if version not in ["rapid", "latest"]:
105-
version = f"v{version}"
106-
if not python.startswith("pypy"):
107-
python = f"py{python}"
108-
return f"{base} {HOSTS[host].display_name} {version} {python}"
120+
display_name = f"{base} {HOSTS[host].display_name}"
121+
for key, value in kwargs.items():
122+
name = value
123+
if key == "version":
124+
if value not in ["rapid", "latest"]:
125+
name = f"v{value}"
126+
elif key == "python":
127+
if not value.startswith("pypy"):
128+
name = f"py{value}"
129+
elif key.lower() in DISPLAY_LOOKUP:
130+
name = DISPLAY_LOOKUP[key.lower()][value]
131+
else:
132+
raise ValueError(f"Missing display handling for {key}")
133+
display_name = f"{display_name} {name}"
134+
return display_name
109135

110136

111137
def zip_cycle(*iterables, empty_default=None):
@@ -115,6 +141,15 @@ def zip_cycle(*iterables, empty_default=None):
115141
yield tuple(next(i, empty_default) for i in cycles)
116142

117143

144+
def generate_yaml(tasks=None, variants=None):
145+
"""Generate the yaml for a given set of tasks and variants."""
146+
project = EvgProject(tasks=tasks, buildvariants=variants)
147+
out = ShrubService.generate_yaml(project)
148+
# Dedent by two spaces to match what we use in config.yml
149+
lines = [line[2:] for line in out.splitlines()]
150+
print("\n".join(lines)) # noqa: T201
151+
152+
118153
##############
119154
# Variants
120155
##############
@@ -159,9 +194,63 @@ def create_ocsp_variants() -> list[BuildVariant]:
159194
return variants
160195

161196

197+
def create_server_variants() -> list[BuildVariant]:
198+
variants = []
199+
200+
# Run the full matrix on linux with min and max CPython, and latest pypy.
201+
host = "rhel8"
202+
for python, (auth, ssl) in product([*MIN_MAX_PYTHON, PYPYS[-1]], AUTH_SSLS):
203+
display_name = f"Test {host}"
204+
expansions = dict(AUTH=auth, SSL=ssl, COVERAGE="coverage")
205+
display_name = get_display_name("Test", host, python=python, **expansions)
206+
variant = create_variant(
207+
[f".{t}" for t in TOPOLOGIES],
208+
display_name,
209+
python=python,
210+
host=host,
211+
tags=["coverage_tag"],
212+
expansions=expansions,
213+
)
214+
variants.append(variant)
215+
216+
# Test the rest of the pythons on linux.
217+
for python, (auth, ssl), topology in zip_cycle(
218+
CPYTHONS[1:-1] + PYPYS[:-1], AUTH_SSLS, TOPOLOGIES
219+
):
220+
display_name = f"Test {host}"
221+
expansions = dict(AUTH=auth, SSL=ssl)
222+
display_name = get_display_name("Test", host, python=python, **expansions)
223+
variant = create_variant(
224+
[f".{topology}"],
225+
display_name,
226+
python=python,
227+
host=host,
228+
expansions=expansions,
229+
)
230+
variants.append(variant)
231+
232+
# Test a subset on each of the other platforms.
233+
for host in ("macos", "macos-arm64", "win64", "win32"):
234+
for (python, (auth, ssl), topology), sync in product(
235+
zip_cycle(MIN_MAX_PYTHON, AUTH_SSLS, TOPOLOGIES), SYNCS
236+
):
237+
test_suite = "default" if sync == "sync" else "default_async"
238+
expansions = dict(AUTH=auth, SSL=ssl, TEST_SUITES=test_suite)
239+
display_name = get_display_name("Test", host, python=python, **expansions)
240+
variant = create_variant(
241+
[f".{topology}"],
242+
display_name,
243+
python=python,
244+
host=host,
245+
expansions=expansions,
246+
)
247+
variants.append(variant)
248+
249+
return variants
250+
251+
162252
##################
163253
# Generate Config
164254
##################
165255

166-
project = EvgProject(tasks=None, buildvariants=create_ocsp_variants())
167-
print(ShrubService.generate_yaml(project)) # noqa: T201
256+
generate_yaml(variants=create_server_variants())

doc/changelog.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ PyMongo 4.11 brings a number of changes including:
1414
- Dropped support for MongoDB 3.6.
1515
- Added support for free-threaded Python with the GIL disabled. For more information see:
1616
`Free-threaded CPython <https://docs.python.org/3.13/whatsnew/3.13.html#whatsnew313-free-threaded-cpython>`_.
17+
- :attr:`~pymongo.asynchronous.mongo_client.AsyncMongoClient.address` and
18+
:attr:`~pymongo.mongo_client.MongoClient.address` now correctly block when called on unconnected clients
19+
until either connection succeeds or a server selection timeout error is raised.
1720

1821
Issues Resolved
1922
...............

pymongo/asynchronous/mongo_client.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,13 +1453,6 @@ async def address(self) -> Optional[tuple[str, int]]:
14531453
'Cannot use "address" property when load balancing among'
14541454
' mongoses, use "nodes" instead.'
14551455
)
1456-
if topology_type not in (
1457-
TOPOLOGY_TYPE.ReplicaSetWithPrimary,
1458-
TOPOLOGY_TYPE.Single,
1459-
TOPOLOGY_TYPE.LoadBalanced,
1460-
TOPOLOGY_TYPE.Sharded,
1461-
):
1462-
return None
14631456
return await self._server_property("address")
14641457

14651458
@property

pymongo/synchronous/mongo_client.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1447,13 +1447,6 @@ def address(self) -> Optional[tuple[str, int]]:
14471447
'Cannot use "address" property when load balancing among'
14481448
' mongoses, use "nodes" instead.'
14491449
)
1450-
if topology_type not in (
1451-
TOPOLOGY_TYPE.ReplicaSetWithPrimary,
1452-
TOPOLOGY_TYPE.Single,
1453-
TOPOLOGY_TYPE.LoadBalanced,
1454-
TOPOLOGY_TYPE.Sharded,
1455-
):
1456-
return None
14571450
return self._server_property("address")
14581451

14591452
@property

test/asynchronous/test_client.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -838,8 +838,6 @@ async def test_init_disconnected(self):
838838
c = await self.async_rs_or_single_client(connect=False)
839839
self.assertIsInstance(c.topology_description, TopologyDescription)
840840
self.assertEqual(c.topology_description, c._topology._description)
841-
self.assertIsNone(await c.address) # PYTHON-2981
842-
await c.admin.command("ping") # connect
843841
if async_client_context.is_rs:
844842
# The primary's host and port are from the replica set config.
845843
self.assertIsNotNone(await c.address)
@@ -2019,6 +2017,22 @@ async def test_handshake_08_invalid_aws_ec2(self):
20192017
None,
20202018
)
20212019

2020+
async def test_handshake_09_container_with_provider(self):
2021+
await self._test_handshake(
2022+
{
2023+
ENV_VAR_K8S: "1",
2024+
"AWS_LAMBDA_RUNTIME_API": "1",
2025+
"AWS_REGION": "us-east-1",
2026+
"AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "256",
2027+
},
2028+
{
2029+
"container": {"orchestrator": "kubernetes"},
2030+
"name": "aws.lambda",
2031+
"region": "us-east-1",
2032+
"memory_mb": 256,
2033+
},
2034+
)
2035+
20222036
def test_dict_hints(self):
20232037
self.db.t.find(hint={"x": 1})
20242038

test/test_client.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -812,8 +812,6 @@ def test_init_disconnected(self):
812812
c = self.rs_or_single_client(connect=False)
813813
self.assertIsInstance(c.topology_description, TopologyDescription)
814814
self.assertEqual(c.topology_description, c._topology._description)
815-
self.assertIsNone(c.address) # PYTHON-2981
816-
c.admin.command("ping") # connect
817815
if client_context.is_rs:
818816
# The primary's host and port are from the replica set config.
819817
self.assertIsNotNone(c.address)
@@ -1977,6 +1975,22 @@ def test_handshake_08_invalid_aws_ec2(self):
19771975
None,
19781976
)
19791977

1978+
def test_handshake_09_container_with_provider(self):
1979+
self._test_handshake(
1980+
{
1981+
ENV_VAR_K8S: "1",
1982+
"AWS_LAMBDA_RUNTIME_API": "1",
1983+
"AWS_REGION": "us-east-1",
1984+
"AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "256",
1985+
},
1986+
{
1987+
"container": {"orchestrator": "kubernetes"},
1988+
"name": "aws.lambda",
1989+
"region": "us-east-1",
1990+
"memory_mb": 256,
1991+
},
1992+
)
1993+
19801994
def test_dict_hints(self):
19811995
self.db.t.find(hint={"x": 1})
19821996

test/test_replica_set_reconfig.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ def test_client(self):
5959

6060
with self.assertRaises(ServerSelectionTimeoutError):
6161
c.db.command("ping")
62-
self.assertEqual(c.address, None)
62+
with self.assertRaises(ServerSelectionTimeoutError):
63+
_ = c.address
6364

6465
# Client can still discover the primary node
6566
c.revive_host("a:1")

0 commit comments

Comments
 (0)