mrkeyoor.com_
Thu 06 Aug 07:40 UTC
PyPIUtilsupdated 06 Aug 2026

typeguard

typeguard checks at run time that values actually match the type annotations you already wrote. mypy and pyright only look at your source; typeguard looks at the objects flowing through it, so a dict that arrives from JSON claiming to be a list of Person gets caught the moment it is wrong. It offers three levels of intrusion. check_type(value, SomeType) is an isinstance that understands typing constructs like Sequence[int], TypedDict, Literal, and Protocol. The @typechecked decorator rewrites one function or class so its arguments, return value, yields, and annotated local assignments are all checked. install_import_hook does the same rewriting to every module in a package as it is imported, with no changes to that code at all. The usual pattern is checks on in tests and CI, off in production.

Verdict

As a test-time safety net, typeguard is the most thorough run-time checker in Python and the pytest plugin makes adopting it a one-line change. For always-on checking in production code, beartype is the pragmatic choice and pydantic is the right tool at the edges where data enters your system.

API stability4/5The v4 surface has held since 2023 and 4.5.0 restored check_argument_types and check_return_type that v3 had removed. The v2 to v3 rewrite was disruptive, and 4.6.0 dropped Python 3.9.
Docs5/5The Read the Docs site has a user guide, a full API reference, a features matrix of which typing constructs are supported, and an extending guide, plus a changelog that spells out every fix with its issue number.
Maintenance5/5Pushed July 2026, releases every few months, 23 open issues (27 issues and PRs), and 4.6.0 already supports Python 3.15. One maintainer, but a consistently responsive one.
Ecosystem4/5About 18.1M weekly downloads, a first-party pytest plugin, and a documented plugin hook for custom checkers through checker_lookup_functions. Most of the volume comes from other libraries depending on it.

Use it if

  • You are loading data from JSON, YAML, or a message queue and want the shape verified against an existing TypedDict or dataclass annotation instead of writing a parallel validation schema
  • You want your test suite to catch annotation lies; the pytest plugin instruments your packages with one command-line flag and nothing in your source changes
  • You have a large codebase where a static checker cannot see everything, such as plugin loading, dynamic dispatch, or values crossing a C extension boundary
  • You want checks on generator yield and send values, or on assignments to annotated local variables, which no other run-time checker covers as thoroughly
Skip it if

Setup reality

pip install typeguard is a pure Python package with one dependency, typing_extensions, and nothing to compile. Getting value out of it takes more thought than installing it. The import hook has to be installed before the target modules are imported, so a stray top-level import in your conftest or __init__ silently means half your package is never instrumented. @typechecked has to be the innermost decorator, because it works by recompiling the function it wraps, and anything already wrapping that function hides it. check_type deliberately ignores the global typeguard.config object, so settings you tuned for the decorator do not apply and you pass them per call instead. The pytest route, pytest --typeguard-packages=myapp, is the least fragile way in, and --typeguard-packages-ignore exists for the modules that do not tolerate rewriting.

Patterns

isinstance that understands typingcheck-type-directly

from typeguard import check_type

check_type([1, 2, 3], list[int])          # passes, returns the value
check_type({"a": 1}, dict[str, str])      # raises TypeCheckError

It returns the value on success, so you can inline it. Note that check_type ignores the global typeguard.config; pass forward_ref_policy or collection_check_strategy as keyword arguments here instead.

Check parsed JSON against a TypedDictvalidate-external-json

import json
from typing import TypedDict
from typeguard import check_type

class Person(TypedDict):
    name: str
    phone: str

with open("people.json") as f:
    people = check_type(json.load(f), list[Person])
# static checkers now treat `people` as list[Person]

This is the safe replacement for typing.cast, which asserts nothing. It validates rather than coerces, so a phone number that arrives as a number stays an error instead of becoming a string.

Instrument one functiontypechecked-function

from typeguard import typechecked

@typechecked
def send(user_id: int, tags: list[str]) -> bool:
    ...
    return True

