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

LocalProxy breaking isinstance() for abc.ABC #2188

Closed
douglas-raillard-arm opened this issue Jul 20, 2021 · 2 comments
Closed

LocalProxy breaking isinstance() for abc.ABC #2188

douglas-raillard-arm opened this issue Jul 20, 2021 · 2 comments
Milestone

Comments

@douglas-raillard-arm
Copy link

When tested with isinstance() against an ABC, LocalProxy instances raise an exception rather than returning False.

import abc
from werkzeug import local

ls=local.LocalStack()
x=ls()

class C(abc.ABC):
	pass

isinstance(x, C)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.9/abc.py", line 119, in __instancecheck__
    return _abc_instancecheck(cls, instance)
  File "/usr/lib/python3.9/abc.py", line 123, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
TypeError: issubclass() arg 1 must be a class

The call to isinstance() should have returned False rather than an exception.

It seems to be related to the fact that x.__class__ is not a class as expected, but an unbound method. Calling it with x.__class__() returns the expected werkzeug.local.LocalProxy class. While it's not documented in Python except for builtin types, most of the ecosystem will assume type(x) is x.__class__, including it appears the standard library.

Environment:

  • Python version: Python 3.9.6
  • Werkzeug version: 2.0.1
@davidism
Copy link
Member

It looks like the C implementation of abc doesn't trigger the descriptor protocol when inspecting the __class__ attribute. This seems like it should be reported to CPython.

@douglas-raillard-arm
Copy link
Author

If something is not triggered, it's not only in abc though:

It seems to be related to the fact that x.__class__ is not a class as expected, but an unbound method

This can be observed with:

import abc
from werkzeug import local

ls=local.LocalStack()
x=ls()

print(x.__class__)
assert isinstance(x.__class__, type)

Which prints:

<bound method LocalProxy.<lambda> of <LocalProxy unbound>>
Traceback (most recent call last):
  File "testwzeug.py", line 8, in <module>
    assert isinstance(x.__class__, type)
AssertionError

It would be expected to print:

<class 'werkzeug.local.LocalProxy'>

which can be obtained with x.__class__().

The descriptor protocol seems to be followed by Python:

(Pdb) type(x).__dict__['__class__'].__get__(x, type(x))
<bound method LocalProxy.<lambda> of <LocalProxy unbound>>

In local.py:

class _ProxyLookup:
    ....
    def __get__(self, instance: "LocalProxy", owner: t.Optional[type] = None) -> t.Any:
        ....
            return self.fallback.__get__(instance, owner)  # type: ignore

Replacing with return self.fallback.__get__(instance, owner)() fixes the problem by actually calling the bound method, rather than just returning it

@davidism davidism added this to the 2.0.3 milestone Dec 23, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jan 7, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants