• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

agronholm / typeguard / 11804632645

12 Nov 2024 07:50PM UTC coverage: 94.458% (-0.05%) from 94.511%
11804632645

Pull #475

github

web-flow
Merge 859a611d8 into b6a7e4387
Pull Request #475: Use environment variable TYPEGUARD_DISABLE to disable typeguard.

2 of 2 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

1585 of 1678 relevant lines covered (94.46%)

5.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.5
/src/typeguard/_decorators.py
1
from __future__ import annotations
6✔
2

3
import ast
6✔
4
import inspect
6✔
5
import os
6✔
6
import sys
6✔
7
from collections.abc import Sequence
6✔
8
from functools import partial
6✔
9
from inspect import isclass, isfunction
6✔
10
from types import CodeType, FrameType, FunctionType
6✔
11
from typing import TYPE_CHECKING, Any, Callable, ForwardRef, TypeVar, cast, overload
6✔
12
from warnings import warn
6✔
13

14
from ._config import CollectionCheckStrategy, ForwardRefPolicy, global_config
6✔
15
from ._exceptions import InstrumentationWarning
6✔
16
from ._functions import TypeCheckFailCallback
6✔
17
from ._transformer import TypeguardTransformer
6✔
18
from ._utils import Unset, function_name, get_stacklevel, is_method_of, unset
6✔
19

20
T_CallableOrType = TypeVar("T_CallableOrType", bound=Callable[..., Any])
6✔
21

22
if TYPE_CHECKING:
23
    from typeshed.stdlib.types import _Cell
24

25
    def typeguard_ignore(arg: T_CallableOrType) -> T_CallableOrType:
26
        """This decorator is a noop during static type-checking."""
27
        return arg
28

29
else:
30
    from typing import no_type_check as typeguard_ignore  # noqa: F401
6✔
31

32

33
def make_cell(value: object) -> _Cell:
6✔
34
    return (lambda: value).__closure__[0]  # type: ignore[index]
6✔
35

36

37
def find_target_function(
6✔
38
    new_code: CodeType, target_path: Sequence[str], firstlineno: int
39
) -> CodeType | None:
40
    target_name = target_path[0]
6✔
41
    for const in new_code.co_consts:
6✔
42
        if isinstance(const, CodeType):
6✔
43
            if const.co_name == target_name:
6✔
44
                if const.co_firstlineno == firstlineno:
6✔
45
                    return const
6✔
46
                elif len(target_path) > 1:
6✔
47
                    target_code = find_target_function(
6✔
48
                        const, target_path[1:], firstlineno
49
                    )
50
                    if target_code:
6✔
51
                        return target_code
6✔
52

53
    return None
×
54

55

56
def instrument(f: T_CallableOrType) -> FunctionType | str:
6✔
57
    if not getattr(f, "__code__", None):
6✔
58
        return "no code associated"
×
59
    elif not getattr(f, "__module__", None):
6✔
60
        return "__module__ attribute is not set"
×
61
    elif f.__code__.co_filename == "<stdin>":
6✔
62
        return "cannot instrument functions defined in a REPL"
×
63
    elif hasattr(f, "__wrapped__"):
6✔
64
        return (
6✔
65
            "@typechecked only supports instrumenting functions wrapped with "
66
            "@classmethod, @staticmethod or @property"
67
        )
68

69
    target_path = [item for item in f.__qualname__.split(".") if item != "<locals>"]
6✔
70
    module_source = inspect.getsource(sys.modules[f.__module__])
6✔
71
    module_ast = ast.parse(module_source)
6✔
72
    instrumentor = TypeguardTransformer(target_path, f.__code__.co_firstlineno)
6✔
73
    instrumentor.visit(module_ast)
6✔
74

75
    if not instrumentor.target_node or instrumentor.target_lineno is None:
6✔
76
        return "instrumentor did not find the target function"
×
77

78
    module_code = compile(module_ast, f.__code__.co_filename, "exec", dont_inherit=True)
6✔
79
    new_code = find_target_function(
6✔
80
        module_code, target_path, instrumentor.target_lineno
81
    )
82
    if not new_code:
6✔
83
        return "cannot find the target function in the AST"
×
84

85
    if global_config.debug_instrumentation and sys.version_info >= (3, 9):
6✔
86
        # Find the matching AST node, then unparse it to source and print to stdout
87
        print(
6✔
88
            f"Source code of {f.__qualname__}() after instrumentation:"
89
            "\n----------------------------------------------",
90
            file=sys.stderr,
91
        )
92
        print(ast.unparse(instrumentor.target_node), file=sys.stderr)
6✔
93
        print(
6✔
94
            "----------------------------------------------",
95
            file=sys.stderr,
96
        )
97

98
    closure = f.__closure__
6✔
99
    if new_code.co_freevars != f.__code__.co_freevars:
6✔
100
        # Create a new closure and find values for the new free variables
101
        frame = cast(FrameType, inspect.currentframe())
6✔
102
        frame = cast(FrameType, frame.f_back)
6✔
103
        frame_locals = cast(FrameType, frame.f_back).f_locals
6✔
104
        cells: list[_Cell] = []
6✔
105
        for key in new_code.co_freevars:
6✔
106
            if key in instrumentor.names_used_in_annotations:
6✔
107
                # Find the value and make a new cell from it
108
                value = frame_locals.get(key) or ForwardRef(key)
6✔
109
                cells.append(make_cell(value))
6✔
110
            else:
111
                # Reuse the cell from the existing closure
112
                assert f.__closure__
6✔
113
                cells.append(f.__closure__[f.__code__.co_freevars.index(key)])
