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

140 statements  

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. 

6 

7This replaces the need for the `adapter=` argument of `wrapt.decorator`, 

8which is planned for deprecation. 

9""" 

10 

11from inspect import CO_VARARGS, CO_VARKEYWORDS, Parameter, Signature 

12from inspect import signature as _inspect_signature 

13 

14from .__wrapt__ import ( 

15 BaseObjectProxy, 

16 BoundFunctionWrapper, 

17 CallableObjectProxy, 

18 FunctionWrapper, 

19) 

20from .doc import _BOUND_DOC_PROPERTY, _DOC_PROPERTY, _NO_DOC_OVERRIDE 

21 

22_MISSING = object() 

23 

24 

25def _cached(attr): 

26 """Read-only property that memoizes its derivation in a `_self_*` slot. 

27 

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('_')}" 

37 

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 

44 

45 return property(getter) 

46 

47 

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 

57 

58 

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 

67 

68 

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 

76 

77 

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) 

98 

99 

100class _SignatureCode(BaseObjectProxy): 

101 """Code-object proxy deriving argument-related co_* attrs from a Signature. 

102 

103 Non-argument bits (co_flags for coroutine/generator, co_filename, etc.) 

104 fall through to the wrapped function's real __code__. 

105 """ 

106 

107 def __init__(self, wrapped, signature): 

108 super().__init__(wrapped) 

109 self._self_signature = signature 

110 

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) 

135 

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") 

141 

142 

143class _SignatureMixin: 

144 """Shared Signature-derived introspection attrs for wrapper + surrogate.""" 

145 

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) 

157 

158 @property 

159 def __signature__(self): 

160 return self._self_signature 

161 

162 __annotations__ = _cached("__annotations__") 

163 __defaults__ = _cached("__defaults__") 

164 __kwdefaults__ = _cached("__kwdefaults__") 

165 __code__ = _cached("__code__") 

166 

167 

168class _SignatureFunctionSurrogate(_SignatureMixin, CallableObjectProxy): 

169 """Function surrogate exposing Signature-derived introspection attrs. 

170 

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 """ 

175 

176 def __init__(self, wrapped, signature): 

177 super().__init__(wrapped) 

178 self._self_signature = signature 

179 

180 

181class _BoundSignatureFunctionWrapper(BoundFunctionWrapper): 

182 __doc__ = _BOUND_DOC_PROPERTY 

183 

184 @property 

185 def __func__(self): 

186 return _SignatureFunctionSurrogate( 

187 self.__wrapped__.__func__, self._self_parent._self_signature 

188 ) 

189 

190 @property 

191 def __signature__(self): 

192 return self._self_parent._self_signature 

193 

194 

195class _SignatureFunctionWrapper(_SignatureMixin, FunctionWrapper): 

196 __doc__ = _DOC_PROPERTY 

197 

198 __bound_function_wrapper__ = _BoundSignatureFunctionWrapper 

199 

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 

204 

205 @property 

206 def __func__(self): 

207 return _SignatureFunctionSurrogate(self.__wrapped__, self._self_signature) 

208 

209 

210def with_signature( 

211 wrapped=None, /, *, prototype=None, signature=None, factory=None, doc=None 

212): 

213 """Override the signature of a wrapped callable. 

214 

215 Exactly one of `prototype`, `signature`, or `factory` must be supplied: 

216 

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. 

223 

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. 

227 

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 """ 

233 

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 ) 

243 

244 def _decorator(wrapped): 

245 def _wrapper(wrapped, instance, args, kwargs): 

246 return wrapped(*args, **kwargs) 

247 

248 resolved_doc = _NO_DOC_OVERRIDE if doc is None else doc 

249 

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 ) 

265 

266 return _SignatureFunctionWrapper(wrapped, _wrapper, resolved, resolved_doc) 

267 

268 if wrapped is None: 

269 return _decorator 

270 return _decorator(wrapped)