Skip to content

Commit

Permalink
Replace setattr and getattr calls with __dict__ manipulations (#905)
Browse files Browse the repository at this point in the history
- fixes problems with custom setattr__ implementations like pytorch
  • Loading branch information
Numeri authored Nov 15, 2023
1 parent c4444e4 commit 1d52e3f
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 22 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ The released versions correspond to PyPI releases.
### Fixes
* fixes the problem that filesystem patching was still active in the pytest
logreport phase (see [#904](../../issues/904))
* Restores compatibility with PyTorch 2.0 and above, as well as with other
classes that have custom __setattr__ methods (see [#905](../../pull/905)).

## [Version 5.3.0](https://pypi.python.org/pypi/pyfakefs/5.3.0) (2023-10-11)
Adds official support for Python 3.12.
Expand Down
36 changes: 14 additions & 22 deletions pyfakefs/mox3_stubout.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,22 +61,20 @@ def smart_set(self, obj, attr_name, new_attr):
This method supports the case where attr_name is a staticmethod or a
classmethod of obj.
Notes:
- If obj is an instance, then it is its class that will actually be
stubbed. Note that the method Set() does not do that: if obj is
an instance, it (and not its class) will be stubbed.
- The stubbing is using the builtin getattr and setattr. So, the
__get__ and __set__ will be called when stubbing (TODO: A better
idea would probably be to manipulate obj.__dict__ instead of
getattr() and setattr()).
If obj is an instance, then it is its class that will actually be
stubbed. Note that the method Set() does not do that: if obj is an
instance, it (and not its class) will be stubbed.
Raises AttributeError if the attribute cannot be found.
"""
if inspect.ismodule(obj) or (
not inspect.isclass(obj) and attr_name in obj.__dict__
):
orig_obj = obj
orig_attr = getattr(obj, attr_name)
if attr_name in obj.__dict__:
orig_attr = obj.__dict__[attr_name]
else:
orig_attr = None

else:
if not inspect.isclass(obj):
Expand All @@ -91,21 +89,15 @@ def smart_set(self, obj, attr_name, new_attr):
for cls in mro:
try:
orig_obj = cls
orig_attr = getattr(obj, attr_name)
except AttributeError:
orig_attr = obj.__dict__[attr_name]
except KeyError:
continue

if orig_attr is None:
raise AttributeError("Attribute not found.")

# Calling getattr() on a staticmethod transforms it to a 'normal'
# function. We need to ensure that we put it back as a staticmethod.
old_attribute = obj.__dict__.get(attr_name)
if old_attribute is not None and isinstance(old_attribute, staticmethod):
orig_attr = staticmethod(orig_attr) # pytype: disable=not-callable

self.stubs.append((orig_obj, attr_name, orig_attr))
setattr(orig_obj, attr_name, new_attr)
orig_obj.__dict__[attr_name] = new_attr

def smart_unset_all(self):
"""Reverses all the SmartSet() calls.
Expand All @@ -116,8 +108,8 @@ def smart_unset_all(self):
"""
self.stubs.reverse()

for args in self.stubs:
setattr(*args)
for obj, attr_name, old_attr in self.stubs:
obj.__dict__[attr_name] = old_attr

self.stubs = []

Expand All @@ -143,7 +135,7 @@ def set(self, parent, child_name, new_child):
old_child = classmethod(old_child.__func__)

self.cache.append((parent, old_child, child_name))
setattr(parent, child_name, new_child)
parent.__dict__[child_name] = new_child

def unset_all(self):
"""Reverses all the Set() calls.
Expand All @@ -158,5 +150,5 @@ def unset_all(self):
self.cache.reverse()

for parent, old_child, child_name in self.cache:
setattr(parent, child_name, old_child)
parent.__dict__[child_name] = old_child
self.cache = []

0 comments on commit 1d52e3f

Please sign in to comment.