6✔
114

115
        closure = tuple(cells)
6✔
116

117
    new_function = FunctionType(new_code, f.__globals__, f.__name__, closure=closure)
6✔
118
    new_function.__module__ = f.__module__
6✔
119
    new_function.__name__ = f.__name__
6✔
120
    new_function.__qualname__ = f.__qualname__
6✔
121
    new_function.__annotations__ = f.__annotations__
6✔
122
    new_function.__doc__ = f.__doc__
6✔
123
    new_function.__defaults__ = f.__defaults__
6✔
124
    new_function.__kwdefaults__ = f.__kwdefaults__
6✔
125
    return new_function
6✔
126

127

128
@overload
6✔
129
def typechecked(
6✔
130
    *,
131
    forward_ref_policy: ForwardRefPolicy | Unset = unset,
132
    typecheck_fail_callback: TypeCheckFailCallback | Unset = unset,
133
    collection_check_strategy: CollectionCheckStrategy | Unset = unset,
134
    debug_instrumentation: bool | Unset = unset,
135
) -> Callable[[T_CallableOrType], T_CallableOrType]: ...
136

137

138
@overload
6✔
139
def typechecked(target: T_CallableOrType) -> T_CallableOrType: ...
6✔
140

141

142
def typechecked(
6✔
143
    target: T_CallableOrType | None = None,
144
    *,
145
    forward_ref_policy: ForwardRefPolicy | Unset = unset,
146
    typecheck_fail_callback: TypeCheckFailCallback | Unset = unset,
147
    collection_check_strategy: CollectionCheckStrategy | Unset = unset,
148
    debug_instrumentation: bool | Unset = unset,
149
) -> Any:
150
    """
151
    Instrument the target function to perform run-time type checking.
152

153
    This decorator recompiles the target function, injecting code to type check
154
    arguments, return values, yield values (excluding ``yield from``) and assignments to
155
    annotated local variables.
156

157
    This can also be used as a class decorator. This will instrument all type annotated
158
    methods, including :func:`@classmethod <classmethod>`,
159
    :func:`@staticmethod <staticmethod>`,  and :class:`@property <property>` decorated
160
    methods in the class.
161

162
    .. note:: When Python is run in optimized mode (``-O`` or ``-OO``, or when the
163
        environment variable `TYPEGUARD_DISABLE` is set, this decorator is a no-op).
164
        This is a feature meant for selectively introducing type checking
165
        into a code base where the checks aren't meant to be run in production.
166

167
    :param target: the function or class to enable type checking for
168
    :param forward_ref_policy: override for
169
        :attr:`.TypeCheckConfiguration.forward_ref_policy`
170
    :param typecheck_fail_callback: override for
171
        :attr:`.TypeCheckConfiguration.typecheck_fail_callback`
172
    :param collection_check_strategy: override for
173
        :attr:`.TypeCheckConfiguration.collection_check_strategy`
174
    :param debug_instrumentation: override for
175
        :attr:`.TypeCheckConfiguration.debug_instrumentation`
176

177
    """
178
    if target is None:
6✔
179
        return partial(
6✔
180
            typechecked,
181
            forward_ref_policy=forward_ref_policy,
182
            typecheck_fail_callback=typecheck_fail_callback,
183
            collection_check_strategy=collection_check_strategy,
184
            debug_instrumentation=debug_instrumentation,
185
        )
186

187
    if not __debug__ or "TYPEGUARD_DISABLE" in os.environ:
6✔
UNCOV
188
        return target
×
189

190
    if isclass(target):
6✔
191
        for key, attr in target.__dict__.items():
6✔
192
            if is_method_of(attr, target):
6✔
193
                retval = instrument(attr)
6✔
194
                if isfunction(retval):
6✔
195
                    setattr(target, key, retval)
6✔
196
            elif isinstance(attr, (classmethod, staticmethod)):
6✔
197
                if is_method_of(attr.__func__, target):
6✔
198
                    retval = instrument(attr.__func__)
6✔
199
                    if isfunction(retval):
6✔
200
                        wrapper = attr.__class__(retval)
6✔
201
                        setattr(target, key, wrapper)
6✔
202
            elif isinstance(attr, property):
6✔
203
                kwargs: dict[str, Any] = dict(doc=attr.__doc__)
6✔
204
                for name in ("fset", "fget", "fdel"):
6✔
205
                    property_func = kwargs[name] = getattr(attr, name)
6✔
206
                    if is_method_of(property_func, target):
6✔
207
                        retval = instrument(property_func)
6✔
208
                        if isfunction(retval):
6✔
209
                            kwargs[name] = retval
6✔
210

211
                setattr(target, key, attr.__class__(**kwargs))
6✔
212

213
        return target
6✔
214

215
    # Find either the first Python wrapper or the actual function
216
    wrapper_class: (
6✔
217
        type[classmethod[Any, Any, Any]] | type[staticmethod[Any, Any]] | None
218
    ) = None
219
    if isinstance(target, (classmethod, staticmethod)):
6✔
220
        wrapper_class = target.__class__
6✔
221
        target = target.__func__  # type: ignore[assignment]
6✔
222

223
    retval = instrument(target)
6✔
224
    if isinstance(retval, str):
6✔
225
        warn(
×
226
            f"{retval} -- not typechecking {function_name(target)}",
227
            InstrumentationWarning,
228
            stacklevel=get_stacklevel(),
229
        )
230
        return target
×
231

232
    if wrapper_class is None:
6✔
233
        return retval
6✔
234
    else:
235
        return wrapper_class(retval)
6✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc