1from __future__ import annotations
2
3import functools
4import inspect
5from collections.abc import Callable
6from typing import Any
7
8
9@functools.lru_cache(maxsize=512)
10def _get_func_parameters(
11 func: Callable[..., Any], remove_first: bool
12) -> tuple[inspect.Parameter, ...]:
13 parameters = tuple(inspect.signature(func).parameters.values())
14 if remove_first:
15 parameters = parameters[1:]
16 return parameters
17
18
19def _get_callable_parameters(
20 meth_or_func: Callable[..., Any],
21) -> tuple[inspect.Parameter, ...]:
22 is_method = inspect.ismethod(meth_or_func)
23 func = meth_or_func.__func__ if is_method else meth_or_func
24 return _get_func_parameters(func, remove_first=is_method)
25
26
27def get_func_args(func: Callable[..., Any]) -> list[str]:
28 params = _get_callable_parameters(func)
29 return [
30 param.name
31 for param in params
32 if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
33 ]
34
35
36def func_accepts_kwargs(func: Callable[..., Any]) -> bool:
37 """Return True if function 'func' accepts keyword arguments **kwargs."""
38 return any(p for p in _get_callable_parameters(func) if p.kind == p.VAR_KEYWORD)
39
40
41def method_has_no_args(meth: Callable[..., Any]) -> bool:
42 """Return True if a method only accepts 'self'."""
43 count = len(
44 [p for p in _get_callable_parameters(meth) if p.kind == p.POSITIONAL_OR_KEYWORD]
45 )
46 return count == 0 if inspect.ismethod(meth) else count == 1