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
|
Extending Typeguard
===================
.. py:currentmodule:: 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:
#. The origin type (the annotation with any arguments stripped from it)
#. The previously stripped out generic arguments, if any
#. Extra arguments from the :class:`~typing.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:
#. The value to be type checked
#. The origin type
#. The generic arguments from the annotation (empty tuple when the annotation was not
parametrized)
#. The memo object (:class:`~.TypeCheckMemo`)
There are a couple of things to take into account when writing a type checker:
#. If your type checker function needs to do further type checks (such as type checking
items in a collection), you need to use :func:`~.check_type_internal` (and pass
along ``memo`` to it)
#. If you're type checking collections, your checker function should respect the
:attr:`~.TypeCheckConfiguration.collection_check_strategy` setting, available from
:attr:`~.TypeCheckMemo.config`
.. versionchanged:: 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):
#. Append to :data:`typeguard.checker_lookup_functions`
#. 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``:
.. code-block:: 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.
.. _entry point: https://docs.python.org/3/library/importlib.metadata.html#entry-points
|