From 7c4e356a1d3835c43d99e1d22c0b22e3fd25a785 Mon Sep 17 00:00:00 2001 From: Iris Date: Wed, 30 Sep 2026 14:03:55 -0700 Subject: [PATCH 1/4] callback must return bool --- pymongo/asynchronous/srv_resolver.py | 5 +++++ pymongo/synchronous/srv_resolver.py | 5 +++++ test/asynchronous/test_dns.py | 6 ++++++ test/test_dns.py | 6 ++++++ 4 files changed, 22 insertions(+) diff --git a/pymongo/asynchronous/srv_resolver.py b/pymongo/asynchronous/srv_resolver.py index 554d226acf..262e9107ce 100644 --- a/pymongo/asynchronous/srv_resolver.py +++ b/pymongo/asynchronous/srv_resolver.py @@ -148,6 +148,11 @@ def _validate_host(self, srv_host: str) -> None: raise ConfigurationError( f"srv_host_validator raised an exception for SRV host {srv_host}: {exc}" ) from exc + if not isinstance(allowed, bool): + raise ConfigurationError( + f"srv_host_validator must return a bool for SRV host {srv_host}, " + f"not {type(allowed).__name__}" + ) if not allowed: raise ConfigurationError( f"Invalid SRV host: {srv_host} was rejected by srv_host_validator" diff --git a/pymongo/synchronous/srv_resolver.py b/pymongo/synchronous/srv_resolver.py index 18083c8a0c..25fc6f7ad6 100644 --- a/pymongo/synchronous/srv_resolver.py +++ b/pymongo/synchronous/srv_resolver.py @@ -148,6 +148,11 @@ def _validate_host(self, srv_host: str) -> None: raise ConfigurationError( f"srv_host_validator raised an exception for SRV host {srv_host}: {exc}" ) from exc + if not isinstance(allowed, bool): + raise ConfigurationError( + f"srv_host_validator must return a bool for SRV host {srv_host}, " + f"not {type(allowed).__name__}" + ) if not allowed: raise ConfigurationError( f"Invalid SRV host: {srv_host} was rejected by srv_host_validator" diff --git a/test/asynchronous/test_dns.py b/test/asynchronous/test_dns.py index befa119b46..9898057725 100644 --- a/test/asynchronous/test_dns.py +++ b/test/asynchronous/test_dns.py @@ -402,6 +402,12 @@ async def test_13_throw_when_srv_host_validator_is_used_with_a_non_srv_uri(self) connect=False, ) + async def test_14_throw_when_srv_host_validator_returns_a_non_boolean_value(self): + with self.assertRaisesRegex(ConfigurationError, "srv_host_validator must return a bool"): + await self._parse( + "blogs.mongodb.com", "cluster.mongodb.com", srv_host_validator=lambda host: "true" + ) + async def test_srv_hostname_with_three_or_more_parts_may_equal_the_returned_hostname( self, ): diff --git a/test/test_dns.py b/test/test_dns.py index 89dee6f3a7..fee520d052 100644 --- a/test/test_dns.py +++ b/test/test_dns.py @@ -398,6 +398,12 @@ def test_13_throw_when_srv_host_validator_is_used_with_a_non_srv_uri(self): connect=False, ) + def test_14_throw_when_srv_host_validator_returns_a_non_boolean_value(self): + with self.assertRaisesRegex(ConfigurationError, "srv_host_validator must return a bool"): + self._parse( + "blogs.mongodb.com", "cluster.mongodb.com", srv_host_validator=lambda host: "true" + ) + def test_srv_hostname_with_three_or_more_parts_may_equal_the_returned_hostname( self, ): From 8600eade63b0512965f3c2683fe6c5d95e8e3de7 Mon Sep 17 00:00:00 2001 From: Iris Date: Thu, 1 Oct 2026 09:45:56 -0700 Subject: [PATCH 2/4] add _ test --- test/asynchronous/test_dns.py | 8 ++++++++ test/test_dns.py | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/test/asynchronous/test_dns.py b/test/asynchronous/test_dns.py index 9898057725..26398ed866 100644 --- a/test/asynchronous/test_dns.py +++ b/test/asynchronous/test_dns.py @@ -408,6 +408,14 @@ async def test_14_throw_when_srv_host_validator_returns_a_non_boolean_value(self "blogs.mongodb.com", "cluster.mongodb.com", srv_host_validator=lambda host: "true" ) + async def test_15_accept_an_underscore_in_srv_allowed_hosts_suffix(self): + res = await self._parse( + "blogs.my_domain.net", + "cluster.my_domain.net", + srv_allowed_hosts_suffix=".my_domain.net", + ) + self.assertEqual(["cluster.my_domain.net"], [node[0] for node in res["nodelist"]]) + async def test_srv_hostname_with_three_or_more_parts_may_equal_the_returned_hostname( self, ): diff --git a/test/test_dns.py b/test/test_dns.py index fee520d052..9d34455b45 100644 --- a/test/test_dns.py +++ b/test/test_dns.py @@ -404,6 +404,14 @@ def test_14_throw_when_srv_host_validator_returns_a_non_boolean_value(self): "blogs.mongodb.com", "cluster.mongodb.com", srv_host_validator=lambda host: "true" ) + def test_15_accept_an_underscore_in_srv_allowed_hosts_suffix(self): + res = self._parse( + "blogs.my_domain.net", + "cluster.my_domain.net", + srv_allowed_hosts_suffix=".my_domain.net", + ) + self.assertEqual(["cluster.my_domain.net"], [node[0] for node in res["nodelist"]]) + def test_srv_hostname_with_three_or_more_parts_may_equal_the_returned_hostname( self, ): From 08404f470afc08e2353326f883bb45f9635a9ba8 Mon Sep 17 00:00:00 2001 From: Iris Date: Tue, 6 Oct 2026 14:34:57 -0700 Subject: [PATCH 3/4] update docstring + changelog --- doc/changelog.rst | 4 +++- pymongo/asynchronous/mongo_client.py | 4 +++- pymongo/synchronous/mongo_client.py | 4 +++- test/asynchronous/test_dns.py | 4 +++- test/test_dns.py | 4 +++- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 9ea2f05c2e..6494e2bfe8 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -29,7 +29,9 @@ PyMongo 4.19 brings a number of changes including: :class:`~pymongo.asynchronous.mongo_client.AsyncMongoClient`, an alternative to ``srvAllowedHostsSuffix`` for deployments whose acceptable SRV hosts cannot be expressed as a single suffix. The callback is invoked once per SRV-returned - host and returns ``True`` to accept it. It is mutually exclusive with + host and must return a boolean: ``True`` to accept the host and ``False`` to + reject it. During initial DNS discovery a non-boolean return raises an error; + during SRV polling it is treated as a rejection. It is mutually exclusive with ``srvAllowedHostsSuffix`` and, because it takes a callable, cannot be set in a connection string. See the :class:`~pymongo.synchronous.mongo_client.MongoClient` and diff --git a/pymongo/asynchronous/mongo_client.py b/pymongo/asynchronous/mongo_client.py index 65aecce446..df390a218e 100644 --- a/pymongo/asynchronous/mongo_client.py +++ b/pymongo/asynchronous/mongo_client.py @@ -476,7 +476,9 @@ def __init__( argument, and must return ``True`` to accept the host or ``False`` to reject it. During initial DNS discovery, rejecting a host raises :exc:`~pymongo.errors.ConfigurationError`, as does an exception raised by - the callback itself. Use this when the set of acceptable hosts cannot be + the callback itself or a return value that is not a boolean. During SRV + polling, a rejection or a non-boolean return is treated as ``False`` and no + error is raised. Use this when the set of acceptable hosts cannot be expressed as a single suffix:: def validator(host: str) -> bool: diff --git a/pymongo/synchronous/mongo_client.py b/pymongo/synchronous/mongo_client.py index df09b03b20..6aaaaed20a 100644 --- a/pymongo/synchronous/mongo_client.py +++ b/pymongo/synchronous/mongo_client.py @@ -477,7 +477,9 @@ def __init__( argument, and must return ``True`` to accept the host or ``False`` to reject it. During initial DNS discovery, rejecting a host raises :exc:`~pymongo.errors.ConfigurationError`, as does an exception raised by - the callback itself. Use this when the set of acceptable hosts cannot be + the callback itself or a return value that is not a boolean. During SRV + polling, a rejection or a non-boolean return is treated as ``False`` and no + error is raised. Use this when the set of acceptable hosts cannot be expressed as a single suffix:: def validator(host: str) -> bool: diff --git a/test/asynchronous/test_dns.py b/test/asynchronous/test_dns.py index 26398ed866..2a18091f5e 100644 --- a/test/asynchronous/test_dns.py +++ b/test/asynchronous/test_dns.py @@ -403,10 +403,12 @@ async def test_13_throw_when_srv_host_validator_is_used_with_a_non_srv_uri(self) ) async def test_14_throw_when_srv_host_validator_returns_a_non_boolean_value(self): - with self.assertRaisesRegex(ConfigurationError, "srv_host_validator must return a bool"): + with self.assertRaisesRegex(ConfigurationError, "must return a bool") as ctx: await self._parse( "blogs.mongodb.com", "cluster.mongodb.com", srv_host_validator=lambda host: "true" ) + # A non-bool return is treated as though the validator raised an error. + self.assertIsInstance(ctx.exception.__cause__, TypeError) async def test_15_accept_an_underscore_in_srv_allowed_hosts_suffix(self): res = await self._parse( diff --git a/test/test_dns.py b/test/test_dns.py index 9d34455b45..a4dcef6253 100644 --- a/test/test_dns.py +++ b/test/test_dns.py @@ -399,10 +399,12 @@ def test_13_throw_when_srv_host_validator_is_used_with_a_non_srv_uri(self): ) def test_14_throw_when_srv_host_validator_returns_a_non_boolean_value(self): - with self.assertRaisesRegex(ConfigurationError, "srv_host_validator must return a bool"): + with self.assertRaisesRegex(ConfigurationError, "must return a bool") as ctx: self._parse( "blogs.mongodb.com", "cluster.mongodb.com", srv_host_validator=lambda host: "true" ) + # A non-bool return is treated as though the validator raised an error. + self.assertIsInstance(ctx.exception.__cause__, TypeError) def test_15_accept_an_underscore_in_srv_allowed_hosts_suffix(self): res = self._parse( From 270c69adcc5106f5836f64f63983fe321463e421 Mon Sep 17 00:00:00 2001 From: Iris Date: Wed, 7 Oct 2026 09:36:59 -0700 Subject: [PATCH 4/4] i changed my mind about how the error should be raised and forgot to fix the tests too -- oops --- test/asynchronous/test_dns.py | 4 +--- test/test_dns.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/test/asynchronous/test_dns.py b/test/asynchronous/test_dns.py index 2a18091f5e..26398ed866 100644 --- a/test/asynchronous/test_dns.py +++ b/test/asynchronous/test_dns.py @@ -403,12 +403,10 @@ async def test_13_throw_when_srv_host_validator_is_used_with_a_non_srv_uri(self) ) async def test_14_throw_when_srv_host_validator_returns_a_non_boolean_value(self): - with self.assertRaisesRegex(ConfigurationError, "must return a bool") as ctx: + with self.assertRaisesRegex(ConfigurationError, "srv_host_validator must return a bool"): await self._parse( "blogs.mongodb.com", "cluster.mongodb.com", srv_host_validator=lambda host: "true" ) - # A non-bool return is treated as though the validator raised an error. - self.assertIsInstance(ctx.exception.__cause__, TypeError) async def test_15_accept_an_underscore_in_srv_allowed_hosts_suffix(self): res = await self._parse( diff --git a/test/test_dns.py b/test/test_dns.py index a4dcef6253..9d34455b45 100644 --- a/test/test_dns.py +++ b/test/test_dns.py @@ -399,12 +399,10 @@ def test_13_throw_when_srv_host_validator_is_used_with_a_non_srv_uri(self): ) def test_14_throw_when_srv_host_validator_returns_a_non_boolean_value(self): - with self.assertRaisesRegex(ConfigurationError, "must return a bool") as ctx: + with self.assertRaisesRegex(ConfigurationError, "srv_host_validator must return a bool"): self._parse( "blogs.mongodb.com", "cluster.mongodb.com", srv_host_validator=lambda host: "true" ) - # A non-bool return is treated as though the validator raised an error. - self.assertIsInstance(ctx.exception.__cause__, TypeError) def test_15_accept_an_underscore_in_srv_allowed_hosts_suffix(self): res = self._parse(