An extensive drop-in argument validator.
Project description
typekeeper
Lightweight, dependency-free runtime argument validation for Python via a single decorator. See Changelog.md for version history.
Overview
typekeeper instruments your functions with comprehensive runtime checks
through one decorator, @validate_args. With no code changes beyond the
decorator, you get:
- Type checking against rich
typingannotations. - Warnings for mismatched or mutable default values.
- Inclusive numeric-range and length-range constraints via a tiny spec
string, including nested-path targeting with
:and wildcards. - Recursive
_validate()traversal of nested data structures. - Precise source locations (file + line) for every warning, both at decoration time and call time.
- Global, per-decorator, and contextual on/off switches.
Features
- Type validation — recursively checks the common
typingforms: built-in generics (list[int],dict[str, float]),Optional,Union/ PEP 604 unions,Literal,Type[T]/type[T],Annotated,Final,NewType,TypeVar(constraints and bounds),Callable(callability and positional arity),TypedDict(structural dict check),Protocol(structural attribute check for both@runtime_checkableand plain protocols),Self, and abstract base classes such asSequence,Mapping, andIterable. Unresolved string forward references are treated as unchecked;ForwardRefobjects are resolved against the typing namespace when possible. - Default-value checks — warns when a default does not match its
annotation, and detects mutable defaults (suppressible per decorator
via
ignore_defaults=True). - Range & length specs — pass
constraints; each spec isname=token1,token2,...where a token is either a single numberNor an inclusive rangemin-max. Whitespace around the inner-is tolerated. Use:to traverse nested mappings and sequences, with integer segments selecting sequence elements (negative indices follow normal Python rules). Wildcards (*or a trailing:) match every element under that point; later specs override earlier ones for the same target. Missing mapping keys yield no matches (no spurious warnings). - Recursive
_validate()— every argument is walked. Any object with a callable_validate()is invoked; falsy returns or raised exceptions are reported with the path inside the original structure. Mappings (includingUserDict/MappingProxyType) are recursed viacollections.abc.Mapping. Iterators and generators are never consumed by validation. - Context-aware warnings — messages embed both the decoration
location (
file:line) and the call site. - Global control —
set_arg_checks(False)disables decoration entirely;set_stop_on_error(True)turns every warning into aValueError;suspended_arg_checks()is a context manager that switches checks off inside awithblock (overhead drops to under half a microsecond per call).
Installation
pip install typekeeper
Development
pip install -e ".[dev]"
python -m pytest tests/ --cov=typekeeper
The test suite covers 100% of typekeeper/typekeeper.py and exercises a
broad slice of typing features end-to-end.
Configuration
| API | Effect |
|---|---|
set_arg_checks(enabled: bool) |
Turn all argument checking on/off globally. Disabling at decoration time skips wrapping entirely (zero overhead). |
set_stop_on_error(stop: bool) |
When True, every validation failure raises ValueError instead of warning. |
suspended_arg_checks() |
Context manager that disables checks within a with block (decorator stays in place). |
API Reference
validate_args
def validate_args(
_func=None,
constraints: str | None = None,
*,
ignore_defaults: bool = False,
stop_on_error: bool | None = None,
) -> Callable: ...
constraints— numeric/length spec string covering both numeric ranges and sequence-length ranges (see syntax below).ignore_defaults— skip the mutable-default warning ifTrue.stop_on_error— per-decorator override of the globalset_stop_on_errorflag; warnings from this decorator raiseValueErrorregardless of the global setting.
Constraint syntax — a semicolon-separated list:
name=token1,token2,...
Each token is N (exact match) or min-max (inclusive range).
Nested targets use : after the name: users:tables:headers=100,
rows:0:score=0-100, data:*=1-5. Whitespace inside ranges is
tolerated (1 - 3 parses identically to 1-3).
Inside path segments, the following backslash escapes are honoured:
| Escape | Meaning |
|---|---|
\: |
Literal : inside a segment. |
\\ |
Literal backslash. |
\* |
Literal * segment (no wildcard expansion). |
\! |
Literal empty segment — addresses dict keys that are literally "" without firing the wildcard branch. |
A bare empty segment (e.g. trailing :) or * still means every entry
at this level; specs whose first segment is empty (":x=1-3") emit a
parse-time warning instead of being silently dropped.
Examples
Full runnable scripts live in examples/. Each script
prints its own validation overhead via examples/_bench.py.
| File | Topic |
|---|---|
basic.py |
Type + default checks. |
range_specs.py |
Numeric and length ranges. |
path_indices.py |
Integer index segments (incl. negatives). |
nested_override.py |
Wildcards and spec overrides. |
custom_validate.py |
_validate() recursion + mutable defaults. |
complex_typing.py |
Union, Literal, Callable (arity), TypeVar, NewType, Annotated. |
typeddicts.py |
Structural TypedDict checks (total=True / False). |
protocols.py |
@runtime_checkable and plain Protocol (structural attribute check). |
forward_refs.py |
from __future__ import annotations and forward references. |
globals_and_context.py |
set_arg_checks, set_stop_on_error, suspended_arg_checks. |
# Snapshot of basic.py
from typekeeper import validate_args
@validate_args()
def greet(name: str, times: int = 1) -> str:
return " ".join([name] * times)
greet("Alice")
greet(42)
# UserWarning: Arg 'name' to '<function greet ...>' mismatches <class 'str'>;
# expected <class 'str'>, got <class 'int'>
Performance
Run tools/profile.py to reproduce these numbers.
They are representative of a single CPython 3.14 run on a desktop
x86-64 machine (50,000 iterations per scenario, after a warm-up burst):
| Scenario | Bare (µs/call) | Checked (µs/call) | Suspended (µs/call) | Overhead (µs/call) |
|---|---|---|---|---|
simple (x: int, y: str) |
0.08 | 10.9 | 0.19 | 10.8 |
nested_generic (list[dict[str, int]], 3 dicts) |
0.35 | 39.5 | 0.66 | 39.1 |
constraints (numeric + length spec) |
0.07 | 23.0 | 0.18 | 22.9 |
path_spec (rows:*:score=0-100, 5 rows) |
0.05 | 60.6 | 0.25 | 60.6 |
varargs (*values: int, 6 args) |
0.07 | 20.6 | 0.53 | 20.6 |
custom_validate (8-element list with _validate) |
0.05 | 25.3 | 0.21 | 25.2 |
Run it yourself:
python tools/profile.py --iterations 50000 # scenario benchmark
python tools/profile.py --cprofile # cProfile breakdown
python tools/profile.py --iterations 50000 --json results.json
Key takeaways:
- The decorator itself (suspended mode) costs well under 1 µs per call (typically 0.2 µs for fixed-arity signatures).
- Scalar arguments add roughly 10 µs per call for type checking.
- Per-element checking dominates the cost on collection-shaped signatures; expect ~5–10 µs / leaf in the worst case.
- Path-spec wildcards walk every leaf — prefer indexed paths
(
rows:0:v=...) when you only care about a few cells. - The
_recursive_validate()walk runs on every argument; if your hot path passes large structures and you don't need it, hoist the validated call out of the loop or wrap it inwith suspended_arg_checks(): ....
Contributing
Contributions are welcome:
- Fork the repository.
- Create a feature branch.
- Run
python -m pytest tests/ --cov=typekeeper(target stays at 100%). - Submit a pull request.
License
MIT License.
Author
Parth Mittal — parth@privatepanda.co
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file typekeeper-0.0.4.tar.gz.
File metadata
- Download URL: typekeeper-0.0.4.tar.gz
- Upload date:
- Size: 30.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7dbc456fd08cb9a037c8458edddb9e561bd9cda1bd073f35ad1a7876e5305a5e
|
|
| MD5 |
dc12d871a01eb3f8a7b596776a1096ac
|
|
| BLAKE2b-256 |
389e8b8b2bebcf8dcdf8bf9e9e463a71d6b28330d821d88825f217922a2e2f02
|
File details
Details for the file typekeeper-0.0.4-py3-none-any.whl.
File metadata
- Download URL: typekeeper-0.0.4-py3-none-any.whl
- Upload date:
- Size: 15.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ecfa5021c3fd8f9fbb8fa38ef4cce05b678b157fb631a19224cbef5a26efa82
|
|
| MD5 |
bb305bc1417f372b6c3b0426d37bb3f9
|
|
| BLAKE2b-256 |
d1e663b7344b18da8ac0d35e024657bddae903feed1fe926d812c4318d7f75cd
|