diff --git a/CHANGES.md b/CHANGES.md index 6325397d..979d8c8c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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. diff --git a/pyfakefs/mox3_stubout.py b/pyfakefs/mox3_stubout.py index c3f3a881..6e500de0 100644 --- a/pyfakefs/mox3_stubout.py +++ b/pyfakefs/mox3_stubout.py @@ -61,14 +61,9 @@ 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. """ @@ -76,7 +71,10 @@ def smart_set(self, obj, attr_name, new_attr): 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): @@ -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. @@ -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 = [] @@ -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. @@ -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 = []