Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wrapt/signature.py: 32%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""The `with_signature` decorator: override the signature (and related
2introspection attributes) of a wrapped callable without mutating the wrapped
3function itself. Accepts a prototype callable, a prebuilt `inspect.Signature`,
4or a factory that derives a signature from the wrapped function at decoration
5time.
7This replaces the need for the `adapter=` argument of `wrapt.decorator`,
8which is planned for deprecation.
9"""
11from inspect import CO_VARARGS, CO_VARKEYWORDS, Parameter, Signature
12from inspect import signature as _inspect_signature
14from .__wrapt__ import (
15 BaseObjectProxy,
16 BoundFunctionWrapper,
17 CallableObjectProxy,
18 FunctionWrapper,
19)
20from .doc import _BOUND_DOC_PROPERTY, _DOC_PROPERTY, _NO_DOC_OVERRIDE
22_MISSING = object()
25def _cached(attr):
26 """Read-only property that memoizes its derivation in a `_self_*` slot.
28 `functools.cached_property` is unusable on ObjectProxy subclasses: wrapt
29 overrides `__dict__` with a property returning the wrapped object's dict,
30 so cached_property's `instance.__dict__[name] = value` would mutate the
31 wrapped function. Storing under a `_self_*` name routes through wrapt's
32 own setattr handling and lands in the proxy's real instance dict. Using
33 a `@property` (data descriptor) also ensures we win over any value
34 copied into the instance dict by `FunctionWrapper.__init__`.
35 """
36 slot = f"_self_cached_{attr.lstrip('_')}"
38 def getter(self):
39 value = getattr(self, slot, _MISSING)
40 if value is _MISSING:
41 value = self._derive(attr)
42 self.__self_setattr__(slot, value)
43 return value
45 return property(getter)
48def _derive_annotations(sig):
49 ann = {
50 p.name: p.annotation
51 for p in sig.parameters.values()
52 if p.annotation is not Parameter.empty
53 }
54 if sig.return_annotation is not Signature.empty:
55 ann["return"] = sig.return_annotation
56 return ann
59def _derive_defaults(sig):
60 defaults = tuple(
61 p.default
62 for p in sig.parameters.values()
63 if p.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD)
64 and p.default is not Parameter.empty
65 )
66 return defaults or None
69def _derive_kwdefaults(sig):
70 kwdefaults = {
71 p.name: p.default
72 for p in sig.parameters.values()
73 if p.kind is Parameter.KEYWORD_ONLY and p.default is not Parameter.empty
74 }
75 return kwdefaults or None
78def _derive_varnames(sig):
79 pos_only, pos_or_kw, kw_only = [], [], []
80 var_pos = var_kw = None
81 for p in sig.parameters.values():
82 if p.kind is Parameter.POSITIONAL_ONLY:
83 pos_only.append(p.name)
84 elif p.kind is Parameter.POSITIONAL_OR_KEYWORD:
85 pos_or_kw.append(p.name)
86 elif p.kind is Parameter.VAR_POSITIONAL:
87 var_pos = p.name
88 elif p.kind is Parameter.KEYWORD_ONLY:
89 kw_only.append(p.name)
90 elif p.kind is Parameter.VAR_KEYWORD:
91 var_kw = p.name
92 names = pos_only + pos_or_kw + kw_only
93 if var_pos:
94 names.append(var_pos)
95 if var_kw:
96 names.append(var_kw)
97 return tuple(names)
100class _SignatureCode(BaseObjectProxy):
101 """Code-object proxy deriving argument-related co_* attrs from a Signature.
103 Non-argument bits (co_flags for coroutine/generator, co_filename, etc.)
104 fall through to the wrapped function's real __code__.
105 """
107 def __init__(self, wrapped, signature):
108 super().__init__(wrapped)
109 self._self_signature = signature
111 def _derive(self, attr):
112 sig = self._self_signature
113 if attr == "co_argcount":
114 kinds = (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD)
115 return sum(p.kind in kinds for p in sig.parameters.values())
116 if attr == "co_posonlyargcount":
117 return sum(
118 p.kind is Parameter.POSITIONAL_ONLY for p in sig.parameters.values()
119 )
120 if attr == "co_kwonlyargcount":
121 return sum(
122 p.kind is Parameter.KEYWORD_ONLY for p in sig.parameters.values()
123 )
124 if attr == "co_varnames":
125 return _derive_varnames(sig)
126 if attr == "co_flags":
127 kinds = {p.kind for p in sig.parameters.values()}
128 flags = self.__wrapped__.co_flags & ~(CO_VARARGS | CO_VARKEYWORDS)
129 if Parameter.VAR_POSITIONAL in kinds:
130 flags |= CO_VARARGS
131 if Parameter.VAR_KEYWORD in kinds:
132 flags |= CO_VARKEYWORDS
133 return flags
134 raise AttributeError(attr)
136 co_argcount = _cached("co_argcount")
137 co_posonlyargcount = _cached("co_posonlyargcount")
138 co_kwonlyargcount = _cached("co_kwonlyargcount")
139 co_varnames = _cached("co_varnames")
140 co_flags = _cached("co_flags")
143class _SignatureMixin:
144 """Shared Signature-derived introspection attrs for wrapper + surrogate."""
146 def _derive(self, attr):
147 sig = self._self_signature
148 if attr == "__annotations__":
149 return _derive_annotations(sig)
150 if attr == "__defaults__":
151 return _derive_defaults(sig)
152 if attr == "__kwdefaults__":
153 return _derive_kwdefaults(sig)
154 if attr == "__code__":
155 return _SignatureCode(self.__wrapped__.__code__, sig)
156 raise AttributeError(attr)
158 @property
159 def __signature__(self):
160 return self._self_signature
162 __annotations__ = _cached("__annotations__")
163 __defaults__ = _cached("__defaults__")
164 __kwdefaults__ = _cached("__kwdefaults__")
165 __code__ = _cached("__code__")
168class _SignatureFunctionSurrogate(_SignatureMixin, CallableObjectProxy):
169 """Function surrogate exposing Signature-derived introspection attrs.
171 Used as __func__ of a bound wrapper so that inspect.signature -- which
172 treats the bound wrapper as a MethodType and consults __func__ -- sees
173 our override and strips self/cls correctly.
174 """
176 def __init__(self, wrapped, signature):
177 super().__init__(wrapped)
178 self._self_signature = signature
181class _BoundSignatureFunctionWrapper(BoundFunctionWrapper):
182 __doc__ = _BOUND_DOC_PROPERTY
184 @property
185 def __func__(self):
186 return _SignatureFunctionSurrogate(
187 self.__wrapped__.__func__, self._self_parent._self_signature
188 )
190 @property
191 def __signature__(self):
192 return self._self_parent._self_signature
195class _SignatureFunctionWrapper(_SignatureMixin, FunctionWrapper):
196 __doc__ = _DOC_PROPERTY
198 __bound_function_wrapper__ = _BoundSignatureFunctionWrapper
200 def __init__(self, wrapped, wrapper, signature, doc=_NO_DOC_OVERRIDE):
201 super().__init__(wrapped, wrapper)
202 self._self_signature = signature
203 self._self_doc = doc
205 @property
206 def __func__(self):
207 return _SignatureFunctionSurrogate(self.__wrapped__, self._self_signature)
210def with_signature(
211 wrapped=None, /, *, prototype=None, signature=None, factory=None, doc=None
212):
213 """Override the signature of a wrapped callable.
215 Exactly one of `prototype`, `signature`, or `factory` must be supplied:
217 - `prototype`: a callable whose signature will be used.
218 - `signature`: a prebuilt `inspect.Signature` object.
219 - `factory`: a callable `factory(wrapped)` invoked at decoration time
220 that returns either a `Signature` or a prototype callable, or a
221 tuple of `(signature_or_prototype, docstring)` to override the
222 docstring at the same time.
224 The docstring may also be overridden by supplying `doc`, in which case
225 a docstring from the factory is ignored. When neither is supplied the
226 wrapper reports the docstring of the wrapped function as normal.
228 The resulting wrapper exposes the override via `__signature__`, and
229 derives `__annotations__`, `__defaults__`, `__kwdefaults__`, and the
230 argument-related attributes of `__code__` from the same source. The
231 wrapped function is not mutated. Calling behaviour is unchanged.
232 """
234 specified = sum(x is not None for x in (prototype, signature, factory))
235 if specified == 0:
236 raise TypeError(
237 "with_signature requires one of prototype=, signature=, or factory="
238 )
239 if specified > 1:
240 raise TypeError(
241 "with_signature accepts only one of prototype=, signature=, or factory="
242 )
244 def _decorator(wrapped):
245 def _wrapper(wrapped, instance, args, kwargs):
246 return wrapped(*args, **kwargs)
248 resolved_doc = _NO_DOC_OVERRIDE if doc is None else doc
250 if signature is not None:
251 resolved = signature
252 elif prototype is not None:
253 resolved = _inspect_signature(prototype)
254 else:
255 produced = factory(wrapped)
256 if isinstance(produced, tuple):
257 produced, produced_doc = produced
258 if doc is None:
259 resolved_doc = produced_doc
260 resolved = (
261 produced
262 if isinstance(produced, Signature)
263 else _inspect_signature(produced)
264 )
266 return _SignatureFunctionWrapper(wrapped, _wrapper, resolved, resolved_doc)
268 if wrapped is None:
269 return _decorator
270 return _decorator(wrapped)