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

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

20 

21_MISSING = object() 

22 

23 

24def _cached(attr): 

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

26 

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

36 

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 

43 

44 return property(getter) 

45 

46 

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 

56 

57 

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 

66 

67 

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 

75 

76 

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) 

97 

98 

99class _SignatureCode(BaseObjectProxy): 

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

101 

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

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

104 """ 

105 

106 def __init__(self, wrapped, signature): 

107 super().__init__(wrapped) 

108 self._self_signature = signature 

109 

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) 

134 

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

140 

141 

142class _SignatureMixin: 

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

144 

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) 

156 

157 @property 

158 def __signature__(self): 

159 return self._self_signature 

160 

161 __annotations__ = _cached("__annotations__") 

162 __defaults__ = _cached("__defaults__") 

163 __kwdefaults__ = _cached("__kwdefaults__") 

164 __code__ = _cached("__code__") 

165 

166 

167class _SignatureFunctionSurrogate(_SignatureMixin, CallableObjectProxy): 

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

169 

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

174 

175 def __init__(self, wrapped, signature): 

176 super().__init__(wrapped) 

177 self._self_signature = signature 

178 

179 

180class _BoundSignatureFunctionWrapper(BoundFunctionWrapper): 

181 @property 

182 def __func__(self): 

183 return _SignatureFunctionSurrogate( 

184 self.__wrapped__.__func__, self._self_parent._self_signature 

185 ) 

186 

187 @property 

188 def __signature__(self): 

189 return self._self_parent._self_signature 

190 

191 

192class _SignatureFunctionWrapper(_SignatureMixin, FunctionWrapper): 

193 __bound_function_wrapper__ = _BoundSignatureFunctionWrapper 

194 

195 def __init__(self, wrapped, wrapper, signature): 

196 super().__init__(wrapped, wrapper) 

197 self._self_signature = signature 

198 

199 @property 

200 def __func__(self): 

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

202 

203 

204def with_signature(wrapped=None, /, *, prototype=None, signature=None, factory=None): 

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

206 

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

208 

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. 

213 

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

219 

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 ) 

229 

230 def _decorator(wrapped): 

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

232 return wrapped(*args, **kwargs) 

233 

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 ) 

245 

246 return _SignatureFunctionWrapper(wrapped, _wrapper, resolved) 

247 

248 if wrapped is None: 

249 return _decorator 

250 return _decorator(wrapped)