Skip to main content

typing-validation

Python versions PyPI version PyPI status Checked with Mypy Documentation Status

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 in test/cases.py.
  • benchmark/ — the benchmark suite; run it with python -m benchmark. The machinery is in benchmark/tools/, the generated numbers in benchmark/REPORT.md, and the written synthesis in benchmark/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

typing_validation-2.2.1.tar.gz (61.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

typing_validation-2.2.1-py3-none-any.whl (71.1 kB view details)

Uploaded Python 3

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

Hashes for typing_validation-2.2.1.tar.gz
Algorithm Hash digest
SHA256 db33e27b0269098902677aa23eba07fcd13f076b6ef2ffc1bb341be5c0942c75
MD5 2b0d156a2ceb7fa3732021e6be429763
BLAKE2b-256 600c1a8a4850f9b24fb8c8ac2f99394d3fb4fd929360ebfd1436e13ecebf5baf

See more details on using hashes here.

File details

Details for the file typing_validation-2.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for typing_validation-2.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 7b6740a98ebd3ba73a542032406807cd4a9620d578b69040a99bd58bdde9da7c
MD5 af79ab3245c95b00f5b99bfa0e8fe0fe
BLAKE2b-256 3f0cf44ed22596e22ec9b359d4bb99742ee3380aa433e9e161ff7acb824e5fac

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page