typeguard

repository·master·Indexed 23 days ago

https://github.com/agronholm/typeguard

A run-time type checker for Python that validates PEP 484 type annotations for function arguments, return values, and local variables. It provides tools for manual verification via check_type, automatic enforcement using the @typechecked decorator, and a global import hook for project-wide instrumentation. The library supports custom checkers, plugins, and a wide range of standard library annotations including Protocols, Generics, and PEP 604 unions.

Tokens
7.2K
Snippets
12
Records
57
Agent score
75%

What's inside typeguard

  1. What Typeguard checks and does not check

    master

    Supported Checks

    • Types of arguments passed to instrumented functions.
    • Types of values returned from instrumented functions.
    • Types of values yielded from instrumented generator functions.
    • Types of values sent to instrumented generator functions.
    • Types of values assigned to local variables within instrumented functions.

    Unsupported Checks

    • Types of values assigned to class or instance variables.
    • Types of values assigned to global or nonlocal variables.
    • Stubs defined with @typing.overload (the implementation is checked if instrumented).
    • yield from statements in generator functions.
    • ParamSpec and Concatenate (currently ignored).
    • Types shadowed by arguments with the same name (e.g., def foo(x: type, type: str): ...).
  2. How generator function type checking works

    master

    The level of type checking applied to a generator depends on its return annotation:

    • Full checking: If you use typing.Generator or collections.abc.Generator, Typeguard checks the yielded values, the values sent to the generator, and the returned value.
    • Partial checking: If you use typing.Iterator or collections.abc.Iterator, Typeguard only checks the yielded values.

    Supported annotations:

    • Generator, Iterator, Iterable (and their collections.abc equivalents)
    • AsyncIterator, AsyncIterable, AsyncGenerator (Async generators only support returning None)

    Note: For AsyncGenerator, the annotation should only have two items (yield type and send type).

    from collections.abc import Generator
    
    # Full checking: yield, send, and return are checked
    def my_generator() -> Generator[int, str, bool]:
        a = yield 6
        return True
    
    # Partial checking: only yield is checked
    from collections.abc import Iterator
    
    def my_generator() -> Iterator[int]:
        a = yield 6
        return True
  3. Protocol checking in Typeguard

    master

    As of version 4.3.0, Typeguard can check instances and classes against Protocols, even if they were not explicitly annotated with @typing.runtime_checkable.

    Limitation: Argument annotations are not currently checked for compatibility against the Protocol; this is intended to be handled by static type checkers.

  4. How to add new type checkers to Typeguard

    master

    To extend the types supported by Typeguard, you must implement two components: a type checker lookup function and one or more type checker functions.

    1. The Type Checker Lookup Function

    This function determines if a custom type checker should be used for a given annotation. It receives three arguments:

    • origin_type: The base type (e.g., tuple from tuple[int]).
    • args: The generic arguments stripped from the annotation (e.g., (int,) from tuple[int]).
    • extras: Extra arguments from typing.Annotated (e.g., ('foo',) from Annotated[int, 'foo']).

    It must return a TypeCheckerCallable or None if no match is found.

    2. The Type Checker Function

    This function performs the actual validation. It receives four arguments:

    • value: The actual value being checked.
    • origin_type: The origin type.
    • args: The generic arguments (an empty tuple if not parametrized).
    • memo: A TypeCheckMemo object.

    Important Implementation Rules:

    • Recursive Checks: If your checker needs to validate nested elements (like items in a collection), use check_type_internal and pass the memo object along.
    • Configuration Compliance: Since Typeguard 4.0, checker functions must respect settings in memo.config (specifically memo.config.collection_check_strategy) rather than relying on global configuration.
    from __future__ import annotations
    from inspect import isclass
    from typing import Any
    
    from typeguard import TypeCheckError, TypeCheckerCallable, TypeCheckMemo
    
    
    class MySpecialType:
        pass
    
    
    def check_my_special_type(
        value: Any, origin_type: Any, args: tuple[Any, ...], memo: TypeCheckMemo
    ) -> None:
        if not isinstance(value, MySpecialType):
            raise TypeCheckError('is not my special type')
    
    
    def my_checker_lookup(
        origin_type: Any, args: tuple[Any, ...], extras: tuple[Any, ...]
    ) -> TypeCheckerCallable | None:
        if isclass(origin_type) and issubclass(origin_type, MySpecialType):
            return check_my_special_type
    
        return None
  5. Special considerations for TYPE_CHECKING blocks

    master
    Both the import hook and the @typechecked decorator avoid checking against types imported inside if TYPE_CHECKING: or if typing.TYPE_CHECKING: blocks. Since these types are not available at runtime, Typeguard will not emit errors or warnings for such annotations, even if they would normally be considered missing.
  6. Debug instrumented code with typeguard.config.debug_instrumentation

    master
    If your code behaves unexpectedly while Typeguard instrumentation is active, you can inspect the modified code by setting the typeguard.config.debug_instrumentation flag to True. This will print the instrumented version of your code to the console, allowing you to identify why the instrumentation might be causing unexpected behavior.
  7. How to use @typechecked with other decorators

    master

    Because @typechecked works by recompiling the target function with instrumentation, it must replace all references to the original function with the new instrumented one. If @typechecked is placed on top of another decorator that wraps the function, it may be unable to perform this replacement.

    Workarounds:

    1. Place @typechecked at the bottom of the decorator stack.
    2. Use the import hook instead of the decorator.
  8. Support for PEP 604 unions and generic collections on older Python versions

    master

    Typeguard provides compatibility for newer Python syntax on older versions, provided that from __future__ import annotations is used in the module:

    • PEP 604 Unions (X | Y): Typeguard uses a special parser to convert these to typing.Union internally for Python versions older than 3.10.
    • Generic Built-in Collections: For Python versions older than 3.9, Typeguard substitutes built-in generic types (like list[int]) with their typing equivalents (like typing.List[int]).
  9. Use the Typeguard import hook for non-invasive checking

    master

    The import hook allows you to automatically instrument all type-annotated functions in a package without modifying source code. It modifies the AST (Abstract Syntax Tree) when modules are loaded.

    Installation

    Install the hook before importing the modules you want to check:

    from typeguard import install_import_hook
    
    install_import_hook('myapp')
    from myapp import some_module  # Must import AFTER installing

    Key Features

    • Excluding packages: Use ignore_packages=['pkg.name'] to skip specific modules.
    • Context Manager: Use with install_import_hook('myapp'): for scoped instrumentation.
    • Uninstallation: install_import_hook returns a manager object that can call .uninstall().
    • Customization: Pass a subclass of TypeguardFinder to the cls parameter to control which modules are instrumented via the should_instrument(module_name) method.
  10. Run the test suite

    master

    You can run the test suite using tox to handle multiple Python versions in separate virtual environments, or by using pytest directly in a local virtual environment.

    Using tox

    Run tox to execute tests against all supported Python versions present on your system. To pass specific arguments to the underlying pytest command, use the -- separator.

    Using pytest directly

    1. Create a virtual environment.
    2. Activate the environment.
    3. Install the project in development mode with test dependencies using pip install -e .[test].
    4. Run pytest.
    # Using tox
    tox
    tox -- -k somekeyword
    
    # Using pytest directly (Linux/macOS)
    python -m venv venv
    source venv/bin/activate
    pip install -e .[test]
    pytest