Coverage for C:\Python311\Lib\site-packages\persist_cache\helpers.py: 81%

36 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2024-05-06 20:49 +1000

1import inspect 

2import re 

3from typing import Any, Callable, Union 

4 

5 

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

8 

9 signature = {} 

10 args_parameter = None 

11 args_i = None 

12 

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 

17 

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 

22 

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 

25 

26 return signature, args_parameter, args_i 

27 

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

30 

31 # Copy the signature to avoid modifying the original. 

32 arguments = signature.copy() 

33 

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 

37 

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:] 

41 

42 # Merge positional and keyword arguments with the function's arguments. 

43 arguments |= kwargs 

44 

45 return arguments 

46 

47def is_async(func: Callable) -> bool: 

48 """Determine whether a callable is asynchronous.""" 

49 

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 

53 

54 try: 

55 source = inspect.getsource(func) 

56 

57 except (OSError, TypeError): 

58 return False 

59 

60 has_async_def = re.search(r'^\s*async\s+def\s+' + re.escape(func.__name__) + r'\s*\(', source, re.MULTILINE) 

61 

62 if has_async_def: 

63 has_sync_def = re.search(r'^\s*def\s+' + re.escape(func.__name__) + r'\s*\(', source, re.MULTILINE) 

64 

65 if not has_sync_def: 

66 return True 

67 

68 return False