Skip to content

Commit

Permalink
Merge pull request #2497 from pallets/local-wrapped
Browse files Browse the repository at this point in the history
`__wrapped__` is always set on unbound local proxy
  • Loading branch information
davidism authored Aug 8, 2022
2 parents fe8a56e + 82d3fba commit bebf46d
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 13 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 9 additions & 3 deletions src/werkzeug/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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>"
Expand Down
19 changes: 9 additions & 10 deletions tests/test_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import operator
import time
from contextvars import ContextVar
from functools import partial
from threading import Thread

import pytest
Expand All @@ -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)
Expand Down Expand Up @@ -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():
Expand Down

0 comments on commit bebf46d

Please sign in to comment.