diff options
author | David Lord <davidism@gmail.com> | 2021-12-23 14:30:57 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-23 14:30:57 -0700 |
commit | 175942940f447b3fcfface3ee77197d292d898b7 (patch) | |
tree | 4ba58f76c18e8a2327b5261e46290feba3178855 | |
parent | c58205e94affb96887faf0ee780e89bb3328b065 (diff) | |
parent | eb73beb4c475ea694a93158a86069e166a2696a1 (diff) | |
download | werkzeug-175942940f447b3fcfface3ee77197d292d898b7.tar.gz |
Merge pull request #2306 from pallets:proxy-fallback-attr
fix proxy `__class__` and `__doc__` fallback
-rw-r--r-- | CHANGES.rst | 3 | ||||
-rw-r--r-- | src/werkzeug/local.py | 30 | ||||
-rw-r--r-- | tests/test_local.py | 15 |
3 files changed, 36 insertions, 12 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index fd0f7163..6bbb2e0b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,6 +11,9 @@ Unreleased ``Request`` in addition to ``WSGIEnvironment`` for the first parameter. :pr:`2290` - Fix type annotation for ``Request.user_agent_class``. :issue:`2273` +- Accessing ``LocalProxy.__class__`` and ``__doc__`` on an unbound + proxy returns the fallback value instead of a method object. + :issue:`2188` Version 2.0.2 diff --git a/src/werkzeug/local.py b/src/werkzeug/local.py index d2c99af5..2b222274 100644 --- a/src/werkzeug/local.py +++ b/src/werkzeug/local.py @@ -381,19 +381,23 @@ class _ProxyLookup: :param f: The built-in function this attribute is accessed through. Instead of looking up the special method, the function call is redone on the object. - :param fallback: Call this method if the proxy is unbound instead of - raising a :exc:`RuntimeError`. - :param class_value: Value to return when accessed from the class. - Used for ``__doc__`` so building docs still works. + :param fallback: Return this function if the proxy is unbound + instead of raising a :exc:`RuntimeError`. + :param is_attr: This proxied name is an attribute, not a function. + Call the fallback immediately to get the value. + :param class_value: Value to return when accessed from the + ``LocalProxy`` class directly. Used for ``__doc__`` so building + docs still works. """ - __slots__ = ("bind_f", "fallback", "class_value", "name") + __slots__ = ("bind_f", "fallback", "is_attr", "class_value", "name") def __init__( self, f: t.Optional[t.Callable] = None, fallback: t.Optional[t.Callable] = None, class_value: t.Optional[t.Any] = None, + is_attr: bool = False, ) -> None: bind_f: t.Optional[t.Callable[["LocalProxy", t.Any], t.Callable]] @@ -416,6 +420,7 @@ class _ProxyLookup: self.bind_f = bind_f self.fallback = fallback self.class_value = class_value + self.is_attr = is_attr def __set_name__(self, owner: "LocalProxy", name: str) -> None: self.name = name @@ -433,7 +438,14 @@ class _ProxyLookup: if self.fallback is None: raise - return self.fallback.__get__(instance, owner) # type: ignore + fallback = self.fallback.__get__(instance, owner) # type: ignore + + if self.is_attr: + # __class__ and __doc__ are attributes, not methods. + # Call the fallback to get the value. + return fallback() + + return fallback if self.bind_f is not None: return self.bind_f(instance, obj) @@ -559,7 +571,7 @@ class LocalProxy: raise RuntimeError(f"no object bound to {name}") from None __doc__ = _ProxyLookup( # type: ignore - class_value=__doc__, fallback=lambda self: type(self).__doc__ + class_value=__doc__, fallback=lambda self: type(self).__doc__, is_attr=True ) # __del__ should only delete the proxy __repr__ = _ProxyLookup( # type: ignore @@ -591,7 +603,9 @@ class LocalProxy: # __weakref__ (__getattr__) # __init_subclass__ (proxying metaclass not supported) # __prepare__ (metaclass) - __class__ = _ProxyLookup(fallback=lambda self: type(self)) # type: ignore + __class__ = _ProxyLookup( + fallback=lambda self: type(self), is_attr=True + ) # type: ignore __instancecheck__ = _ProxyLookup(lambda self, other: isinstance(other, self)) __subclasscheck__ = _ProxyLookup(lambda self, other: issubclass(other, self)) # __class_getitem__ triggered through __getitem__ diff --git a/tests/test_local.py b/tests/test_local.py index d434a840..dbab4e75 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -225,13 +225,20 @@ def test_proxy_doc(): def test_proxy_fallback(): - def _raises(): - raise RuntimeError() + local_stack = local.LocalStack() + local_proxy = local_stack() - local_proxy = local.LocalProxy(_raises) assert repr(local_proxy) == "<LocalProxy unbound>" assert isinstance(local_proxy, local.LocalProxy) - assert not isinstance(local_proxy, Thread) + assert local_proxy.__class__ is local.LocalProxy + assert "LocalProxy" in local_proxy.__doc__ + + local_stack.push(42) + + assert repr(local_proxy) == "42" + assert isinstance(local_proxy, int) + assert local_proxy.__class__ is int + assert "int(" in local_proxy.__doc__ def test_proxy_unbound(): |