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

beartype

beartype enforces the type hints you already wrote, at runtime. Decorate a function with @beartype and it generates a small wrapper that checks arguments and the return value against their annotations, raising a beartype.roar exception with a message pointing at the exact offending value. The trick that makes it usable in production is its default O(1) strategy: for containers it checks the container type plus one randomly chosen item, so the cost per call stays roughly constant no matter how big the list is. Beyond the decorator there is beartype.claw, an import hook that applies the decorator to every annotated class, function, and even variable assignment across a package without you touching any files, and beartype.door, which gives you is_bearable and die_if_unbearable for checking any object against any hint at any point in your code. It is pure Python with no runtime dependencies and needs Python 3.10 or later.

Verdict

The cheapest way to make Python annotations mean something at runtime, and the import hook makes adopting it a two-line change. Just be clear with your team that the default check samples one item per container, so it catches shape mistakes rather than proving a collection is homogeneous.

API stability4/5The @beartype decorator and BeartypeConf have been compatible for years and deprecated names keep working through an alias shim, but the project is still 0.x, subsystems like door and claw have been reorganized between minors, and infer_hint has already moved out of beartype.door into beartype.bite.
Docs3/5The ReadTheDocs site covers every API with examples and the FAQ genuinely explains the O(1) design, but the writing is dense comedy that buries the operational facts, and the crucial detail that the On and Ologn strategies are unimplemented is easiest to confirm by reading the enum source.
Maintenance5/5Repo pushed 5 August 2026, 0.22.9 released 13 December 2025, and the release cadence has been several versions a year for years; 108 open issues (115 counting PRs) is a normal backlog for a project at this download volume and the maintainer answers threads in detail.
Ecosystem5/5About 26M weekly downloads and 3.5k stars, with first-class handling for numpy, pandas, jax, torch, and pandera hints; several scientific Python projects ship it as their runtime checker of choice.

Use it if

  • Your code is already annotated and you want those annotations enforced against real data at runtime, not just checked against source by mypy in CI
  • You are handling data from outside your process (JSON payloads, database rows, ML tensors, user config) and want the failure to happen at the boundary with a readable message instead of three frames deeper as an AttributeError
  • You want per-call overhead you can actually pay: the O(1) strategy means checking a million-element list costs about the same as checking a ten-element one
  • You want to switch it on for a whole package in two lines with beartype.claw.beartype_this_package() rather than decorating hundreds of functions by hand
  • You need a runtime isinstance() that understands modern hints: is_bearable(x, list[tuple[int, str]] | None) works where isinstance() raises TypeError on subscripted generics
  • You want type checking in tests and development but zero cost in production, which BeartypeStrategy.O0 or simply not installing the import hook gives you
Skip it if

Setup reality

pip install beartype pulls nothing else in: no compiler, no wheels to worry about, no runtime dependencies at all on Python 3.10 through 3.14. Turning it on is where decisions start. The decorator route is explicit and boring. The import hook route (beartype.claw.beartype_this_package() at the top of your package __init__) is two lines but has real ordering rules: it must run before the submodules it covers are imported, so putting it below your other imports silently covers nothing. Import hooks also do not apply to modules already in sys.modules, which bites in test suites and in notebooks where you re-run a cell. Third-party code is a separate decision: beartype_all() with a plain config will raise exceptions from libraries you do not control, so the documented pattern is beartype_all(conf=BeartypeConf(violation_type=UserWarning)) to downgrade those to warnings while your own package still raises. Finally, PEP 563 (from __future__ import annotations) turns hints into strings that beartype has to resolve, and forward references to names that are not importable at runtime will fail there rather than at definition time.

Patterns

Check one function's arguments and return valuedecorate-function

from beartype import beartype

@beartype
def repeat(text: str, times: int) -> list[str]:
    return [text] * times

repeat('hi', 3)      # fine
repeat('hi', '3')    # BeartypeCallHintParamViolation

Put @beartype closest to the function when stacking decorators, otherwise it wraps the outer decorator's signature instead of yours. Unannotated parameters are simply not checked, which makes partial adoption safe.

Turn it on for a whole packageimport-hook-package

# your_package/__init__.py, at the very top
from beartype.claw import beartype_this_package

beartype_this_package()

# only after this line:
from your_package import api, models

The hook only affects modules imported after it runs, so anything already in sys.modules is skipped. That is why it has to be the first statement in __init__, above your own imports.

Raise in your code, warn about everyone else'swarn-on-third-party

from beartype import BeartypeConf
from beartype.claw import beartype_all, beartype_this_package

beartype_this_package()
beartype_all(conf=BeartypeConf(violation_type=UserWarning))

beartype_all() without the UserWarning config will crash on the first sloppy annotation in a dependency you cannot patch. Expect a noisy first run either way and use warnings filters to mute known offenders.

