Coverage for C:\Python311\Lib\site-packages\persist_cache\helpers.py: 86%
36 statements
« prev ^ index » next coverage.py v7.3.2, created at 2024-03-14 21:04 +1100
« prev ^ index » next coverage.py v7.3.2, created at 2024-03-14 21:04 +1100
1import inspect
2import re
3from typing import Any, Callable, Union
6def signaturize(func: Callable) -> tuple[dict[str, Any], Union[str, None], Union[int, None], Union[str, None]]:
7 """Map the given function's arguments to their default values and also return the name and index of the args parameter if such a parameter exists."""
9 signature = {}
10 args_parameter = None
11 args_i = None
13 for i, parameter in enumerate(inspect.signature(func).parameters.values()):
14 # Skip the kwargs parameter.
15 if parameter.kind.name == 'VAR_KEYWORD':
16 continue
18 # If the parameter is the args parameter, record its name and index.
19 if parameter.kind.name == 'VAR_POSITIONAL':
20 args_parameter = parameter.name
21 args_i = i
23 # Set the parameter's default value if it has one, otherwise, use `None` instead of `inspect._empty`.
24 signature[parameter.name] = parameter.default if parameter.default != parameter.empty else None
26 return signature, args_parameter, args_i
28def inflate_arguments(signature: dict[str, Any], args_parameter: Union[str, None], args_i: Union[int, None], args: list, kwargs: dict) -> dict[str, Any]:
29 """Map arguments to their keywords or the keyword of the args parameter where necessary using the given mapping of a function's arguments to their default values and the name and index of the function's args parameter if such a parameter exists."""
31 # Copy the signature to avoid modifying the original.
32 arguments = signature.copy()
34 # Map positional arguments to their keywords by zipping the function's arguments with the provided positional arguments truncated to the index of the args parameter if such a parameter exists.
35 for argument, positional_argument in zip(arguments, args[:args_i]):
36 arguments[argument] = positional_argument
38 # If the args parameter exists, map the remaining positional arguments to the args parameter.
39 if args_parameter is not None:
40 arguments[args_parameter] = args[args_i:]
42 # Merge positional and keyword arguments with the function's arguments.
43 arguments |= kwargs
45 return arguments
47def is_async(func: Callable) -> bool:
48 """Determine whether a callable is asynchronous."""
50 # If `inspect.iscoroutinefunction` identifies the callable as asynchronous, then return `True`. If it doesn't, then try to search for a line that begins (ignoring preceeding whitespace) with `async def` followed by the callable's name and a `(` character inside its source code, returning `True` if such a line is found and there is no such line beginning with `def` (indicating that asynchronous and synchronous functions with the same name were defined in the same block as the callable).
51 if inspect.iscoroutinefunction(func):
52 return True
54 try:
55 source = inspect.getsource(func)
57 except (OSError, TypeError):
58 return False
60 has_async_def = re.search(r'^\s*async\s+def\s+' + re.escape(func.__name__) + r'\s*\(', source, re.MULTILINE)
62 if has_async_def:
63 has_sync_def = re.search(r'^\s*def\s+' + re.escape(func.__name__) + r'\s*\(', source, re.MULTILINE)
65 if not has_sync_def:
66 return True
68 return False