Skip to content

Dataclasses

The dataclass adaptor reads any class dataclasses.is_dataclass says is one, and its members are instances of that class. There is no validator in the standard library, so membership is the package's own neutral checker, which holds each field to the type and the bounds the annotations spell. It needs nothing installed beyond mathema and this package, and it runs on every Python the package supports.

What it reads

Dataclass spelling Neutral model Notes
int, float, bool, str, bytes, Decimal the matching base type
date, time, datetime, timedelta date, time, datetime, duration
Literal["web", "shop"] a categorical with those levels
list[T], tuple[T, ...], set[T] a list of T
dict[K, V] a map from K to V
a nested dataclass, TypedDict or pydantic model a struct of its fields
T \| None, Optional[T] nullable
Annotated[T, Ge(1), Le(10)] min, max read by attribute, so annotated_types, pydantic's Field and any look-alike work: gt, ge, lt, le, min_length, max_length, multiple_of
a default or a default_factory the field's default
a pattern marker refused the standard library has nothing to hold a regular expression to, so the adaptor refuses the class and names pydantic or a JSON Schema instead
__post_init__ and any other code not read a check written in code is invisible to the neutral model

A worked claim

The shop's parcels, from examples/shop/shipping.py:

from dataclasses import dataclass
from typing import Annotated

from annotated_types import Ge, Le, MaxLen


@dataclass
class Parcel:
    postcode: Annotated[str, MaxLen(8)]
    weight_kg: Annotated[float, Ge(0.0), Le(30.0)]


def postage(parcel: Parcel) -> float:
    """What it costs to send the parcel: a base rate plus a rate per kilo."""
    return 3.5 + 1.2 * parcel.weight_kg
Function Claim Verdict Why
postage for parcel in L[shop.shipping.Parcel], 3.5 <= f(parcel) <= 39.5 proven The lift reads the weight's bounds off the annotation, nought to thirty kilos.
postage for parcel in L[shop.shipping.Parcel], f(parcel) <= 30 falsified A thirty-kilo parcel costs 39.50.

Enforcing the schema

A function that guards a boundary should refuse a record its schema rejects. excluded_outside_domain feeds it records just outside the language, and the witness names the value and why it is outside, in the library's own words. This function trusts its input, so a record the schema rejects goes straight through:

def address_label(parcel: Parcel) -> str:
    """The postcode printed on the parcel's label."""
    return parcel.postcode.upper()
f = address_label
for parcel in L[shop.shipping.Parcel], excluded_outside_domain(parcel)
    falsified   parcel = Parcel(postcode='', weight_kg=-1.0) (outside L[shop.shipping.Parcel] at .weight_kg: >= 0.0)

The same claim over the function that loads the record, from a request or a database, is the one that should hold.