Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace setattr and getattr calls with __dict__ manipulations #905

Merged
merged 8 commits into from
Nov 15, 2023
Merged
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
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 = []
Loading