Skip to content

Commit da0d5ec

Browse files
committed
Merge branch 'master' of github.com:mongodb/mongo-python-driver
2 parents b52c46e + 412d000 commit da0d5ec

File tree

14 files changed

+199
-43
lines changed

14 files changed

+199
-43
lines changed

.evergreen/generated_configs/variants.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,7 @@ buildvariants:
620620
- macos-14
621621
batchtime: 10080
622622
expansions:
623-
TEST_NAME: pyopenssl
623+
SUB_TEST_NAME: pyopenssl
624624
PYTHON_BINARY: /Library/Frameworks/Python.Framework/Versions/3.9/bin/python3
625625
- name: pyopenssl-rhel8-python3.10
626626
tasks:
@@ -631,7 +631,7 @@ buildvariants:
631631
- rhel87-small
632632
batchtime: 10080
633633
expansions:
634-
TEST_NAME: pyopenssl
634+
SUB_TEST_NAME: pyopenssl
635635
PYTHON_BINARY: /opt/python/3.10/bin/python3
636636
- name: pyopenssl-rhel8-python3.11
637637
tasks:
@@ -642,7 +642,7 @@ buildvariants:
642642
- rhel87-small
643643
batchtime: 10080
644644
expansions:
645-
TEST_NAME: pyopenssl
645+
SUB_TEST_NAME: pyopenssl
646646
PYTHON_BINARY: /opt/python/3.11/bin/python3
647647
- name: pyopenssl-rhel8-python3.12
648648
tasks:
@@ -653,7 +653,7 @@ buildvariants:
653653
- rhel87-small
654654
batchtime: 10080
655655
expansions:
656-
TEST_NAME: pyopenssl
656+
SUB_TEST_NAME: pyopenssl
657657
PYTHON_BINARY: /opt/python/3.12/bin/python3
658658
- name: pyopenssl-win64-python3.13
659659
tasks:
@@ -664,7 +664,7 @@ buildvariants:
664664
- windows-64-vsMulti-small
665665
batchtime: 10080
666666
expansions:
667-
TEST_NAME: pyopenssl
667+
SUB_TEST_NAME: pyopenssl
668668
PYTHON_BINARY: C:/python/Python313/python.exe
669669
- name: pyopenssl-rhel8-pypy3.10
670670
tasks:
@@ -675,7 +675,7 @@ buildvariants:
675675
- rhel87-small
676676
batchtime: 10080
677677
expansions:
678-
TEST_NAME: pyopenssl
678+
SUB_TEST_NAME: pyopenssl
679679
PYTHON_BINARY: /opt/python/pypy3.10/bin/python3
680680

681681
# Search index tests

.evergreen/scripts/generate_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ def create_enterprise_auth_variants():
250250
def create_pyopenssl_variants():
251251
base_name = "PyOpenSSL"
252252
batchtime = BATCHTIME_WEEK
253-
expansions = dict(TEST_NAME="pyopenssl")
253+
expansions = dict(SUB_TEST_NAME="pyopenssl")
254254
variants = []
255255

256256
for python in ALL_PYTHONS:

.evergreen/scripts/run_tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def run() -> None:
181181
return
182182

183183
if os.environ.get("DEBUG_LOG"):
184-
TEST_ARGS.extend(f"-o log_cli_level={logging.DEBUG} -o log_cli=1".split())
184+
TEST_ARGS.extend(f"-o log_cli_level={logging.DEBUG}".split())
185185

186186
# Run local tests.
187187
ret = pytest.main(TEST_ARGS + sys.argv[1:])

.evergreen/scripts/setup-tests.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ if [ -f $SCRIPT_DIR/env.sh ]; then
2020
source $SCRIPT_DIR/env.sh
2121
fi
2222

23+
echo "Setting up tests with args \"$*\"..."
2324
uv run $SCRIPT_DIR/setup_tests.py "$@"
25+
echo "Setting up tests with args \"$*\"... done."

.evergreen/scripts/setup_tests.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -394,8 +394,8 @@ def handle_test_env() -> None:
394394
load_config_from_file(csfle_dir / "secrets-export.sh")
395395
run_command(f"bash {csfle_dir.as_posix()}/start-servers.sh")
396396

397-
if sub_test_name == "pyopenssl":
398-
UV_ARGS.append("--extra ocsp")
397+
if sub_test_name == "pyopenssl":
398+
UV_ARGS.append("--extra ocsp")
399399

400400
if is_set("TEST_CRYPT_SHARED") or opts.crypt_shared:
401401
config = read_env(f"{DRIVERS_TOOLS}/mo-expansion.sh")
@@ -468,9 +468,7 @@ def handle_test_env() -> None:
468468
UV_ARGS.append(f"--group {framework}")
469469

