typing-validation
A library to perform runtime validation of Python objects using type hints.
Install
Install the latest release from PyPI:
$ pip install --upgrade typing-validation
Usage
Validate a value against a type hint. validate returns True on success and raises on failure:
>>> from typing_validation import validate
>>> validate([1, 2, 3], list[int])
True
The True return exists so that validation can be gated behind an assertion, and compiled out entirely under -O:
assert validate(val, t)
When a value does not conform, the error says where:
>>> validate({"a": [1, "b"]}, dict[str, list[int]])
Traceback (most recent call last):
...
typing_validation.errors.ValidationError: For type dict[str, list[int]]: a component failed
For type list[int] value at key 'a': a component failed
For type <class 'int'> index 1: not an instance, got 'b'
The structured explanation is on the exception, for reading programmatically:
>>> try:
... validate([1, "b"], list[int])
... except ValidationError as e:
... print(e.failure.causes[0].location.at)
1
Validating the same type repeatedly
validate analyses the type on every call. When you validate many values against one type, validator analyses it once and hands back a function:
>>> from typing_validation import validator
>>> check = validator(list[int])
>>> check([1, 2, 3])
True
Same contract, same verdict, 2.7× faster per call, and it repays the cost of building it within a handful of values.
And when the values keep coming in very large numbers, compiled_validator emits Python specialised to the type and compiles it:
>>> from typing_validation import compiled_validator
>>> check = compiled_validator(list[int])
>>> check([1, 2, 3])
True
That runs at 23 ns per type-node against a hand-written check's 23 — it is, to within the noise, the code you would have written yourself.
It costs more to build, and it only helps where there is structure to unroll: for a recursive alias or a NumPy array it stops unrolling and hands back a validator, and the table says never rather than pretending otherwise.
So: validate for one-off checks, validator when the type is fixed and the values keep coming, compiled_validator when there are very many of them.
| Type | validate |
validator |
compiled_validator |
hand-written |
|---|---|---|---|---|
list[int] (1000 items) |
115.6 µs | 42.9 µs | 22.5 µs | 23.1 µs |
list[int] (20 items) |
2.7 µs | 1.0 µs | 561 ns | 541 ns |
dict[str, int] (20 items) |
5.2 µs | 1.9 µs | 1.0 µs | 1.0 µs |
tuple[int, str] |
724 ns | 362 ns | 150 ns | 127 ns |
int |
95 ns | 92 ns | 89 ns | 56 ns |
benchmark/REPORT.md has the full table — every case, both outcomes, construction costs, and the break-even points that say exactly how many values each mechanism needs before it repays — with the machine it was measured on.
Absolute figures move a long way with that machine; the ratios between the columns move far less, and are what these rows are for.
It also measures the library against seven others, in ten configurations, which benchmark/PEER-COMPARISON.md reads and draws conclusions from.
Run python -m benchmark for your own numbers, or python -m benchmark --write to regenerate the report.
One difference, and it is deliberate. Both validator and compiled_validator analyse the whole type before seeing any value, so they reject an unsupported type immediately:
validator(list[Callable[[int], int]]) # UnsupportedTypeError, at once
validate([], list[Callable[[int], int]]) # True — no value reached the Callable
The rest of the surface
from typing_validation import is_valid, validated, validated_iter
is_valid([1, "a"], list[int]) # False — a boolean, at boolean prices
validated(payload, list[int]) # returns payload, for use in an expression
validated_iter(stream, Iterator[int]) # checks each item as it is yielded
is_valid deliberately builds no explanation: a caller who wants one calls validate and catches the exception.
validated_iter is not a convenience wrapper. Determining the items of a one-shot iterator consumes it, so Iterator[int] cannot check its items eagerly without destroying the value — checking them on the way past is the only honest way.
Asking about a type
Support is all-or-nothing: if any component of a type is unsupported, the whole type is. can_validate answers up front:
>>> from typing_validation import can_validate, inspect_type
>>> can_validate(list[int])
True
>>> can_validate(tuple[int, Callable[[int], int]]) # poisoned by the Callable
False
inspect_type returns the whole structure and names precisely what poisoned it, so "unsupported" is never opaque:
>>> node = inspect_type(tuple[int, Callable[[int], int]])
>>> [c.t for c in node.unsupported_components()]
[typing.Callable[[int], int]]
NumPy
NumPy array types are supported by an extension, which you enable by importing:
import typing_validation.numpy # required
from numpy.typing import NDArray
validate(np.array([1, 2], dtype=np.uint8), NDArray[np.uint8])
The import is required rather than automatic, so that the supported surface never depends on whether some unrelated dependency happened to import NumPy.
Extending
A parametrised class can say how its own type arguments are validated:
class Box[T]:
@classmethod
def __validate__(cls, val, args):
return is_valid(val.item, args[0])
For classes you do not own, use register_validator(cls, check).
API
The full API documentation is available at typing-validation.readthedocs.io.
Structure
typing_validation/— the package source.knowledge/— design documents: the architecture, and the catalogue of supported type forms.test/— the conformance suite, with the case corpus intest/cases.py.benchmark/— the benchmark suite; run it withpython -m benchmark. The machinery is inbenchmark/tools/, the generated numbers inbenchmark/REPORT.md, and the written synthesis inbenchmark/PEER-COMPARISON.md.docs/— the Sphinx documentation pipeline.
License
LGPL-3.0-or-later © Hashberg Ltd
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 typing_validation-2.2.1.tar.gz.
File metadata
- Download URL: typing_validation-2.2.1.tar.gz
- Upload date:
- Size: 61.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
db33e27b0269098902677aa23eba07fcd13f076b6ef2ffc1bb341be5c0942c75
|
|
| MD5 |
2b0d156a2ceb7fa3732021e6be429763
|
|
| BLAKE2b-256 |
600c1a8a4850f9b24fb8c8ac2f99394d3fb4fd929360ebfd1436e13ecebf5baf
|
File details
Details for the file typing_validation-2.2.1-py3-none-any.whl.
File metadata
- Download URL: typing_validation-2.2.1-py3-none-any.whl
- Upload date:
- Size: 71.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7b6740a98ebd3ba73a542032406807cd4a9620d578b69040a99bd58bdde9da7c
|
|
| MD5 |
af79ab3245c95b00f5b99bfa0e8fe0fe
|
|
| BLAKE2b-256 |
3f0cf44ed22596e22ec9b359d4bb99742ee3380aa433e9e161ff7acb824e5fac
|