Extending Typeguard

Adding new type checkers

The range of types supported by Typeguard can be extended by writing a type checker lookup function and one or more type checker functions. The former will return one of the latter, or None if the given value does not match any of your custom type checker functions.

The lookup function receives three arguments:

  1. The origin type (the annotation with any arguments stripped from it)

  2. The previously stripped out generic arguments, if any

  3. Extra arguments from the Annotated annotation, if any

For example, if the annotation was tuple,, the lookup function would be called with tuple, (), (). If the type was parametrized, like tuple[str, int], it would be called with tuple, (str, int), (). If the annotation was Annotated[tuple[str, int], "foo", "bar"], the arguments would instead be tuple, (str, int), ("foo", "bar").

The checker function receives four arguments:

  1. The value to be type checked

  2. The origin type

  3. The generic arguments from the annotation (empty tuple when the annotation was not parametrized)

  4. The memo object (TypeCheckMemo)

There are a couple of things to take into account when writing a type checker:

  1. If your type checker function needs to do further type checks (such as type checking items in a collection), you need to use check_type_internal() (and pass along memo to it)

  2. If you’re type checking collections, your checker function should respect the collection_check_strategy setting, available from config

Changed in version 4.0: In Typeguard 4.0, checker functions must respect the settings in memo.config, rather than the global configuration

The following example contains a lookup function and type checker for a custom class (MySpecialType):

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

Registering your type checker lookup function with Typeguard

Just writing a type checker lookup function doesn’t do anything by itself. You’ll have to advertise your type checker lookup function to Typeguard somehow. There are two ways to do that (pick just one):

  1. Append to typeguard.checker_lookup_functions

  2. Add an entry point to your project in the typeguard.checker_lookup group

If you’re packaging your project with standard packaging tools, it may be better to add an entry point instead of registering it manually, because manual registration requires the registration code to run first before the lookup function can work.

To manually register the type checker lookup function with Typeguard:

from typeguard import checker_lookup_functions

checker_lookup_functions.append(my_checker_lookup)

For adding entry points to your project packaging metadata, the exact method may vary depending on your packaging tool of choice, but the standard way (supported at least by recent versions of setuptools) is to add this to pyproject.toml:

[project.entry-points]
typeguard.checker_lookup = {myplugin = "myapp.my_plugin_module:my_checker_lookup"}

The configuration above assumes that the globally unique (within the typeguard.checker_lookup namespace) entry point name for your lookup function is myplugin, it lives in the myapp.my_plugin_module and the name of the function there is my_checker_lookup.

Note

After modifying your project configuration, you may have to reinstall it in order for the entry point to become discoverable.