Fix MonkeyPatch.setattr/delattr binding descriptors when raising=False

This commit is contained in:
Ganden Schaffner 2023-01-09 00:00:00 -08:00
parent d5f04b526b
commit 82557504ba
No known key found for this signature in database
GPG Key ID: AAF2420F20D8B553
2 changed files with 15 additions and 12 deletions

View File

@ -0,0 +1,3 @@
Fix bug where `monkeypatch.setattr` and `monkeypatch.delattr` bind descriptors (possibly causing unwanted side-effects) to implement their `hasattr` checks even when their checks are disabled by `raising=False`.
Note that they still must bind descriptors to implement their checks if `raising=True`.

View File

@ -241,8 +241,7 @@ class MonkeyPatch:
"import string" "import string"
) )
oldval = getattr(target, name, notset) if raising and not hasattr(target, name):
if raising and oldval is notset:
raise AttributeError(f"{target!r} has no attribute {name!r}") raise AttributeError(f"{target!r} has no attribute {name!r}")
# Prevent `undo` from polluting `vars(target)` with an object that was not in it # Prevent `undo` from polluting `vars(target)` with an object that was not in it
@ -268,7 +267,6 @@ class MonkeyPatch:
``raising`` is set to False. ``raising`` is set to False.
""" """
__tracebackhide__ = True __tracebackhide__ = True
import inspect
if isinstance(name, Notset): if isinstance(name, Notset):
if not isinstance(target, str): if not isinstance(target, str):
@ -279,16 +277,18 @@ class MonkeyPatch:
) )
name, target = derive_importpath(target, raising) name, target = derive_importpath(target, raising)
if not hasattr(target, name): if raising and not hasattr(target, name):
if raising: raise AttributeError(f"{target!r} has no attribute {name!r}")
raise AttributeError(f"{target!r} has no attribute {name!r}")
else: # Prevent `undo` from overwriting class descriptors (like
oldval = getattr(target, name, notset) # staticmethod/classmethod) with the results of descriptor binding.
# Avoid class descriptors like staticmethod/classmethod. oldval = vars(target).get(name, notset)
if inspect.isclass(target): try:
oldval = target.__dict__.get(name, notset)
self._setattr.append((target, name, oldval))
delattr(target, name) delattr(target, name)
except AttributeError:
pass
else:
self._setattr.append((target, name, oldval))
def setitem(self, dic: MutableMapping[K, V], name: K, value: V) -> None: def setitem(self, dic: MutableMapping[K, V], name: K, value: V) -> None:
"""Set dictionary entry ``name`` to value.""" """Set dictionary entry ``name`` to value."""