470470
else:
471-
# Use --capture=tee-sys so pytest prints test output inline:
472-
# https://docs.pytest.org/en/stable/how-to/capture-stdout-stderr.html
473-
TEST_ARGS = f"-v --capture=tee-sys --durations=5 {TEST_ARGS}"
471+
TEST_ARGS = f"-v --durations=5 {TEST_ARGS}"
474472
TEST_SUITE = TEST_SUITE_MAP.get(test_name)
475473
if TEST_SUITE:
476474
TEST_ARGS = f"-m {TEST_SUITE} {TEST_ARGS}"

.evergreen/scripts/utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ class Distro:
4343
"kms": "kms",
4444
"load_balancer": "load_balancer",
4545
"mockupdb": "mockupdb",
46-
"pyopenssl": "",
4746
"ocsp": "ocsp",
4847
"perf": "perf",
4948
"serverless": "",

CONTRIBUTING.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -352,11 +352,11 @@ If you are running one of the `no-responder` tests, omit the `run-server` step.
352352

353353
## Enable Debug Logs
354354

355-
- Use `-o log_cli_level="DEBUG" -o log_cli=1` with `just test` or `pytest`.
356-
- Add `log_cli_level = "DEBUG` and `log_cli = 1` to the `tool.pytest.ini_options` section in `pyproject.toml` for Evergreen patches or to enable debug logs by default on your machine.
357-
- You can also set `DEBUG_LOG=1` and run either `just setup-tests` or `just-test`.
355+
- Use `-o log_cli_level="DEBUG" -o log_cli=1` with `just test` or `pytest` to output all debug logs to the terminal. **Warning**: This will output a huge amount of logs.
356+
- Add `log_cli=1` and `log_cli_level="DEBUG"` to the `tool.pytest.ini_options` section in `pyproject.toml` to enable debug logs in this manner by default on your machine.
357+
- Set `DEBUG_LOG=1` and run `just setup-tests`, `just-test`, or `pytest` to enable debug logs only for failed tests.
358358
- Finally, you can use `just setup-tests --debug-log`.
359-
- For evergreen patch builds, you can use `evergreen patch --param DEBUG_LOG=1` to enable debug logs for the patch.
359+
- For evergreen patch builds, you can use `evergreen patch --param DEBUG_LOG=1` to enable debug logs for failed tests in the patch.
360360

361361
## Adding a new test suite
362362

doc/changelog.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ Version 4.12.1 is a bug fix release.
1010
- Fixed a bug that could raise ``UnboundLocalError`` when creating asynchronous connections over SSL.
1111
- Fixed a bug causing SRV hostname validation to fail when resolver and resolved hostnames are identical with three domain levels.
1212
- Fixed a bug that caused direct use of ``pymongo.uri_parser`` to raise an ``AttributeError``.
13+
- Fixed a bug where clients created with connect=False and a "mongodb+srv://" connection string
14+
could cause public ``pymongo.MongoClient`` and ``pymongo.AsyncMongoClient`` attributes (topology_description,
15+
nodes, address, primary, secondaries, arbiters) to incorrectly return a Database, leading to type
16+
errors such as: "NotImplementedError: Database objects do not implement truth value testing or bool()".
1317
- Removed Eventlet testing against Python versions newer than 3.9 since
1418
Eventlet is actively being sunset by its maintainers and has compatibility issues with PyMongo's dnspython dependency.
1519

16-
1720
Issues Resolved
1821
...............
1922

