diff --git a/doc/changelog.rst b/doc/changelog.rst index 12991eeb29..47e6acbb4b 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -12,6 +12,11 @@ PyMongo 4.12 brings a number of changes including: - Added index hinting support to the :meth:`~pymongo.asynchronous.collection.AsyncCollection.distinct` and :meth:`~pymongo.collection.Collection.distinct` commands. +- 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 581f7ca66f..dae414c37c 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( + "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=4, + ) return hedge @@ -183,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: @@ -203,6 +211,12 @@ def hedge(self) -> Optional[_Hedge]: .. versionadded:: 3.11 """ + if self.__hedge is not 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, + ) return self.__hedge @property @@ -312,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. @@ -354,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. @@ -397,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. @@ -441,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. 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()