diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index bcce7818a..08c0dbbf3 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -620,7 +620,7 @@ buildvariants: - macos-14 batchtime: 10080 expansions: - TEST_NAME: pyopenssl + SUB_TEST_NAME: pyopenssl PYTHON_BINARY: /Library/Frameworks/Python.Framework/Versions/3.9/bin/python3 - name: pyopenssl-rhel8-python3.10 tasks: @@ -631,7 +631,7 @@ buildvariants: - rhel87-small batchtime: 10080 expansions: - TEST_NAME: pyopenssl + SUB_TEST_NAME: pyopenssl PYTHON_BINARY: /opt/python/3.10/bin/python3 - name: pyopenssl-rhel8-python3.11 tasks: @@ -642,7 +642,7 @@ buildvariants: - rhel87-small batchtime: 10080 expansions: - TEST_NAME: pyopenssl + SUB_TEST_NAME: pyopenssl PYTHON_BINARY: /opt/python/3.11/bin/python3 - name: pyopenssl-rhel8-python3.12 tasks: @@ -653,7 +653,7 @@ buildvariants: - rhel87-small batchtime: 10080 expansions: - TEST_NAME: pyopenssl + SUB_TEST_NAME: pyopenssl PYTHON_BINARY: /opt/python/3.12/bin/python3 - name: pyopenssl-win64-python3.13 tasks: @@ -664,7 +664,7 @@ buildvariants: - windows-64-vsMulti-small batchtime: 10080 expansions: - TEST_NAME: pyopenssl + SUB_TEST_NAME: pyopenssl PYTHON_BINARY: C:/python/Python313/python.exe - name: pyopenssl-rhel8-pypy3.10 tasks: @@ -675,7 +675,7 @@ buildvariants: - rhel87-small batchtime: 10080 expansions: - TEST_NAME: pyopenssl + SUB_TEST_NAME: pyopenssl PYTHON_BINARY: /opt/python/pypy3.10/bin/python3 # Search index tests diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 80eb248f3..7b88be85b 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -250,7 +250,7 @@ def create_enterprise_auth_variants(): def create_pyopenssl_variants(): base_name = "PyOpenSSL" batchtime = BATCHTIME_WEEK - expansions = dict(TEST_NAME="pyopenssl") + expansions = dict(SUB_TEST_NAME="pyopenssl") variants = [] for python in ALL_PYTHONS: diff --git a/.evergreen/scripts/run_tests.py b/.evergreen/scripts/run_tests.py index ae073c266..5c1ba25a9 100644 --- a/.evergreen/scripts/run_tests.py +++ b/.evergreen/scripts/run_tests.py @@ -181,7 +181,7 @@ def run() -> None: return if os.environ.get("DEBUG_LOG"): - TEST_ARGS.extend(f"-o log_cli_level={logging.DEBUG} -o log_cli=1".split()) + TEST_ARGS.extend(f"-o log_cli_level={logging.DEBUG}".split()) # Run local tests. ret = pytest.main(TEST_ARGS + sys.argv[1:]) diff --git a/.evergreen/scripts/setup-tests.sh b/.evergreen/scripts/setup-tests.sh index 0b75051a6..1074c7eaa 100755 --- a/.evergreen/scripts/setup-tests.sh +++ b/.evergreen/scripts/setup-tests.sh @@ -20,4 +20,6 @@ if [ -f $SCRIPT_DIR/env.sh ]; then source $SCRIPT_DIR/env.sh fi +echo "Setting up tests with args \"$*\"..." uv run $SCRIPT_DIR/setup_tests.py "$@" +echo "Setting up tests with args \"$*\"... done." diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index bd307a4e1..98c382ff6 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -394,8 +394,8 @@ def handle_test_env() -> None: load_config_from_file(csfle_dir / "secrets-export.sh") run_command(f"bash {csfle_dir.as_posix()}/start-servers.sh") - if sub_test_name == "pyopenssl": - UV_ARGS.append("--extra ocsp") + if sub_test_name == "pyopenssl": + UV_ARGS.append("--extra ocsp") if is_set("TEST_CRYPT_SHARED") or opts.crypt_shared: config = read_env(f"{DRIVERS_TOOLS}/mo-expansion.sh") @@ -468,9 +468,7 @@ def handle_test_env() -> None: UV_ARGS.append(f"--group {framework}") else: - # Use --capture=tee-sys so pytest prints test output inline: - # https://docs.pytest.org/en/stable/how-to/capture-stdout-stderr.html - TEST_ARGS = f"-v --capture=tee-sys --durations=5 {TEST_ARGS}" + TEST_ARGS = f"-v --durations=5 {TEST_ARGS}" TEST_SUITE = TEST_SUITE_MAP.get(test_name) if TEST_SUITE: TEST_ARGS = f"-m {TEST_SUITE} {TEST_ARGS}" diff --git a/.evergreen/scripts/utils.py b/.evergreen/scripts/utils.py index bd9353260..c9195b638 100644 --- a/.evergreen/scripts/utils.py +++ b/.evergreen/scripts/utils.py @@ -43,7 +43,6 @@ TEST_SUITE_MAP = { "kms": "kms", "load_balancer": "load_balancer", "mockupdb": "mockupdb", - "pyopenssl": "", "ocsp": "ocsp", "perf": "perf", "serverless": "", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 112511368..369e688b1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -352,11 +352,11 @@ If you are running one of the `no-responder` tests, omit the `run-server` step. ## Enable Debug Logs -- Use `-o log_cli_level="DEBUG" -o log_cli=1` with `just test` or `pytest`. -- 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. -- You can also set `DEBUG_LOG=1` and run either `just setup-tests` or `just-test`. +- 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. +- 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. +- Set `DEBUG_LOG=1` and run `just setup-tests`, `just-test`, or `pytest` to enable debug logs only for failed tests. - Finally, you can use `just setup-tests --debug-log`. -- For evergreen patch builds, you can use `evergreen patch --param DEBUG_LOG=1` to enable debug logs for the patch. +- For evergreen patch builds, you can use `evergreen patch --param DEBUG_LOG=1` to enable debug logs for failed tests in the patch. ## Adding a new test suite diff --git a/doc/changelog.rst b/doc/changelog.rst index 2fb225e2e..4fff06c9c 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -10,10 +10,13 @@ Version 4.12.1 is a bug fix release. - Fixed a bug that could raise ``UnboundLocalError`` when creating asynchronous connections over SSL. - Fixed a bug causing SRV hostname validation to fail when resolver and resolved hostnames are identical with three domain levels. - Fixed a bug that caused direct use of ``pymongo.uri_parser`` to raise an ``AttributeError``. +- Fixed a bug where clients created with connect=False and a "mongodb+srv://" connection string + could cause public ``pymongo.MongoClient`` and ``pymongo.AsyncMongoClient`` attributes (topology_description, + nodes, address, primary, secondaries, arbiters) to incorrectly return a Database, leading to type + errors such as: "NotImplementedError: Database objects do not implement truth value testing or bool()". - Removed Eventlet testing against Python versions newer than 3.9 since Eventlet is actively being sunset by its maintainers and has compatibility issues with PyMongo's dnspython dependency. - Issues Resolved ............... diff --git a/pymongo/asynchronous/mongo_client.py b/pymongo/asynchronous/mongo_client.py index 7744a75d9..a236b2134 100644 --- a/pymongo/asynchronous/mongo_client.py +++ b/pymongo/asynchronous/mongo_client.py @@ -109,6 +109,7 @@ from pymongo.operations import ( ) from pymongo.read_preferences import ReadPreference, _ServerMode from pymongo.results import ClientBulkWriteResult +from pymongo.server_description import ServerDescription from pymongo.server_selectors import writable_server_selector from pymongo.server_type import SERVER_TYPE from pymongo.topology_description import TOPOLOGY_TYPE, TopologyDescription @@ -779,7 +780,7 @@ class AsyncMongoClient(common.BaseObject, Generic[_DocumentType]): keyword_opts["document_class"] = doc_class self._resolve_srv_info: dict[str, Any] = {"keyword_opts": keyword_opts} - seeds = set() + self._seeds = set() is_srv = False username = None password = None @@ -804,18 +805,18 @@ class AsyncMongoClient(common.BaseObject, Generic[_DocumentType]): srv_max_hosts=srv_max_hosts, ) is_srv = entity.startswith(SRV_SCHEME) - seeds.update(res["nodelist"]) + self._seeds.update(res["nodelist"]) username = res["username"] or username password = res["password"] or password dbase = res["database"] or dbase opts = res["options"] fqdn = res["fqdn"] else: - seeds.update(split_hosts(entity, self._port)) - if not seeds: + self._seeds.update(split_hosts(entity, self._port)) + if not self._seeds: raise ConfigurationError("need to specify at least one host") - for hostname in [node[0] for node in seeds]: + for hostname in [node[0] for node in self._seeds]: if _detect_external_db(hostname): break @@ -838,7 +839,7 @@ class AsyncMongoClient(common.BaseObject, Generic[_DocumentType]): srv_service_name = opts.get("srvServiceName", common.SRV_SERVICE_NAME) srv_max_hosts = srv_max_hosts or opts.get("srvmaxhosts") - opts = self._normalize_and_validate_options(opts, seeds) + opts = self._normalize_and_validate_options(opts, self._seeds) # Username and password passed as kwargs override user info in URI. username = opts.get("username", username) @@ -857,7 +858,7 @@ class AsyncMongoClient(common.BaseObject, Generic[_DocumentType]): "username": username, "password": password, "dbase": dbase, - "seeds": seeds, + "seeds": self._seeds, "fqdn": fqdn, "srv_service_name": srv_service_name, "pool_class": pool_class, @@ -873,8 +874,7 @@ class AsyncMongoClient(common.BaseObject, Generic[_DocumentType]): self._options.read_concern, ) - if not is_srv: - self._init_based_on_options(seeds, srv_max_hosts, srv_service_name) + self._init_based_on_options(self._seeds, srv_max_hosts, srv_service_name) self._opened = False self._closed = False @@ -975,6 +975,7 @@ class AsyncMongoClient(common.BaseObject, Generic[_DocumentType]): srv_service_name=srv_service_name, srv_max_hosts=srv_max_hosts, server_monitoring_mode=self._options.server_monitoring_mode, + topology_id=self._topology_settings._topology_id if self._topology_settings else None, ) if self._options.auto_encryption_opts: from pymongo.asynchronous.encryption import _Encrypter @@ -1205,6 +1206,16 @@ class AsyncMongoClient(common.BaseObject, Generic[_DocumentType]): .. versionadded:: 4.0 """ + if self._topology is None: + servers = {(host, port): ServerDescription((host, port)) for host, port in self._seeds} + return TopologyDescription( + TOPOLOGY_TYPE.Unknown, + servers, + None, + None, + None, + self._topology_settings, + ) return self._topology.description @property @@ -1218,6 +1229,8 @@ class AsyncMongoClient(common.BaseObject, Generic[_DocumentType]): to any servers, or a network partition causes it to lose connection to all servers. """ + if self._topology is None: + return frozenset() description = self._topology.description return frozenset(s.address for s in description.known_servers) @@ -1576,6 +1589,8 @@ class AsyncMongoClient(common.BaseObject, Generic[_DocumentType]): .. versionadded:: 3.0 """ + if self._topology is None: + await self._get_topology() topology_type = self._topology._description.topology_type if ( topology_type == TOPOLOGY_TYPE.Sharded @@ -1598,6 +1613,8 @@ class AsyncMongoClient(common.BaseObject, Generic[_DocumentType]): .. versionadded:: 3.0 AsyncMongoClient gained this property in version 3.0. """ + if self._topology is None: + await self._get_topology() return await self._topology.get_primary() # type: ignore[return-value] @property @@ -1611,6 +1628,8 @@ class AsyncMongoClient(common.BaseObject, Generic[_DocumentType]): .. versionadded:: 3.0 AsyncMongoClient gained this property in version 3.0. """ + if self._topology is None: + await self._get_topology() return await self._topology.get_secondaries() @property @@ -1621,6 +1640,8 @@ class AsyncMongoClient(common.BaseObject, Generic[_DocumentType]): connected to a replica set, there are no arbiters, or this client was created without the `replicaSet` option. """ + if self._topology is None: + await self._get_topology() return await self._topology.get_arbiters() @property diff --git a/pymongo/asynchronous/settings.py b/pymongo/asynchronous/settings.py index 62be853fb..9c2331971 100644 --- a/pymongo/asynchronous/settings.py +++ b/pymongo/asynchronous/settings.py @@ -51,6 +51,7 @@ class TopologySettings: srv_service_name: str = common.SRV_SERVICE_NAME, srv_max_hosts: int = 0, server_monitoring_mode: str = common.SERVER_MONITORING_MODE, + topology_id: Optional[ObjectId] = None, ): """Represent MongoClient's configuration. @@ -78,8 +79,10 @@ class TopologySettings: self._srv_service_name = srv_service_name self._srv_max_hosts = srv_max_hosts or 0 self._server_monitoring_mode = server_monitoring_mode - - self._topology_id = ObjectId() + if topology_id is not None: + self._topology_id = topology_id + else: + self._topology_id = ObjectId() # Store the allocation traceback to catch unclosed clients in the # test suite. self._stack = "".join(traceback.format_stack()[:-2]) diff --git a/pymongo/synchronous/mongo_client.py b/pymongo/synchronous/mongo_client.py index 1c0adb5d6..99a517e5c 100644 --- a/pymongo/synchronous/mongo_client.py +++ b/pymongo/synchronous/mongo_client.py @@ -101,6 +101,7 @@ from pymongo.operations import ( ) from pymongo.read_preferences import ReadPreference, _ServerMode from pymongo.results import ClientBulkWriteResult +from pymongo.server_description import ServerDescription from pymongo.server_selectors import writable_server_selector from pymongo.server_type import SERVER_TYPE from pymongo.synchronous import client_session, database, uri_parser @@ -777,7 +778,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]): keyword_opts["document_class"] = doc_class self._resolve_srv_info: dict[str, Any] = {"keyword_opts": keyword_opts} - seeds = set() + self._seeds = set() is_srv = False username = None password = None @@ -802,18 +803,18 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]): srv_max_hosts=srv_max_hosts, ) is_srv = entity.startswith(SRV_SCHEME) - seeds.update(res["nodelist"]) + self._seeds.update(res["nodelist"]) username = res["username"] or username password = res["password"] or password dbase = res["database"] or dbase opts = res["options"] fqdn = res["fqdn"] else: - seeds.update(split_hosts(entity, self._port)) - if not seeds: + self._seeds.update(split_hosts(entity, self._port)) + if not self._seeds: raise ConfigurationError("need to specify at least one host") - for hostname in [node[0] for node in seeds]: + for hostname in [node[0] for node in self._seeds]: if _detect_external_db(hostname): break @@ -836,7 +837,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]): srv_service_name = opts.get("srvServiceName", common.SRV_SERVICE_NAME) srv_max_hosts = srv_max_hosts or opts.get("srvmaxhosts") - opts = self._normalize_and_validate_options(opts, seeds) + opts = self._normalize_and_validate_options(opts, self._seeds) # Username and password passed as kwargs override user info in URI. username = opts.get("username", username) @@ -855,7 +856,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]): "username": username, "password": password, "dbase": dbase, - "seeds": seeds, + "seeds": self._seeds, "fqdn": fqdn, "srv_service_name": srv_service_name, "pool_class": pool_class, @@ -871,8 +872,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]): self._options.read_concern, ) - if not is_srv: - self._init_based_on_options(seeds, srv_max_hosts, srv_service_name) + self._init_based_on_options(self._seeds, srv_max_hosts, srv_service_name) self._opened = False self._closed = False @@ -973,6 +973,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]): srv_service_name=srv_service_name, srv_max_hosts=srv_max_hosts, server_monitoring_mode=self._options.server_monitoring_mode, + topology_id=self._topology_settings._topology_id if self._topology_settings else None, ) if self._options.auto_encryption_opts: from pymongo.synchronous.encryption import _Encrypter @@ -1203,6 +1204,16 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]): .. versionadded:: 4.0 """ + if self._topology is None: + servers = {(host, port): ServerDescription((host, port)) for host, port in self._seeds} + return TopologyDescription( + TOPOLOGY_TYPE.Unknown, + servers, + None, + None, + None, + self._topology_settings, + ) return self._topology.description @property @@ -1216,6 +1227,8 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]): to any servers, or a network partition causes it to lose connection to all servers. """ + if self._topology is None: + return frozenset() description = self._topology.description return frozenset(s.address for s in description.known_servers) @@ -1570,6 +1583,8 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]): .. versionadded:: 3.0 """ + if self._topology is None: + self._get_topology() topology_type = self._topology._description.topology_type if ( topology_type == TOPOLOGY_TYPE.Sharded @@ -1592,6 +1607,8 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]): .. versionadded:: 3.0 MongoClient gained this property in version 3.0. """ + if self._topology is None: + self._get_topology() return self._topology.get_primary() # type: ignore[return-value] @property @@ -1605,6 +1622,8 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]): .. versionadded:: 3.0 MongoClient gained this property in version 3.0. """ + if self._topology is None: + self._get_topology() return self._topology.get_secondaries() @property @@ -1615,6 +1634,8 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]): connected to a replica set, there are no arbiters, or this client was created without the `replicaSet` option. """ + if self._topology is None: + self._get_topology() return self._topology.get_arbiters() @property diff --git a/pymongo/synchronous/settings.py b/pymongo/synchronous/settings.py index bb17de187..61b86fa18 100644 --- a/pymongo/synchronous/settings.py +++ b/pymongo/synchronous/settings.py @@ -51,6 +51,7 @@ class TopologySettings: srv_service_name: str = common.SRV_SERVICE_NAME, srv_max_hosts: int = 0, server_monitoring_mode: str = common.SERVER_MONITORING_MODE, + topology_id: Optional[ObjectId] = None, ): """Represent MongoClient's configuration. @@ -78,8 +79,10 @@ class TopologySettings: self._srv_service_name = srv_service_name self._srv_max_hosts = srv_max_hosts or 0 self._server_monitoring_mode = server_monitoring_mode - - self._topology_id = ObjectId() + if topology_id is not None: + self._topology_id = topology_id + else: + self._topology_id = ObjectId() # Store the allocation traceback to catch unclosed clients in the # test suite. self._stack = "".join(traceback.format_stack()[:-2]) diff --git a/test/asynchronous/test_client.py b/test/asynchronous/test_client.py index c9cfca81f..b9deb985b 100644 --- a/test/asynchronous/test_client.py +++ b/test/asynchronous/test_client.py @@ -34,7 +34,7 @@ import threading import time import uuid from typing import Any, Iterable, Type, no_type_check -from unittest import mock +from unittest import mock, skipIf from unittest.mock import patch import pytest @@ -629,6 +629,7 @@ class AsyncClientUnitTest(AsyncUnitTest): logs = [record.getMessage() for record in cm.records if record.name == "pymongo.client"] self.assertEqual(len(logs), 7) + @skipIf(os.environ.get("DEBUG_LOG"), "Enabling debug logs breaks this test") @patch("pymongo.asynchronous.srv_resolver._SrvResolver.get_hosts") async def test_detected_environment_warning(self, mock_get_hosts): with self._caplog.at_level(logging.WARN): @@ -849,6 +850,58 @@ class TestClient(AsyncIntegrationTest): with self.assertRaises(ConnectionFailure): await c.pymongo_test.test.find_one() + @async_client_context.require_no_standalone + @async_client_context.require_no_load_balancer + @async_client_context.require_tls + async def test_init_disconnected_with_srv(self): + c = await self.async_rs_or_single_client( + "mongodb+srv://test1.test.build.10gen.cc", connect=False, tlsInsecure=True + ) + # nodes returns an empty set if not connected + self.assertEqual(c.nodes, frozenset()) + # topology_description returns the initial seed description if not connected + topology_description = c.topology_description + self.assertEqual(topology_description.topology_type, TOPOLOGY_TYPE.Unknown) + self.assertEqual( + { + ("test1.test.build.10gen.cc", None): ServerDescription( + ("test1.test.build.10gen.cc", None) + ) + }, + topology_description.server_descriptions(), + ) + + # address causes client to block until connected + self.assertIsNotNone(await c.address) + # Initial seed topology and connected topology have the same ID + self.assertEqual( + c._topology._topology_id, topology_description._topology_settings._topology_id + ) + await c.close() + + c = await self.async_rs_or_single_client( + "mongodb+srv://test1.test.build.10gen.cc", connect=False, tlsInsecure=True + ) + # primary causes client to block until connected + await c.primary + self.assertIsNotNone(c._topology) + await c.close() + + c = await self.async_rs_or_single_client( + "mongodb+srv://test1.test.build.10gen.cc", connect=False, tlsInsecure=True + ) + # secondaries causes client to block until connected + await c.secondaries + self.assertIsNotNone(c._topology) + await c.close() + + c = await self.async_rs_or_single_client( + "mongodb+srv://test1.test.build.10gen.cc", connect=False, tlsInsecure=True + ) + # arbiters causes client to block until connected + await c.arbiters + self.assertIsNotNone(c._topology) + async def test_equality(self): seed = "{}:{}".format(*list(self.client._topology_settings.seeds)[0]) c = await self.async_rs_or_single_client(seed, connect=False) diff --git a/test/test_client.py b/test/test_client.py index 038ba2241..c2df8ab2b 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -34,7 +34,7 @@ import threading import time import uuid from typing import Any, Iterable, Type, no_type_check -from unittest import mock +from unittest import mock, skipIf from unittest.mock import patch import pytest @@ -622,6 +622,7 @@ class ClientUnitTest(UnitTest): logs = [record.getMessage() for record in cm.records if record.name == "pymongo.client"] self.assertEqual(len(logs), 7) + @skipIf(os.environ.get("DEBUG_LOG"), "Enabling debug logs breaks this test") @patch("pymongo.synchronous.srv_resolver._SrvResolver.get_hosts") def test_detected_environment_warning(self, mock_get_hosts): with self._caplog.at_level(logging.WARN): @@ -824,6 +825,58 @@ class TestClient(IntegrationTest): with self.assertRaises(ConnectionFailure): c.pymongo_test.test.find_one() + @client_context.require_no_standalone + @client_context.require_no_load_balancer + @client_context.require_tls + def test_init_disconnected_with_srv(self): + c = self.rs_or_single_client( + "mongodb+srv://test1.test.build.10gen.cc", connect=False, tlsInsecure=True + ) + # nodes returns an empty set if not connected + self.assertEqual(c.nodes, frozenset()) + # topology_description returns the initial seed description if not connected + topology_description = c.topology_description + self.assertEqual(topology_description.topology_type, TOPOLOGY_TYPE.Unknown) + self.assertEqual( + { + ("test1.test.build.10gen.cc", None): ServerDescription( + ("test1.test.build.10gen.cc", None) + ) + }, + topology_description.server_descriptions(), + ) + + # address causes client to block until connected + self.assertIsNotNone(c.address) + # Initial seed topology and connected topology have the same ID + self.assertEqual( + c._topology._topology_id, topology_description._topology_settings._topology_id + ) + c.close() + + c = self.rs_or_single_client( + "mongodb+srv://test1.test.build.10gen.cc", connect=False, tlsInsecure=True + ) + # primary causes client to block until connected + c.primary + self.assertIsNotNone(c._topology) + c.close() + + c = self.rs_or_single_client( + "mongodb+srv://test1.test.build.10gen.cc", connect=False, tlsInsecure=True + ) + # secondaries causes client to block until connected + c.secondaries + self.assertIsNotNone(c._topology) + c.close() + + c = self.rs_or_single_client( + "mongodb+srv://test1.test.build.10gen.cc", connect=False, tlsInsecure=True + ) + # arbiters causes client to block until connected + c.arbiters + self.assertIsNotNone(c._topology) + def test_equality(self): seed = "{}:{}".format(*list(self.client._topology_settings.seeds)[0]) c = self.rs_or_single_client(seed, connect=False)