pymongo/asynchronous/mongo_client.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
)
110110
from pymongo.read_preferences import ReadPreference, _ServerMode
111111
from pymongo.results import ClientBulkWriteResult
112+
from pymongo.server_description import ServerDescription
112113
from pymongo.server_selectors import writable_server_selector
113114
from pymongo.server_type import SERVER_TYPE
114115
from pymongo.topology_description import TOPOLOGY_TYPE, TopologyDescription
@@ -779,7 +780,7 @@ def __init__(
779780
keyword_opts["document_class"] = doc_class
780781
self._resolve_srv_info: dict[str, Any] = {"keyword_opts": keyword_opts}
781782

782-
seeds = set()
783+
self._seeds = set()
783784
is_srv = False
784785
username = None
785786
password = None
@@ -804,18 +805,18 @@ def __init__(
804805
srv_max_hosts=srv_max_hosts,
805806
)
806807
is_srv = entity.startswith(SRV_SCHEME)
807-
seeds.update(res["nodelist"])
808+
self._seeds.update(res["nodelist"])
808809
username = res["username"] or username
809810
password = res["password"] or password
810811
dbase = res["database"] or dbase
811812
opts = res["options"]
812813
fqdn = res["fqdn"]
813814
else:
814-
seeds.update(split_hosts(entity, self._port))
815-
if not seeds:
815+
self._seeds.update(split_hosts(entity, self._port))
816+
if not self._seeds:
816817
raise ConfigurationError("need to specify at least one host")
817818

818-
for hostname in [node[0] for node in seeds]:
819+
for hostname in [node[0] for node in self._seeds]:
819820
if _detect_external_db(hostname):
820821
break
821822

@@ -838,7 +839,7 @@ def __init__(
838839
srv_service_name = opts.get("srvServiceName", common.SRV_SERVICE_NAME)
839840

840841
srv_max_hosts = srv_max_hosts or opts.get("srvmaxhosts")
841-
opts = self._normalize_and_validate_options(opts, seeds)
842+
opts = self._normalize_and_validate_options(opts, self._seeds)
842843

843844
# Username and password passed as kwargs override user info in URI.
844845
username = opts.get("username", username)
@@ -857,7 +858,7 @@ def __init__(
857858
"username": username,
858859
"password": password,
859860
"dbase": dbase,
860-
"seeds": seeds,
861+
"seeds": self._seeds,
861862
"fqdn": fqdn,
862863
"srv_service_name": srv_service_name,
863864
"pool_class": pool_class,
@@ -873,8 +874,7 @@ def __init__(
873874
self._options.read_concern,
874875
)
875876

876-
if not is_srv:
877-
self._init_based_on_options(seeds, srv_max_hosts, srv_service_name)
877+
self._init_based_on_options(self._seeds, srv_max_hosts, srv_service_name)
878878

879879
self._opened = False
880880
self._closed = False
@@ -975,6 +975,7 @@ def _init_based_on_options(
975975
srv_service_name=srv_service_name,
976976
srv_max_hosts=srv_max_hosts,
977977
server_monitoring_mode=self._options.server_monitoring_mode,
978+
topology_id=self._topology_settings._topology_id if self._topology_settings else None,
978979
)
979980
if self._options.auto_encryption_opts:
980981
from pymongo.asynchronous.encryption import _Encrypter
@@ -1205,6 +1206,16 @@ def topology_description(self) -> TopologyDescription:
12051206
12061207
.. versionadded:: 4.0
12071208
"""
1209+
if self._topology is None:
1210+
servers = {(host, port): ServerDescription((host, port)) for host, port in self._seeds}
1211+
return TopologyDescription(
1212+
TOPOLOGY_TYPE.Unknown,
1213+
servers,
1214+
None,
1215+
None,
1216+
None,
1217+
self._topology_settings,
1218+
)
12081219
return self._topology.description
12091220

12101221
@property
@@ -1218,6 +1229,8 @@ def nodes(self) -> FrozenSet[_Address]:
12181229
to any servers, or a network partition causes it to lose connection
12191230
to all servers.
12201231
"""
1232+
if self._topology is None:
1233+
return frozenset()
12211234
description = self._topology.description
12221235
return frozenset(s.address for s in description.known_servers)
12231236

@@ -1576,6 +1589,8 @@ async def address(self) -> Optional[tuple[str, int]]:
15761589
15771590
.. versionadded:: 3.0
15781591
"""
1592+
if self._topology is None:
1593+
await self._get_topology()
15791594
topology_type = self._topology._description.topology_type
15801595
if (
15811596
topology_type == TOPOLOGY_TYPE.Sharded
@@ -1598,6 +1613,8 @@ async def primary(self) -> Optional[tuple[str, int]]:
15981613
.. versionadded:: 3.0
15991614
AsyncMongoClient gained this property in version 3.0.
16001615
"""
1616+
if self._topology is None:
1617+
await self._get_topology()
16011618
return await self._topology.get_primary() # type: ignore[return-value]
16021619

16031620
@property
@@ -1611,6 +1628,8 @@ async def secondaries(self) -> set[_Address]:
16111628
.. versionadded:: 3.0
16121629
AsyncMongoClient gained this property in version 3.0.
16131630
"""
1631+
if self._topology is None:
1632+
await self._get_topology()
16141633
return await self._topology.get_secondaries()
16151634

16161635
@property
@@ -1621,6 +1640,8 @@ async def arbiters(self) -> set[_Address]:
16211640
connected to a replica set, there are no arbiters, or this client was
16221641
created without the `replicaSet` option.
16231642
"""
1643+
if self._topology is None:
1644+
await self._get_topology()
16241645
return await self._topology.get_arbiters()
16251646

16261647
@property

pymongo/asynchronous/settings.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def __init__(
5151
srv_service_name: str = common.SRV_SERVICE_NAME,
5252
srv_max_hosts: int = 0,
5353
server_monitoring_mode: str = common.SERVER_MONITORING_MODE,
54+
topology_id: Optional[ObjectId] = None,
5455
):
5556
"""Represent MongoClient's configuration.
5657
@@ -78,8 +79,10 @@ def __init__(
7879
self._srv_service_name = srv_service_name
7980
self._srv_max_hosts = srv_max_hosts or 0
8081
self._server_monitoring_mode = server_monitoring_mode
81-
82-
self._topology_id = ObjectId()
82+
if topology_id is not None:
83+
self._topology_id = topology_id
84+
else:
85+
self._topology_id = ObjectId()
8386
# Store the allocation traceback to catch unclosed clients in the
8487
# test suite.
8588
self._stack = "".join(traceback.format_stack()[:-2])

0 commit comments

Comments
 (0)