summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2021-12-23 14:30:57 -0700
committerGitHub <noreply@github.com>2021-12-23 14:30:57 -0700
commit175942940f447b3fcfface3ee77197d292d898b7 (patch)
tree4ba58f76c18e8a2327b5261e46290feba3178855
parentc58205e94affb96887faf0ee780e89bb3328b065 (diff)
parenteb73beb4c475ea694a93158a86069e166a2696a1 (diff)
downloadwerkzeug-175942940f447b3fcfface3ee77197d292d898b7.tar.gz
Merge pull request #2306 from pallets:proxy-fallback-attr
fix proxy `__class__` and `__doc__` fallback
-rw-r--r--CHANGES.rst3
-rw-r--r--src/werkzeug/local.py30
-rw-r--r--tests/test_local.py15
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():