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

__wrapped__ is always set on unbound local proxy #2497

Merged
merged 1 commit into from
Aug 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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