Repository navigation
Conversation
|
@ybai08 Thanks for the pull request, and for the thorough analysis and tests. You are correct that this is a bug. Working through it, though, what is reported turns out to be one half of a broader problem. The special methods added to the proxy class should never have been looked up on the wrapped object itself, but always on its type. Special methods are only ever looked up on the type of an object, never the instance, so:
That means the general fix is not a special case for classes, but simply: wrapped_attrs = dir(type(wrapped))in Fixing both halves changes the required code changes, and needs additional tests on top of the ones here: instance attributes shadowing dunder names, on both the Thanks again for reporting the issue and for the investigation which identified it. You will be acknowledged in the changelog entry for the fix. |
|
Thanks for working through it. Looking up the special methods on |
|
By the way. What are you doing that you caught this issue in the first place? In the last couple of weeks I have had a few other obscure corner cases reported that needed fixes and has me wondering whether is AI just being asked to find any problem in the code, or whether, although all reported by different users, you are all somehow connected and working on some hyper complex monkey patching. |
…ect. The special methods added to the class generated for an AutoObjectProxy were determined from dir() of the wrapped object, and the fixups applied when __wrapped__ was reassigned from hasattr() on the wrapped object. As special methods are only ever looked up on the type of an object, this gave the wrong result when the wrapped object was a class, in which case the special methods the class defines for its instances were added to the proxy class, and the special methods of its metaclass were missed. It also gave the wrong result when the wrapped object was an instance, in which case instance attributes shadowing the dunder names were treated as special methods of the wrapped object, and special methods not reported by a __dir__() method were missed. The special methods are now always determined from the type of the wrapped object, which also covers a LazyObjectProxy whose factory returns an object, as resolving the wrapped object reassigns __wrapped__. Supersedes pull request #361.
|
I have released 2.5.2rc1 (https://pypi.org/project/wrapt/2.5.2rc1/) with the changes. The wrapt package gets a lot of use even as a pre-release, so I will let it sit out there as an RC for a few days at least before releasing it as 2.5.2 final version. Thanks for your help again. I will close this PR now since changes were superseded. |
When
AutoObjectProxywraps a class, it decides which special methods to add to its generated class by checkingdir(wrapped). For a class, that lists the methods the class defines for its instances. Python looks special methods up on the type of an object, and the type of a class is its metaclass. So the proxy picks up methods that don't apply to the class it wraps, and misses ones that do.__wrapped_setattr_fixups__()has the same problem throughhasattr(self.__wrapped__, ...), and that path also affects aLazyObjectProxywhose factory returns a class.What goes wrong, compared with using the class directly:
__get__()gets bound as a descriptor on attribute access, so reading the attribute calls the unbound__get__().__iter__()passesisinstance(proxy, collections.abc.Iterable), while the class itself doesn't.__iter__()can be iterated, butlist(AutoObjectProxy(cls))raisesTypeErrorbecause the metaclass method is never added. On Python 3.9 this includesenum.Enumsubclasses, sincedir()of an enum class there doesn't list__iter__.The fix adds a small helper,
_special_method_source(). For a class it returnstype(wrapped), and for anything else it returns the object unchanged.AutoObjectProxy.__new__()and__wrapped_setattr_fixups__()now check that object. Non-class objects take the same path as before, including proxies, whosedir()is forwarded. The__call__check still usescallable()on the wrapped object.LazyObjectProxy.__new__()already checksdir(interface)against a type, so it is unchanged.Tests added:
tests/core/test_auto_object_proxy.py:__await__/__iter__is not reported as awaitable or iterable;__iter__;__wrapped__reassignment.tests/core/test_lazy_object_proxy.py: aLazyObjectProxywhose factory returns a descriptor class, stored in a class body after it has been resolved.I added a change note under a new
Version 2.5.2heading indocs/changes.rst. Please move or retitle it as you see fit.Validation (local, macOS arm64):
hasattr()on a class does reach its metaclass, and it stays in as a guard. After the fix all 51 tests in the two files pass in both modes.just test-versionby hand (just isn't installed here; the venv was kept outside the repo) for Python 3.9, 3.11, 3.12, 3.13 and 3.14. Each version ran pure-Python install, C extension enabled and C extension disabled at runtime, with the pinned mypy, and all 15 runs passed. Python 3.10, 3.14t, 3.15 and 3.15t were not run because those interpreters aren't installed here, so the fulljust testmatrix has not been run.mypy.stubtestwith the allowlist passes for both the C extension and pure Python.black --checkandisort --check-onlypass on the changed files.