Put it closest to the function, below every other decorator. It recompiles the function from source, so if another decorator has already wrapped it there is nothing left to instrument and the checks silently never run.

Instrument every method on a classtypechecked-class

from typeguard import typechecked

@typechecked
class Repository:
    def get(self, key: str) -> bytes:
        ...

    @property
    def size(self) -> int:
        ...

Covers static methods, class methods, and properties, but not inner classes and not methods that already carry a wrapping decorator. Nested classes need their own decorator.

Instrument a whole package on importimport-hook

from typeguard import install_import_hook

install_import_hook("myapp", ignore_packages=["myapp.vendor"])

import myapp.service  # must come AFTER the hook

Anything imported before the hook is installed keeps running unchecked, which is the number one reason people think the hook did nothing. It also returns a manager with uninstall(), and works as a context manager.

Turn checks on for the test suite onlypytest-plugin

pytest --typeguard-packages=myapp,mylib \
       --typeguard-packages-ignore=myapp.legacy

# or in pyproject.toml
# [tool.pytest.ini_options]
# typeguard-packages = "myapp"

The plugin installs the import hook early enough in the pytest startup to catch your package. This is the lowest-risk way to adopt typeguard: full checking in CI, zero overhead in production.

Stop it from checking only the first elementcheck-all-collection-items

from typeguard import CollectionCheckStrategy, typechecked

@typechecked(collection_check_strategy=CollectionCheckStrategy.ALL_ITEMS)
def ingest(rows: list[int]) -> None:
    ...

# or globally, for the decorator and import hook
import typeguard
typeguard.config.collection_check_strategy = CollectionCheckStrategy.ALL_ITEMS

FIRST_ITEM is the default because checking every element of a large list on every call is expensive. It also means [1, "two"] passes as list[int], which surprises people who trusted the check.

Log failures rather than blowing upwarn-instead-of-raise

import typeguard
from typeguard import warn_on_error

typeguard.config.typecheck_fail_callback = warn_on_error

Emits a TypeCheckWarning instead of raising, which is how you roll checks out over an existing codebase without breaking production. Combine with pytest's -W error to make the same warnings fatal in CI.

Catch and report a failurehandle-check-errors

from typeguard import TypeCheckError, check_type

try:
    config = check_type(raw, AppConfig)
except TypeCheckError as exc:
    log.error("bad config: %s", exc)
    raise SystemExit(1)

The message names the exact path, for example 'item 2 of argument "rows" is not an int', which is usually enough to find the offender without a traceback dig.

Skip checking inside a hot blocksuppress-checks

from typeguard import suppress_type_checks

with suppress_type_checks():
    for row in million_rows:
        process(row)   # instrumented, but checks are inert here

Works as a context manager or a decorator and is process-wide while active, not thread-local, so do not reach for it in concurrent code expecting isolation.

Validate what a generator yieldscheck-generator-yields

from collections.abc import Iterator
from typeguard import typechecked

@typechecked
def rows() -> Iterator[tuple[int, str]]:
    for raw in source:
        yield parse(raw)   # each yielded value is checked

Instrumentation checks yield and send values, which plain decorators cannot do because the annotation describes the generator, not the individual items.

Keep TYPE_CHECKING-only imports workingtype-checking-imports

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from expensive.module import Heavy

@typechecked
def handle(obj: "Heavy") -> None:
    ...

typeguard recognizes module-level TYPE_CHECKING blocks and substitutes Any for those names rather than failing to resolve them. For unresolvable forward references elsewhere, typeguard.config.forward_ref_policy chooses between ERROR, WARN, and IGNORE.

Alternatives

PackageRegistryPick it when
beartypePyPIYou want run-time checks cheap enough to leave on in production, with a plain decorator and no AST rewriting.
pydanticPyPIThe values come from outside your process and you want parsing, coercion, and error reports rather than assertions.
trycastPyPIYou only need to test whether a JSON-shaped value matches a TypedDict and branch on the result instead of catching an exception.