From 82d3fba4e7955e00833eff123fd11564fc96cdae Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 8 Aug 2022 13:43:49 -0700 Subject: [PATCH] LocalProxy.__wrapped__ is always set when unbound --- CHANGES.rst | 3 +++ src/werkzeug/local.py | 12 +++++++++--- tests/test_local.py | 19 +++++++++---------- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index bac8a7aff..f8a93e512 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -16,6 +16,9 @@ Unreleased - Parsing of some invalid header characters is more robust. :pr:`2494` - When starting the development server, a warning not to use it in a production deployment is always shown. :issue:`2480` +- ``LocalProxy.__wrapped__`` is always set to the wrapped object when + the proxy is unbound, fixing an issue in doctest that would cause it + to fail. :issue:`2485` Version 2.2.1 diff --git a/src/werkzeug/local.py b/src/werkzeug/local.py index 16e3ce0d1..70e9bf72f 100644 --- a/src/werkzeug/local.py +++ b/src/werkzeug/local.py @@ -435,6 +435,10 @@ class LocalProxy(t.Generic[T]): isinstance(user, User) # True issubclass(type(user), LocalProxy) # True + .. versionchanged:: 2.2.2 + ``__wrapped__`` is set when wrapping an object, not only when + wrapping a function, to prevent doctest from failing. + .. versionchanged:: 2.2 Can proxy a ``ContextVar`` or ``LocalStack`` directly. @@ -453,7 +457,7 @@ class LocalProxy(t.Generic[T]): The class can be instantiated with a callable. """ - __slots__ = ("__wrapped__", "_get_current_object") + __slots__ = ("__wrapped", "_get_current_object") _get_current_object: t.Callable[[], T] """Return the current object this proxy is bound to. If the proxy is @@ -515,16 +519,18 @@ def _get_current_object() -> T: def _get_current_object() -> T: return get_name(local()) # type: ignore - object.__setattr__(self, "__wrapped__", local) - else: raise TypeError(f"Don't know how to proxy '{type(local)}'.") + object.__setattr__(self, "_LocalProxy__wrapped", local) object.__setattr__(self, "_get_current_object", _get_current_object) __doc__ = _ProxyLookup( # type: ignore class_value=__doc__, fallback=lambda self: type(self).__doc__, is_attr=True ) + __wrapped__ = _ProxyLookup( + fallback=lambda self: self._LocalProxy__wrapped, is_attr=True + ) # __del__ should only delete the proxy __repr__ = _ProxyLookup( # type: ignore repr, fallback=lambda self: f"<{type(self).__name__} unbound>" diff --git a/tests/test_local.py b/tests/test_local.py index ba66f566d..2af69d2d6 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -4,7 +4,6 @@ import operator import time from contextvars import ContextVar -from functools import partial from threading import Thread import pytest @@ -15,6 +14,7 @@ # to avoid accumulating anonymous context vars that can't be collected. _cv_ns = ContextVar("werkzeug.tests.ns") _cv_stack = ContextVar("werkzeug.tests.stack") +_cv_val = ContextVar("werkzeug.tests.val") @pytest.fixture(autouse=True) @@ -165,22 +165,21 @@ def test_proxy_wrapped(): class SomeClassWithWrapped: __wrapped__ = "wrapped" - def lookup_func(): - return 42 + proxy = local.LocalProxy(_cv_val) + assert proxy.__wrapped__ is _cv_val + _cv_val.set(42) - proxy = local.LocalProxy(lookup_func) - assert proxy.__wrapped__ is lookup_func - - partial_lookup_func = partial(lookup_func) - partial_proxy = local.LocalProxy(partial_lookup_func) - assert partial_proxy.__wrapped__ == partial_lookup_func + with pytest.raises(AttributeError): + proxy.__wrapped__ ns = local.Local(_cv_ns) ns.foo = SomeClassWithWrapped() ns.bar = 42 assert ns("foo").__wrapped__ == "wrapped" - pytest.raises(AttributeError, lambda: ns("bar").__wrapped__) + + with pytest.raises(AttributeError): + ns("bar").__wrapped__ def test_proxy_doc():