dau_core.models

Frozen models that validate at use.

Every dau-core model is frozen, and construction validates it. Two things get past that. model_copy(update=...) builds a new instance without running a single validator, so a copy can carry a value construction would have refused. And a frozen model can still hold a dict, which anyone holding the model can change in place.

CheckedModel closes both. Its instances re-validate whenever they are handed back to pydantic (revalidate_instances="always"), so a parent built from a copied child refuses a bad child, and check() is that re-validation on demand: a validated copy, or the error. replace() is the validated form of model_copy. A mapping field annotated ReadOnly is stored as a FrozenMapping, which compares, hashes and serializes as the dict it was built from and refuses every mutation.

The ccflow result and callable models cannot change base; they take CheckedMixin and the same config entries by hand.

Attributes

ReadOnly

Classes

FrozenMapping

A read-only mapping a frozen model can hold.

CheckedMixin

check and replace for a frozen pydantic model.

CheckedModel

The base of every dau-core data model: frozen, and re-validated at

Functions

freeze_mapping(value) → FrozenMapping

Module Contents

class dau_core.models.FrozenMapping(data: collections.abc.Mapping[Any, Any] | collections.abc.Iterable[tuple[Any, Any]] = ())[source]

Bases: collections.abc.Mapping[Any, Any]

A read-only mapping a frozen model can hold.

A Mapping rather than a dict subclass: a subclass only overrides the mutating methods, and dict.__setitem__(m, k, v) reaches past any override, so the design that LOOKS safer is the one with a hole in it. Nothing here can write, there is no inherited method that can, and the mapping hashes by content so a frozen model holding one stays hashable.

It reads as the dict it was built from – equality, membership, iteration order, dict(...) – and the ReadOnly annotation carries the serializer that puts a plain dict back on the wire, so the canonical composition JSON (the design-cache key) is unchanged.

__slots__ = ('_data',)
__getitem__(key: Any) → Any[source]
__iter__() → collections.abc.Iterator[Any][source]
__len__() → int[source]
__or__(other: collections.abc.Mapping[Any, Any]) → FrozenMapping[source]

A NEW mapping with other merged in; this one is unchanged.

__repr__() → str[source]
__hash__() → int[source]
__reduce__() → tuple[Any, ...][source]
dau_core.models.freeze_mapping(value: collections.abc.Mapping[Any, Any]) → FrozenMapping[source]
dau_core.models.ReadOnly
class dau_core.models.CheckedMixin[source]

check and replace for a frozen pydantic model.

check() → Self[source]

This model, re-validated: every field constraint and validator runs again, which is what model_copy skipped.

Validation goes through the field VALUES rather than through the instance, because an instance is only revalidated when its own config says so, and a polymorphic base cannot say so (see CheckedModel). Values, not a model_dump: a nested model stays a model, so nothing round-trips through JSON to be checked.

replace(**update: Any) → Self[source]

model_copy(update=...) that validates the result.

class dau_core.models.CheckedModel(/, **data: Any)[source]

Bases: CheckedMixin, ccflow.BaseModel

The base of every dau-core data model: frozen, and re-validated at every boundary it crosses.

revalidate_instances is what makes a copied model refuse at the next boundary: a parent built from a model_copy-mutated child validates the child again and rejects it. It is deliberately NOT on a polymorphic base such as dau_core.generators.CoreGenerator, because a field annotated with the base would revalidate a subclass instance against the base and reject the subclass’s own fields as extra. Those models get CheckedMixin and are checked explicitly.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

model_config

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].