@@ -253,3 +253,139 @@ constructor of the class at the point it it is being created.
253253
254254 @ClassDecorator("string") # <-- Invalid error warning.
255255 def function(): ...
256+
257+ Object Proxies
258+ --------------
259+
260+ In addition to the decorator factories, the **wrapt ** module exposes a family
261+ of object proxy classes that can be used to build custom wrappers for
262+ arbitrary objects. The core classes are ``wrapt.BaseObjectProxy ``,
263+ ``wrapt.ObjectProxy ``, ``wrapt.AutoObjectProxy ``, ``wrapt.LazyObjectProxy ``
264+ and ``wrapt.CallableObjectProxy ``. Each is generic on a single type
265+ parameter that stands for the type of the wrapped object.
266+
267+ ``wrapt.BaseObjectProxy `` is the recommended base for custom proxy
268+ subclasses; ``wrapt.ObjectProxy `` is retained for backward compatibility
269+ and is a thin subclass of ``BaseObjectProxy ``. The discussion below applies
270+ equally to both.
271+
272+ Annotating a proxy with the type of its wrapped value lets the type checker
273+ reason about ``proxy.__wrapped__ ``:
274+
275+ ::
276+
277+ import wrapt
278+
279+ proxy: wrapt.ObjectProxy[int] = wrapt.ObjectProxy(5)
280+
281+ value: int = proxy.__wrapped__
282+
283+ Subclassing a proxy with a specific wrapped type is also supported, and is
284+ the idiomatic way to define a proxy for a particular kind of object:
285+
286+ ::
287+
288+ from io import TextIOWrapper
289+ from typing import Any
290+
291+ class FileProxy(wrapt.ObjectProxy[TextIOWrapper[Any]]):
292+ pass
293+
294+ fp = FileProxy(open("/path/to/file"))
295+
296+ wrapped_file: TextIOWrapper[Any] = fp.__wrapped__
297+
298+ What the type parameter does and does not propagate
299+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
300+
301+ The type parameter only affects the ``__wrapped__ `` attribute directly.
302+ Most other access through the proxy is intentionally typed as ``Any ``,
303+ because the runtime forwarding depends on the wrapped object's interface,
304+ which the stubs cannot claim statically without sacrificing flexibility:
305+
306+ * Attribute access via ``proxy.foo `` returns ``Any ``, since the proxy's
307+ ``__getattr__ `` forwards to the wrapped object.
308+ * Arithmetic and bitwise operators such as ``proxy + 1 `` return ``Any ``
309+ because the result type depends on the wrapped value's implementation.
310+ * Container operations (``proxy[key] ``, ``len(proxy) ``, ...) return
311+ permissive types.
312+ * Context-manager usage ``with proxy as v: `` binds ``v `` as ``Any ``,
313+ because the wrapped object's ``__enter__ `` can return a value of any
314+ type (for example, ``threading.Lock.__enter__ `` returns ``bool ``, not
315+ the lock itself).
316+
317+ If you need a statically typed view of a specific attribute or operation,
318+ access the underlying value via ``proxy.__wrapped__ `` where it is typed as
319+ the wrapped type, or assign the result of an expression to a variable with
320+ an explicit annotation:
321+
322+ ::
323+
324+ proxy: wrapt.ObjectProxy[int] = wrapt.ObjectProxy(5)
325+
326+ bits: int = proxy.__wrapped__.bit_length() # Inferred as int.
327+
328+ Using a proxy class without a type parameter
329+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
330+
331+ Writing ``wrapt.ObjectProxy `` on its own as a type annotation, without a
332+ ``[T] `` parameter, is equivalent to ``wrapt.ObjectProxy[Any] `` from the type
333+ checker's perspective. It is valid code, but the wrapped object's type is
334+ unknown and ``proxy.__wrapped__ `` is typed as ``Any `` rather than a specific
335+ type:
336+
337+ ::
338+
339+ proxy: wrapt.ObjectProxy = wrapt.ObjectProxy(5) # ObjectProxy[Any].
340+
341+ value = proxy.__wrapped__ # Any.
342+
343+ Strict type checker modes (for example ``mypy --strict ``) flag the
344+ unparameterised form as missing type parameters, and require you to write
345+ ``wrapt.ObjectProxy[Any] `` explicitly if that is what you intend. Under
346+ default settings the two forms are interchangeable for type checking
347+ purposes, but subscripting with a concrete type is preferred whenever the
348+ wrapped type is known.
349+
350+ Function Wrappers
351+ -----------------
352+
353+ ``wrapt.FunctionWrapper `` and ``wrapt.BoundFunctionWrapper `` are the runtime
354+ types produced by ``@wrapt.decorator `` and ``@wrapt.function_wrapper ``. Both
355+ are generic on two parameters: a ``ParamSpec `` representing the wrapped
356+ callable's parameter signature, and a ``TypeVar `` representing its return
357+ type.
358+
359+ In most cases you do not need to annotate a function wrapper at all. The
360+ decorator machinery infers the type parameters from the signature of the
361+ wrapped function, so a decorated function continues to appear to the type
362+ checker as a callable with the same arguments and return type:
363+
364+ ::
365+
366+ @pass_through
367+ def add(a: int, b: int) -> int:
368+ return a + b
369+
370+ # Inferred as wrapt.FunctionWrapper[[int, int], int].
371+
372+ If you want to store a reference to a decorated function in a container,
373+ pass it to another function, or otherwise name the type explicitly, the
374+ subscripted form can be used:
375+
376+ ::
377+
378+ wrapped_add: wrapt.FunctionWrapper[[int, int], int] = add
379+
380+ As with object proxies, writing ``wrapt.FunctionWrapper `` on its own is
381+ equivalent to ``wrapt.FunctionWrapper[Any, Any] ``: a callable of any
382+ signature returning any type. The idiomatic spelling for "any signature" is
383+ ``wrapt.FunctionWrapper[..., Any] ``, using ``... `` in the parameter-spec
384+ position. Strict type checker modes will ask for explicit parameters in
385+ either case.
386+
387+ ``wrapt.BoundFunctionWrapper `` is the type produced when a
388+ ``FunctionWrapper `` is accessed via the descriptor protocol on an instance
389+ or class (for example, a decorated method accessed through ``self ``). You
390+ rarely need to name this type directly; it is produced automatically and
391+ flows through type inference.
0 commit comments