File: inspect.py

package info (click to toggle)
python-django 3%3A6.0.1-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 62,208 kB
  • sloc: python: 371,136; javascript: 19,376; xml: 211; makefile: 187; sh: 28
file content (132 lines) | stat: -rw-r--r-- 3,918 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import functools
import inspect
import threading
from contextlib import contextmanager

from django.utils.version import PY314

if PY314:
    import annotationlib

    lock = threading.Lock()
    safe_signature_from_callable = functools.partial(
        inspect._signature_from_callable,
        annotation_format=annotationlib.Format.FORWARDREF,
    )


@functools.lru_cache(maxsize=512)
def _get_func_parameters(func, remove_first):
    # As the annotations are not used in any case, inspect the signature with
    # FORWARDREF to leave any deferred annotations unevaluated.
    if PY314:
        signature = inspect.signature(
            func, annotation_format=annotationlib.Format.FORWARDREF
        )
    else:
        signature = inspect.signature(func)

    parameters = tuple(signature.parameters.values())
    if remove_first:
        parameters = parameters[1:]
    return parameters


def _get_callable_parameters(meth_or_func):
    is_method = inspect.ismethod(meth_or_func)
    func = meth_or_func.__func__ if is_method else meth_or_func
    return _get_func_parameters(func, remove_first=is_method)


ARG_KINDS = frozenset(
    {
        inspect.Parameter.POSITIONAL_ONLY,
        inspect.Parameter.KEYWORD_ONLY,
        inspect.Parameter.POSITIONAL_OR_KEYWORD,
    }
)


def get_func_args(func):
    params = _get_callable_parameters(func)
    return [param.name for param in params if param.kind in ARG_KINDS]


def get_func_full_args(func):
    """
    Return a list of (argument name, default value) tuples. If the argument
    does not have a default value, omit it in the tuple. Arguments such as
    *args and **kwargs are also included.
    """
    params = _get_callable_parameters(func)
    args = []
    for param in params:
        name = param.name
        # Ignore 'self'
        if name == "self":
            continue
        if param.kind == inspect.Parameter.VAR_POSITIONAL:
            name = "*" + name
        elif param.kind == inspect.Parameter.VAR_KEYWORD:
            name = "**" + name
        if param.default != inspect.Parameter.empty:
            args.append((name, param.default))
        else:
            args.append((name,))
    return args


def func_accepts_kwargs(func):
    """Return True if function 'func' accepts keyword arguments **kwargs."""
    return any(p for p in _get_callable_parameters(func) if p.kind == p.VAR_KEYWORD)


def func_accepts_var_args(func):
    """
    Return True if function 'func' accepts positional arguments *args.
    """
    return any(p for p in _get_callable_parameters(func) if p.kind == p.VAR_POSITIONAL)


def method_has_no_args(meth):
    """Return True if a method only accepts 'self'."""
    count = len([p for p in _get_callable_parameters(meth) if p.kind in ARG_KINDS])
    return count == 0 if inspect.ismethod(meth) else count == 1


def func_supports_parameter(func, name):
    return any(param.name == name for param in _get_callable_parameters(func))


def is_module_level_function(func):
    if not inspect.isfunction(func) or inspect.isbuiltin(func):
        return False

    if "<locals>" in func.__qualname__:
        return False

    return True


@contextmanager
def lazy_annotations():
    """
    inspect.getfullargspec eagerly evaluates type annotations. To add
    compatibility with Python 3.14+ deferred evaluation, patch the module-level
    helper to provide the annotation_format that we are using elsewhere.

    This private helper could be removed when there is an upstream solution for
    https://github.com/python/cpython/issues/141560.

    This context manager is not reentrant.
    """
    if not PY314:
        yield
        return
    with lock:
        original_helper = inspect._signature_from_callable
        inspect._signature_from_callable = safe_signature_from_callable
        try:
            yield
        finally:
            inspect._signature_from_callable = original_helper