Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Antony Lee
Arel Cordero
Arias Emmanuel
Ariel Pillemer
Arpan Sahu
Armin Rigo
Aron Coyle
Aron Curzon
Expand Down
1 change: 1 addition & 0 deletions changelog/10644.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix :class:`pytest.MonkeyPatch` restoring inherited attributes into an instance's ``__dict__`` after undoing ``setattr`` on an object.
8 changes: 8 additions & 0 deletions src/_pytest/monkeypatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,14 @@ def setattr(
# avoid class descriptors like staticmethod/classmethod
if inspect.isclass(target):
oldval = target.__dict__.get(name, NOTSET)
else:
try:
target_vars = vars(target)
except TypeError:
pass
else:
if name not in target_vars:
oldval = NOTSET
setattr(target, name, value)
self._setattr.append((target, name, oldval))

Expand Down
23 changes: 23 additions & 0 deletions testing/test_monkeypatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import annotations

from collections.abc import Generator
import inspect
import os
from pathlib import Path
import re
Expand Down Expand Up @@ -51,6 +52,28 @@ class A:
monkeypatch.setattr(A, "y") # type: ignore[call-overload]


def test_setattr_inherited_attribute_undo_restores_instance_dict() -> None:
class Descriptor:
def __get__(self, obj, objtype=None):
return 1

class Parent:
x = 1
descriptor = Descriptor()

for name in ("x", "descriptor"):
target = Parent()
monkeypatch = MonkeyPatch()
assert name not in vars(target)
assert isinstance(inspect.getattr_static(target, "descriptor"), Descriptor)

monkeypatch.setattr(target, name, 2)
monkeypatch.undo()

assert name not in vars(target)
assert isinstance(inspect.getattr_static(target, "descriptor"), Descriptor)


class TestSetattrWithImportPath:
def test_string_expression(self, monkeypatch: MonkeyPatch) -> None:
with monkeypatch.context() as mp:
Expand Down
Loading