(fn: _Fn)
| 264 | """A signature-matching decorator factory.""" |
| 265 | |
| 266 | def decorate(fn: _Fn) -> _Fn: |
| 267 | if not inspect.isfunction(fn) and not inspect.ismethod(fn): |
| 268 | raise Exception("not a decoratable function") |
| 269 | |
| 270 | # Python 3.14 defer creating __annotations__ until its used. |
| 271 | # We do not want to create __annotations__ now. |
| 272 | annofunc = getattr(fn, "__annotate__", None) |
| 273 | if annofunc is not None: |
| 274 | fn.__annotate__ = None # type: ignore[union-attr] |
| 275 | try: |
| 276 | spec = compat.inspect_getfullargspec(fn) |
| 277 | finally: |
| 278 | fn.__annotate__ = annofunc # type: ignore[union-attr] |
| 279 | else: |
| 280 | spec = compat.inspect_getfullargspec(fn) |
| 281 | |
| 282 | # Do not generate code for annotations. |
| 283 | # update_wrapper() copies the annotation from fn to decorated. |
| 284 | # We use dummy defaults for code generation to avoid having |
| 285 | # copy of large globals for compiling. |
| 286 | # We copy __defaults__ and __kwdefaults__ from fn to decorated. |
| 287 | empty_defaults = (None,) * len(spec.defaults or ()) |
| 288 | empty_kwdefaults = dict.fromkeys(spec.kwonlydefaults or ()) |
| 289 | spec = spec._replace( |
| 290 | annotations={}, |
| 291 | defaults=empty_defaults, |
| 292 | kwonlydefaults=empty_kwdefaults, |
| 293 | ) |
| 294 | |
| 295 | names = ( |
| 296 | tuple(cast("Tuple[str, ...]", spec[0])) |
| 297 | + cast("Tuple[str, ...]", spec[1:3]) |
| 298 | + (fn.__name__,) |
| 299 | ) |
| 300 | targ_name, fn_name = _unique_symbols(names, "target", "fn") |
| 301 | |
| 302 | metadata: Dict[str, Optional[str]] = dict(target=targ_name, fn=fn_name) |
| 303 | metadata.update(format_argspec_plus(spec, grouped=False)) |
| 304 | metadata["name"] = fn.__name__ |
| 305 | |
| 306 | if inspect.iscoroutinefunction(fn): |
| 307 | metadata["prefix"] = "async " |
| 308 | metadata["target_prefix"] = "await " |
| 309 | else: |
| 310 | metadata["prefix"] = "" |
| 311 | metadata["target_prefix"] = "" |
| 312 | |
| 313 | # look for __ positional arguments. This is a convention in |
| 314 | # SQLAlchemy that arguments should be passed positionally |
| 315 | # rather than as keyword |
| 316 | # arguments. note that apply_pos doesn't currently work in all cases |
| 317 | # such as when a kw-only indicator "*" is present, which is why |
| 318 | # we limit the use of this to just that case we can detect. As we add |
| 319 | # more kinds of methods that use @decorator, things may have to |
| 320 | # be further improved in this area |
| 321 | if "__" in repr(spec[0]): |
| 322 | code = """\ |
| 323 | %(prefix)sdef %(name)s%(grouped_args)s: |
nothing calls this directly
no test coverage detected