Type-check an object outside a function signaturecheck-value-anywhere

from beartype.door import is_bearable, die_if_unbearable

rows: object = load_json()

if is_bearable(rows, list[dict[str, int]]):
    total = sum(r['n'] for r in rows)

# assert-style, raises BeartypeDoorHintViolation
die_if_unbearable(rows, list[dict[str, int]])

This is the replacement for isinstance() when the hint is subscripted; isinstance(x, list[int]) raises TypeError outright. Same O(1) sampling applies, so is_bearable on a big list is fast but not exhaustive.

Add value constraints on top of a typeconstrain-values

from typing import Annotated
from beartype import beartype
from beartype.vale import Is

NonEmptyStr = Annotated[str, Is[lambda s: bool(s.strip())]]
Port = Annotated[int, Is[lambda n: 1 <= n <= 65535]]

@beartype
def connect(host: NonEmptyStr, port: Port = 5432) -> None:
    ...

Validators run on every check, so keep the lambda cheap: no I/O, no regex compilation inside the body. mypy sees only the base type, so these constraints exist at runtime only.

Catch violations instead of crashinghandle-violations

from beartype.roar import (
    BeartypeCallHintParamViolation,
    BeartypeCallHintReturnViolation,
)

try:
    handle_request(payload)
except BeartypeCallHintParamViolation as exc:
    return {'error': 'bad request', 'detail': str(exc)}, 400
except BeartypeCallHintReturnViolation:
    log.exception('we produced the wrong shape')
    raise

Parameter violations mean the caller sent garbage; return violations mean your own code is wrong. Both subclass BeartypeCallHintViolation, so catch the specific one when the distinction should change your HTTP status.

Raise your own exception class on violationcustom-exception-type

from beartype import beartype, BeartypeConf

class PayloadError(ValueError):
    pass

@beartype(conf=BeartypeConf(violation_type=PayloadError))
def ingest(record: dict[str, str]) -> None:
    ...

# or split it: violation_param_type / violation_return_type

Useful when an existing error handler already catches ValueError and you do not want beartype types leaking into your API layer. violation_param_type and violation_return_type override violation_type for that direction only.

Keep checks in development, drop them in productiondisable-in-production

import os
from beartype import BeartypeConf, BeartypeStrategy, beartype

noop = beartype(conf=BeartypeConf(strategy=BeartypeStrategy.O0))
check = beartype if os.getenv('APP_ENV') != 'prod' else noop

@check
def hot_path(xs: list[float]) -> float:
    return sum(xs)

O0 reduces the decorator to the identity function, so there is no wrapper and no cost. If you use the import hook instead, gate the beartype_this_package() call on the same environment variable.

Enable checking only inside a blockscoped-checking

from beartype.claw import beartyping

with beartyping():
    import plugins.risky_plugin

# modules imported outside the block are untouched

Handy in tests and notebooks where you want one suspect module checked without turning the hook on globally. Modules already imported before the block are not re-checked.

Ask whether one hint is narrower than anothercompare-hints

from beartype.door import TypeHint, is_subhint

is_subhint(list[int], list[object])     # True
is_subhint(int | str, int)              # False

hint = TypeHint(dict[str, list[int]])
hint.args                                # (TypeHint(str), TypeHint(list[int]))
TypeHint(int) <= TypeHint(int | str)     # True

This is the piece to reach for when writing plugin registries or dispatchers that need to reason about annotations. TypeHint wraps a hint into a comparable object so you can sort and dedupe handlers by specificity.

Work with postponed annotation evaluationdeferred-annotations

from __future__ import annotations
from beartype import beartype

@beartype
def link(node: Node, parent: Node | None = None) -> Node:
    ...

class Node:
    pass

With PEP 563 every hint is a string that beartype resolves on the first call, so a name that only exists under TYPE_CHECKING will raise then rather than at import. Keep runtime-referenced classes importable at runtime.

Check only your package while the test suite runspytest-integration

# conftest.py at the repo root
from beartype import BeartypeConf
from beartype.claw import beartype_packages

beartype_packages(
    ('your_package', 'your_package.plugins'),
    conf=BeartypeConf(is_debug=True),
)

conftest.py loads before your test modules, which is exactly the ordering the hook needs. is_debug prints the generated wrapper source, which is the fastest way to understand why a hint is or is not being checked.

Alternatives

PackageRegistryPick it when
typeguardPyPIYou want every element of a container checked rather than a random sample, and you accept the linear cost per call
pydanticPyPIYou need to parse and coerce untrusted input into validated model objects, not just assert that existing objects match their hints
mypyPyPIYou want errors before the code runs, across paths your tests never reach, with no runtime cost at all