From d5aa3636d5b4a4461264b4e6afe72921a6f9f16c Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Mon, 24 Mar 2025 10:40:40 -0400 Subject: [PATCH 1/4] PYTHON-5169 - Deprecate Hedged Reads option --- pymongo/read_preferences.py | 17 +++++++++++++++++ pyproject.toml | 1 + 2 files changed, 18 insertions(+) diff --git a/pymongo/read_preferences.py b/pymongo/read_preferences.py index 581f7ca66f..e6940dd878 100644 --- a/pymongo/read_preferences.py +++ b/pymongo/read_preferences.py @@ -19,6 +19,7 @@ from __future__ import annotations +import warnings from collections import abc from typing import TYPE_CHECKING, Any, Mapping, Optional, Sequence @@ -103,6 +104,11 @@ def _validate_hedge(hedge: Optional[_Hedge]) -> Optional[_Hedge]: if not isinstance(hedge, dict): raise TypeError(f"hedge must be a dictionary, not {hedge!r}") + warnings.warn( + "Hedged reads are deprecated starting in server version 8.0.", + DeprecationWarning, + stacklevel=2, + ) return hedge @@ -143,6 +149,11 @@ def document(self) -> dict[str, Any]: if self.__max_staleness != -1: doc["maxStalenessSeconds"] = self.__max_staleness if self.__hedge not in (None, {}): + warnings.warn( + "Hedged reads are deprecated starting in server version 8.0.", + DeprecationWarning, + stacklevel=2, + ) doc["hedge"] = self.__hedge return doc @@ -203,6 +214,12 @@ def hedge(self) -> Optional[_Hedge]: .. versionadded:: 3.11 """ + if self.__hedge is not None: + warnings.warn( + "Hedged reads are deprecated starting in server version 8.0.", + DeprecationWarning, + stacklevel=2, + ) return self.__hedge @property diff --git a/pyproject.toml b/pyproject.toml index f8c25ed602..719f93b9b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,6 +119,7 @@ filterwarnings = [ "module:please use dns.resolver.Resolver.resolve:DeprecationWarning", # https://github.com/dateutil/dateutil/issues/1314 "module:datetime.datetime.utc:DeprecationWarning", + "module:Hedged reads are deprecated:DeprecationWarning", ] markers = [ "auth_aws: tests that rely on pymongo-auth-aws", From 396b0764ea462a5dd0b3daf9c4705ac1b19ac4eb Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Mon, 24 Mar 2025 14:45:59 -0400 Subject: [PATCH 2/4] Add test, address review --- doc/changelog.rst | 6 +++ pymongo/read_preferences.py | 6 +-- pyproject.toml | 1 - test/asynchronous/test_read_preferences.py | 61 +++++++++++++--------- test/test_read_preferences.py | 61 +++++++++++++--------- 5 files changed, 83 insertions(+), 52 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index b172da6b8e..55fd5e519d 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -9,6 +9,12 @@ PyMongo 4.12 brings a number of changes including: - Support for configuring DEK cache lifetime via the ``key_expiration_ms`` argument to :class:`~pymongo.encryption_options.AutoEncryptionOpts`. - Support for $lookup in CSFLE and QE supported on MongoDB 8.1+. +- Deprecated the ``hedge`` parameter for + :class:`~pymongo.read_preferences.PrimaryPreferred`, + :class:`~pymongo.read_preferences.Secondary`, + :class:`~pymongo.read_preferences.SecondaryPreferred`, + :class:`~pymongo.read_preferences.Nearest`. Support for ``hedge`` will be removed in PyMongo 5.0. + Issues Resolved ............... diff --git a/pymongo/read_preferences.py b/pymongo/read_preferences.py index e6940dd878..ac36ea02aa 100644 --- a/pymongo/read_preferences.py +++ b/pymongo/read_preferences.py @@ -105,7 +105,7 @@ def _validate_hedge(hedge: Optional[_Hedge]) -> Optional[_Hedge]: raise TypeError(f"hedge must be a dictionary, not {hedge!r}") warnings.warn( - "Hedged reads are deprecated starting in server version 8.0.", + "The read preference 'hedge' option is deprecated in PyMongo 4.12+ because hedged reads are deprecated in MongoDB version 8.0+. Support for 'hedge' will be removed in PyMongo 5.0.", DeprecationWarning, stacklevel=2, ) @@ -150,7 +150,7 @@ def document(self) -> dict[str, Any]: doc["maxStalenessSeconds"] = self.__max_staleness if self.__hedge not in (None, {}): warnings.warn( - "Hedged reads are deprecated starting in server version 8.0.", + "The read preference 'hedge' option is deprecated in PyMongo 4.12+ because hedged reads are deprecated in MongoDB version 8.0+. Support for 'hedge' will be removed in PyMongo 5.0.", DeprecationWarning, stacklevel=2, ) @@ -216,7 +216,7 @@ def hedge(self) -> Optional[_Hedge]: """ if self.__hedge is not None: warnings.warn( - "Hedged reads are deprecated starting in server version 8.0.", + "The read preference 'hedge' option is deprecated in PyMongo 4.12+ because hedged reads are deprecated in MongoDB version 8.0+. Support for 'hedge' will be removed in PyMongo 5.0.", DeprecationWarning, stacklevel=2, ) diff --git a/pyproject.toml b/pyproject.toml index 719f93b9b5..f8c25ed602 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,7 +119,6 @@ filterwarnings = [ "module:please use dns.resolver.Resolver.resolve:DeprecationWarning", # https://github.com/dateutil/dateutil/issues/1314 "module:datetime.datetime.utc:DeprecationWarning", - "module:Hedged reads are deprecated:DeprecationWarning", ] markers = [ "auth_aws: tests that rely on pymongo-auth-aws", diff --git a/test/asynchronous/test_read_preferences.py b/test/asynchronous/test_read_preferences.py index 5bea174058..72dd809db0 100644 --- a/test/asynchronous/test_read_preferences.py +++ b/test/asynchronous/test_read_preferences.py @@ -35,6 +35,7 @@ ) from test.utils_shared import ( OvertCommandListener, + _ignore_deprecations, async_wait_until, one, ) @@ -542,33 +543,44 @@ def test_read_preference_document_hedge(self): for mode, cls in cases.items(): with self.assertRaises(TypeError): cls(hedge=[]) # type: ignore - - pref = cls(hedge={}) - self.assertEqual(pref.document, {"mode": mode}) - out = _maybe_add_read_preference({}, pref) - if cls == SecondaryPreferred: - # SecondaryPreferred without hedge doesn't add $readPreference. - self.assertEqual(out, {}) - else: + with _ignore_deprecations(): + pref = cls(hedge={}) + self.assertEqual(pref.document, {"mode": mode}) + out = _maybe_add_read_preference({}, pref) + if cls == SecondaryPreferred: + # SecondaryPreferred without hedge doesn't add $readPreference. + self.assertEqual(out, {}) + else: + self.assertEqual(out, SON([("$query", {}), ("$readPreference", pref.document)])) + + hedge: dict[str, Any] = {"enabled": True} + pref = cls(hedge=hedge) + self.assertEqual(pref.document, {"mode": mode, "hedge": hedge}) + out = _maybe_add_read_preference({}, pref) self.assertEqual(out, SON([("$query", {}), ("$readPreference", pref.document)])) - hedge: dict[str, Any] = {"enabled": True} - pref = cls(hedge=hedge) - self.assertEqual(pref.document, {"mode": mode, "hedge": hedge}) - out = _maybe_add_read_preference({}, pref) - self.assertEqual(out, SON([("$query", {}), ("$readPreference", pref.document)])) + hedge = {"enabled": False} + pref = cls(hedge=hedge) + self.assertEqual(pref.document, {"mode": mode, "hedge": hedge}) + out = _maybe_add_read_preference({}, pref) + self.assertEqual(out, SON([("$query", {}), ("$readPreference", pref.document)])) - hedge = {"enabled": False} - pref = cls(hedge=hedge) - self.assertEqual(pref.document, {"mode": mode, "hedge": hedge}) - out = _maybe_add_read_preference({}, pref) - self.assertEqual(out, SON([("$query", {}), ("$readPreference", pref.document)])) + hedge = {"enabled": False, "extra": "option"} + pref = cls(hedge=hedge) + self.assertEqual(pref.document, {"mode": mode, "hedge": hedge}) + out = _maybe_add_read_preference({}, pref) + self.assertEqual(out, SON([("$query", {}), ("$readPreference", pref.document)])) - hedge = {"enabled": False, "extra": "option"} - pref = cls(hedge=hedge) - self.assertEqual(pref.document, {"mode": mode, "hedge": hedge}) - out = _maybe_add_read_preference({}, pref) - self.assertEqual(out, SON([("$query", {}), ("$readPreference", pref.document)])) + def test_read_preference_hedge_deprecated(self): + cases = { + "primaryPreferred": PrimaryPreferred, + "secondary": Secondary, + "secondaryPreferred": SecondaryPreferred, + "nearest": Nearest, + } + for _, cls in cases.items(): + with self.assertRaises(DeprecationWarning): + cls(hedge={"enabled": True}) async def test_send_hedge(self): cases = { @@ -582,7 +594,8 @@ async def test_send_hedge(self): client = await self.async_rs_client(event_listeners=[listener]) await client.admin.command("ping") for _mode, cls in cases.items(): - pref = cls(hedge={"enabled": True}) + with _ignore_deprecations(): + pref = cls(hedge={"enabled": True}) coll = client.test.get_collection("test", read_preference=pref) listener.reset() await coll.find_one() diff --git a/test/test_read_preferences.py b/test/test_read_preferences.py index e754c896ad..afde01723d 100644 --- a/test/test_read_preferences.py +++ b/test/test_read_preferences.py @@ -35,6 +35,7 @@ ) from test.utils_shared import ( OvertCommandListener, + _ignore_deprecations, one, wait_until, ) @@ -522,33 +523,44 @@ def test_read_preference_document_hedge(self): for mode, cls in cases.items(): with self.assertRaises(TypeError): cls(hedge=[]) # type: ignore - - pref = cls(hedge={}) - self.assertEqual(pref.document, {"mode": mode}) - out = _maybe_add_read_preference({}, pref) - if cls == SecondaryPreferred: - # SecondaryPreferred without hedge doesn't add $readPreference. - self.assertEqual(out, {}) - else: + with _ignore_deprecations(): + pref = cls(hedge={}) + self.assertEqual(pref.document, {"mode": mode}) + out = _maybe_add_read_preference({}, pref) + if cls == SecondaryPreferred: + # SecondaryPreferred without hedge doesn't add $readPreference. + self.assertEqual(out, {}) + else: + self.assertEqual(out, SON([("$query", {}), ("$readPreference", pref.document)])) + + hedge: dict[str, Any] = {"enabled": True} + pref = cls(hedge=hedge) + self.assertEqual(pref.document, {"mode": mode, "hedge": hedge}) + out = _maybe_add_read_preference({}, pref) self.assertEqual(out, SON([("$query", {}), ("$readPreference", pref.document)])) - hedge: dict[str, Any] = {"enabled": True} - pref = cls(hedge=hedge) - self.assertEqual(pref.document, {"mode": mode, "hedge": hedge}) - out = _maybe_add_read_preference({}, pref) - self.assertEqual(out, SON([("$query", {}), ("$readPreference", pref.document)])) + hedge = {"enabled": False} + pref = cls(hedge=hedge) + self.assertEqual(pref.document, {"mode": mode, "hedge": hedge}) + out = _maybe_add_read_preference({}, pref) + self.assertEqual(out, SON([("$query", {}), ("$readPreference", pref.document)])) - hedge = {"enabled": False} - pref = cls(hedge=hedge) - self.assertEqual(pref.document, {"mode": mode, "hedge": hedge}) - out = _maybe_add_read_preference({}, pref) - self.assertEqual(out, SON([("$query", {}), ("$readPreference", pref.document)])) + hedge = {"enabled": False, "extra": "option"} + pref = cls(hedge=hedge) + self.assertEqual(pref.document, {"mode": mode, "hedge": hedge}) + out = _maybe_add_read_preference({}, pref) + self.assertEqual(out, SON([("$query", {}), ("$readPreference", pref.document)])) - hedge = {"enabled": False, "extra": "option"} - pref = cls(hedge=hedge) - self.assertEqual(pref.document, {"mode": mode, "hedge": hedge}) - out = _maybe_add_read_preference({}, pref) - self.assertEqual(out, SON([("$query", {}), ("$readPreference", pref.document)])) + def test_read_preference_hedge_deprecated(self): + cases = { + "primaryPreferred": PrimaryPreferred, + "secondary": Secondary, + "secondaryPreferred": SecondaryPreferred, + "nearest": Nearest, + } + for _, cls in cases.items(): + with self.assertRaises(DeprecationWarning): + cls(hedge={"enabled": True}) def test_send_hedge(self): cases = { @@ -562,7 +574,8 @@ def test_send_hedge(self): client = self.rs_client(event_listeners=[listener]) client.admin.command("ping") for _mode, cls in cases.items(): - pref = cls(hedge={"enabled": True}) + with _ignore_deprecations(): + pref = cls(hedge={"enabled": True}) coll = client.test.get_collection("test", read_preference=pref) listener.reset() coll.find_one() From ce05c697a90b5e724876d55a51160e703b6d0b42 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Mon, 24 Mar 2025 16:08:40 -0400 Subject: [PATCH 3/4] Address review --- pymongo/read_preferences.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/pymongo/read_preferences.py b/pymongo/read_preferences.py index ac36ea02aa..15a0f67102 100644 --- a/pymongo/read_preferences.py +++ b/pymongo/read_preferences.py @@ -149,11 +149,6 @@ def document(self) -> dict[str, Any]: if self.__max_staleness != -1: doc["maxStalenessSeconds"] = self.__max_staleness if self.__hedge not in (None, {}): - warnings.warn( - "The read preference 'hedge' option is deprecated in PyMongo 4.12+ because hedged reads are deprecated in MongoDB version 8.0+. Support for 'hedge' will be removed in PyMongo 5.0.", - DeprecationWarning, - stacklevel=2, - ) doc["hedge"] = self.__hedge return doc @@ -194,7 +189,9 @@ def max_staleness(self) -> int: @property def hedge(self) -> Optional[_Hedge]: - """The read preference ``hedge`` parameter. + """**DEPRECATED** - The read preference 'hedge' option is deprecated in PyMongo 4.12+ because hedged reads are deprecated in MongoDB version 8.0+. Support for 'hedge' will be removed in PyMongo 5.0. + + The read preference ``hedge`` parameter. A dictionary that configures how the server will perform hedged reads. It consists of the following keys: @@ -329,7 +326,7 @@ class PrimaryPreferred(_ServerMode): replication before it will no longer be selected for operations. Default -1, meaning no maximum. If it is set, it must be at least 90 seconds. - :param hedge: The :attr:`~hedge` to use if the primary is not available. + :param hedge: **DEPRECATED** - The :attr:`~hedge` for this read preference. .. versionchanged:: 3.11 Added ``hedge`` parameter. @@ -371,7 +368,7 @@ class Secondary(_ServerMode): replication before it will no longer be selected for operations. Default -1, meaning no maximum. If it is set, it must be at least 90 seconds. - :param hedge: The :attr:`~hedge` for this read preference. + :param hedge: **DEPRECATED** - The :attr:`~hedge` for this read preference. .. versionchanged:: 3.11 Added ``hedge`` parameter. @@ -414,7 +411,7 @@ class SecondaryPreferred(_ServerMode): replication before it will no longer be selected for operations. Default -1, meaning no maximum. If it is set, it must be at least 90 seconds. - :param hedge: The :attr:`~hedge` for this read preference. + :param hedge: **DEPRECATED** - The :attr:`~hedge` for this read preference. .. versionchanged:: 3.11 Added ``hedge`` parameter. @@ -458,7 +455,7 @@ class Nearest(_ServerMode): replication before it will no longer be selected for operations. Default -1, meaning no maximum. If it is set, it must be at least 90 seconds. - :param hedge: The :attr:`~hedge` for this read preference. + :param hedge: **DEPRECATED** - The :attr:`~hedge` for this read preference. .. versionchanged:: 3.11 Added ``hedge`` parameter. From 5b87911c2b137a622007a2b80936d80e4244430a Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Tue, 25 Mar 2025 09:18:02 -0400 Subject: [PATCH 4/4] Stacklevel 4 for constructor --- pymongo/read_preferences.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymongo/read_preferences.py b/pymongo/read_preferences.py index 15a0f67102..dae414c37c 100644 --- a/pymongo/read_preferences.py +++ b/pymongo/read_preferences.py @@ -107,7 +107,7 @@ def _validate_hedge(hedge: Optional[_Hedge]) -> Optional[_Hedge]: warnings.warn( "The read preference 'hedge' option is deprecated in PyMongo 4.12+ because hedged reads are deprecated in MongoDB version 8.0+. Support for 'hedge' will be removed in PyMongo 5.0.", DeprecationWarning, - stacklevel=2, + stacklevel=4, ) return hedge