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)
21_MISSING = object()
24def _cached(attr):
25 """Read-only property that memoizes its derivation in a `_self_*` slot.
27 `functools.cached_property` is unusable on ObjectProxy subclasses: wrapt
28 overrides `__dict__` with a property returning the wrapped object's dict,
29 so cached_property's `instance.__dict__[name] = value` would mutate the
30 wrapped function. Storing under a `_self_*` name routes through wrapt's
31 own setattr handling and lands in the proxy's real instance dict. Using
32 a `@property` (data descriptor) also ensures we win over any value
33 copied into the instance dict by `FunctionWrapper.__init__`.
34 """
35 slot = f"_self_cached_{attr.lstrip('_')}"
37 def getter(self):
38 value = getattr(self, slot, _MISSING)
39 if value is _MISSING:
40 value = self._derive(attr)
41 self.__self_setattr__(slot, value)
42 return value
44 return property(getter)
47def _derive_annotations(sig):
48 ann = {
49 p.name: p.annotation
50 for p in sig.parameters.values()
51 if p.annotation is not Parameter.empty
52 }
53 if sig.return_annotation is not Signature.empty:
54 ann["return"] = sig.return_annotation
55 return ann
58def _derive_defaults(sig):
59 defaults = tuple(
60 p.default
61 for p in sig.parameters.values()
62 if p.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD)
63 and p.default is not Parameter.empty
64 )
65 return defaults or None
68def _derive_kwdefaults(sig):
69 kwdefaults = {
70 p.name: p.default
71 for p in sig.parameters.values()
72 if p.kind is Parameter.KEYWORD_ONLY and p.default is not Parameter.empty
73 }
74 return kwdefaults or None
77def _derive_varnames(sig):
78 pos_only, pos_or_kw, kw_only = [], [], []
79 var_pos = var_kw = None
80 for p in sig.parameters.values():
81 if p.kind is Parameter.POSITIONAL_ONLY:
82 pos_only.append(p.name)
83 elif p.kind is Parameter.POSITIONAL_OR_KEYWORD:
84 pos_or_kw.append(p.name)
85 elif p.kind is Parameter.VAR_POSITIONAL:
86 var_pos = p.name
87 elif p.kind is Parameter.KEYWORD_ONLY:
88 kw_only.append(p.name)
89 elif p.kind is Parameter.VAR_KEYWORD:
90 var_kw = p.name
91 names = pos_only + pos_or_kw + kw_only
92 if var_pos:
93 names.append(var_pos)
94 if var_kw:
95 names.append(var_kw)
96 return tuple(names)
99class _SignatureCode(BaseObjectProxy):
100 """Code-object proxy deriving argument-related co_* attrs from a Signature.
102 Non-argument bits (co_flags for coroutine/generator, co_filename, etc.)
103 fall through to the wrapped function's real __code__.
104 """
106 def __init__(self, wrapped, signature):
107 super().__init__(wrapped)
108 self._self_signature = signature
110 def _derive(self, attr):
111 sig = self._self_signature
112 if attr == "co_argcount":
113 kinds = (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD)
114 return sum(p.kind in kinds for p in sig.parameters.values())
115 if attr == "co_posonlyargcount":
116 return sum(
117 p.kind is Parameter.POSITIONAL_ONLY for p in sig.parameters.values()
118 )
119 if attr == "co_kwonlyargcount":
120 return sum(
121 p.kind is Parameter.KEYWORD_ONLY for p in sig.parameters.values()
122 )
123 if attr == "co_varnames":
124 return _derive_varnames(sig)
125 if attr == "co_flags":
126 kinds = {p.kind for p in sig.parameters.values()}
127 flags = self.__wrapped__.co_flags & ~(CO_VARARGS | CO_VARKEYWORDS)
128 if Parameter.VAR_POSITIONAL in kinds:
129 flags |= CO_VARARGS
130 if Parameter.VAR_KEYWORD in kinds:
131 flags |= CO_VARKEYWORDS
132 return flags
133 raise AttributeError(attr)
135 co_argcount = _cached("co_argcount")
136 co_posonlyargcount = _cached("co_posonlyargcount")
137 co_kwonlyargcount = _cached("co_kwonlyargcount")
138 co_varnames = _cached("co_varnames")
139 co_flags = _cached("co_flags")
142class _SignatureMixin:
143 """Shared Signature-derived introspection attrs for wrapper + surrogate."""
145 def _derive(self, attr):
146 sig = self._self_signature
147 if attr == "__annotations__":
148 return _derive_annotations(sig)
149 if attr == "__defaults__":
150 return _derive_defaults(sig)
151 if attr == "__kwdefaults__":
152 return _derive_kwdefaults(sig)
153 if attr == "__code__":
154 return _SignatureCode(self.__wrapped__.__code__, sig)
155 raise AttributeError(attr)
157 @property
158 def __signature__(self):
159 return self._self_signature
161 __annotations__ = _cached("__annotations__")
162 __defaults__ = _cached("__defaults__")
163 __kwdefaults__ = _cached("__kwdefaults__")
164 __code__ = _cached("__code__")
167class _SignatureFunctionSurrogate(_SignatureMixin, CallableObjectProxy):
168 """Function surrogate exposing Signature-derived introspection attrs.
170 Used as __func__ of a bound wrapper so that inspect.signature -- which
171 treats the bound wrapper as a MethodType and consults __func__ -- sees
172 our override and strips self/cls correctly.
173 """
175 def __init__(self, wrapped, signature):
176 super().__init__(wrapped)
177 self._self_signature = signature
180class _BoundSignatureFunctionWrapper(BoundFunctionWrapper):
181 @property
182 def __func__(self):
183 return _SignatureFunctionSurrogate(
184 self.__wrapped__.__func__, self._self_parent._self_signature
185 )
187 @property
188 def __signature__(self):
189 return self._self_parent._self_signature
192class _SignatureFunctionWrapper(_SignatureMixin, FunctionWrapper):
193 __bound_function_wrapper__ = _BoundSignatureFunctionWrapper
195 def __init__(self, wrapped, wrapper, signature):
196 super().__init__(wrapped, wrapper)
197 self._self_signature = signature
199 @property
200 def __func__(self):
201 return _SignatureFunctionSurrogate(self.__wrapped__, self._self_signature)
204def with_signature(wrapped=None, /, *, prototype=None, signature=None, factory=None):
205 """Override the signature of a wrapped callable.
207 Exactly one of `prototype`, `signature`, or `factory` must be supplied:
209 - `prototype`: a callable whose signature will be used.
210 - `signature`: a prebuilt `inspect.Signature` object.
211 - `factory`: a callable `factory(wrapped)` invoked at decoration time
212 that returns either a `Signature` or a prototype callable.
214 The resulting wrapper exposes the override via `__signature__`, and
215 derives `__annotations__`, `__defaults__`, `__kwdefaults__`, and the
216 argument-related attributes of `__code__` from the same source. The
217 wrapped function is not mutated. Calling behaviour is unchanged.
218 """
220 specified = sum(x is not None for x in (prototype, signature, factory))
221 if specified == 0:
222 raise TypeError(
223 "with_signature requires one of prototype=, signature=, or factory="
224 )
225 if specified > 1:
226 raise TypeError(
227 "with_signature accepts only one of prototype=, signature=, or factory="
228 )
230 def _decorator(wrapped):
231 def _wrapper(wrapped, instance, args, kwargs):
232 return wrapped(*args, **kwargs)
234 if signature is not None:
235 resolved = signature
236 elif prototype is not None:
237 resolved = _inspect_signature(prototype)
238 else:
239 produced = factory(wrapped)
240 resolved = (
241 produced
242 if isinstance(produced, Signature)
243 else _inspect_signature(produced)
244 )
246 return _SignatureFunctionWrapper(wrapped, _wrapper, resolved)
248 if wrapped is None:
249 return _decorator
250 return _decorator(wrapped)