Repository: pallets/werkzeug (python)

Issue:
doctest causes a working out of context RuntimeError with request
Migrating from Flask 2.1 to 2.2 failed a few unit tests of mine. It looks like we can no longer import `flask.request` top level in a test file (reproduced across Python 3.7, 3.8, 3.9, 3.10):

```python
from flask import Flask
from flask import request

def test_something():
    assert True
```

This gives a `RuntimeError`:

```
E   RuntimeError: Working outside of request context.
E
E   This typically means that you attempted to use functionality that needed
E   an active HTTP request. Consult the documentation on testing for
E   information about how to avoid this problem.
```

On my side the fix was simple: importing `flask.request` outside of the global scope, just when I actually need it (my actual tests create small Flask apps to test a client).

Edit: forgot to mention that this only occurs when pytest is run with `--doctest-modules`

Pull request:
`__wrapped__` is always set on unbound local proxy
Doctest calls `inspect.unwrap`, which does `getattr(obj, "__wrapped__")`, which would fail if the proxy was anything but a function. In Flask 2.2, the proxies no longer use functions, so users started reporting that their doctests were failing.

Now the proxy always records what it's wrapping, and returns that when accessing `__wrapped__` when unbound. When bound, the attribute access is always passed through to the real object, rather than always returning the proxy's value.

fixes #2485 

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index bac8a7af..f8a93e51 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 16e3ce0d..70e9bf72 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 @@ class LocalProxy(t.Generic[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>"


Test patch:
diff --git a/tests/test_local.py b/tests/test_local.py
index ba66f566..2af69d2d 100644
--- a/tests/test_local.py
+++ b/tests/test_local.py
@@ -4,7 +4,6 @@ import math
 import operator
 import time
 from contextvars import ContextVar
-from functools import partial
 from threading import Thread
 
 import pytest
@@ -15,6 +14,7 @@ from werkzeug import local
 # 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():