From b493a3fc183ade9879c81d0d0c3d3a0666ce6e2d Mon Sep 17 00:00:00 2001 From: Nils Philippsen Date: Mon, 27 Feb 2023 19:27:50 +0100 Subject: [PATCH] Allow None as default values for MutableDict MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Previously, e.g. `MutableDict.pop(..., None)` wouldn’t return `None` if the key doesn’t exist. Fixes: #9380 Signed-off-by: Nils Philippsen --- lib/sqlalchemy/ext/mutable.py | 19 +++++++++++++++---- test/ext/test_mutable.py | 13 +++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/sqlalchemy/ext/mutable.py b/lib/sqlalchemy/ext/mutable.py index 07f357bd6b..6c87749c5f 100644 --- a/lib/sqlalchemy/ext/mutable.py +++ b/lib/sqlalchemy/ext/mutable.py @@ -389,10 +389,13 @@ from ..orm.context import QueryContext from ..orm.decl_api import DeclarativeAttributeIntercept from ..orm.state import InstanceState from ..orm.unitofwork import UOWTransaction +from ..sql.base import _NoArg +from ..sql.base import NO_ARG from ..sql.base import SchemaEventTarget from ..sql.schema import Column from ..sql.type_api import TypeEngine from ..util import memoized_property +from ..util.typing import Literal from ..util.typing import SupportsIndex from ..util.typing import TypeGuard @@ -781,8 +784,10 @@ class MutableDict(Mutable, Dict[_KT, _VT]): super().__setitem__(key, value) self.changed() - def _exists(self, value: _T | None) -> TypeGuard[_T]: - return value is not None + def _exists( + self, value: _T | None | Literal[_NoArg.NO_ARG] + ) -> TypeGuard[_T]: + return value is not NO_ARG def _is_none(self, value: _T | None) -> TypeGuard[None]: return value is None @@ -795,7 +800,9 @@ class MutableDict(Mutable, Dict[_KT, _VT]): def setdefault(self, key: _KT, value: _VT) -> _VT: ... - def setdefault(self, key: _KT, value: _VT | None = None) -> _VT | None: + def setdefault( + self, key: _KT, value: _VT | None | Literal[_NoArg.NO_ARG] = NO_ARG + ) -> _VT | None: if self._exists(value): result = super().setdefault(key, value) else: @@ -820,7 +827,11 @@ class MutableDict(Mutable, Dict[_KT, _VT]): def pop(self, __key: _KT, __default: _VT | _T) -> _VT | _T: ... - def pop(self, __key: _KT, __default: _VT | _T | None = None) -> _VT | _T: + def pop( + self, + __key: _KT, + __default: _VT | _T | None | Literal[_NoArg.NO_ARG] = NO_ARG, + ) -> _VT | _T: if self._exists(__default): result = super().pop(__key, __default) else: diff --git a/test/ext/test_mutable.py b/test/ext/test_mutable.py index 50dc22e351..9a9ebc92c8 100644 --- a/test/ext/test_mutable.py +++ b/test/ext/test_mutable.py @@ -349,6 +349,19 @@ class _MutableDictTestBase(_MutableDictTestFixture): eq_(f1.data, {"c": "d"}) + def test_pop_default_none(self): + sess = fixture_session() + + f1 = Foo(data={"a": "b", "c": "d"}) + sess.add(f1) + sess.commit() + + eq_(f1.data.pop("a", None), "b") + eq_(f1.data.pop("a", None), None) + sess.commit() + + eq_(f1.data, {"c": "d"}) + def test_popitem(self): sess = fixture_session() -- 2.47.3