Changelog¶
The current CHANGELOG.md is maintained at the workspace root.
This page mirrors its content for the docs site.
Changelog¶
All notable changes to this project will be documented in this file.
The format follows Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]¶
[0.3.1] - 2026-05-01¶
Fixed¶
- Sum-sort encoders (closed Model-ref recursive aliases and TaggedUnion
field types) now route the chosen variant through
model_dump_jsoninstead of baremodel_dump, so any nestedtuple[Embed[T], ...]/dict[str, Embed[T]]/ arbitrary Model-containing structure inside the variant gets the JSON-safe walk. Previously such payloads raisedObject of type X is not JSON serializablefromjson.dumps. (#7) Embed[Inner]round-trip viamodel_dump_json/model_validate_jsonno longer asserts whenInnerhas atuple[T, ...](or anyfrom_json-coerced) field. The embed translation now routes inner JSON payloads throughmodel_validate_jsonso per-fieldfrom_jsonruns at every level (e.g. JSON list to tuple coercion). (#8)model_dumpevaluatesinner_kind == "sum"before theisinstance(value, Model)branch, so a Model variant of a sum-sort field is dumped with its constructor tag instead of collapsing to the variant's record dict. Previously a recursive alias whose Model arm was the current value lost its dispatch info on the dump side; the JSON round-trip then raisedunknown constructoron decode.embed_schema_uriwalks nested Model-containing fields viamodel_dump_jsonso the returned dict is always serialisable; previously failed withTypeErrorwhen the source instance had atuple[Embed[T], ...]field.
Changed¶
- Recursive Model-ref alias encoders prefer the
tupleconstructor over thelistconstructor when both arms are declared, even for Pythonlistinput. This keeps the encoded storage form canonical: round-tripping a Python list and re-encoding produces the same constructor name and the same storage string, restoringModelequality across the round-trip.
[0.3.0] - 2026-05-01¶
Added¶
- Recursive type aliases whose arms include
dx.Modelsubclasses now translate to a panproto-native closed sum sort. The motivating shape is aComponentalias mixing primitives, Models, and JSON-compatible containers; the alias name becomes the panproto sort, with oneOperationper arm declared as a constructor and the sort'sSortClosureset toClosedagainst that constructor list. Wire format for an arm value is a single-key JSON object whose key is the constructor name (matches panproto's term-of-closed-sort encoding). Lists round-trip as tuples to satisfy the tuple-basedFieldValueinvariant. Cycles in the value graph raiseValueErrorrather than recurse. (#2) dx.TaggedUnionsubclasses are now usable directly as a field value type.dict[str, Parameter],tuple[Parameter, ...], and a bareparam: Parameterannotation all work, with dispatch via the variant's discriminator field. The translation contributes a closed sum sort and per-variant constructor ops to the parent Model's Theory; the on-wire format is the variant's naturalmodel_dump(no envelope, since the discriminator is already in the payload). (#5)TypeTranslationgains optionalauxiliary_sortsandauxiliary_opstuples that let a translation contribute extra panproto sort and operation declarations to the parent Model's Theory. Currently produced by the recursive Model-ref alias and TaggedUnion translations;build_theory_specwalks them and dedupes by name. Empty for every other translation.inner_kind = "sum"joins the documented set ofTypeTranslationinner-kind values.model_dumproutes sum-sort fields through their encoder so the constructor-tag dispatch survives JSON round-trip.
Notes¶
- Recursive aliases that aren't pure JSON-shape and aren't
Model-ref-shape (e.g. one admitting
bytes,Decimal, or a non-Model class) continue to raiseTypeNotSupportedErrorwith a clear message. - Panproto's
Theory.sortsandTheory.opsattributes are list-typed at runtime, contrary to the shipped_native.pyistub which marks them as methods. Tests that introspect a built Theory treat them as data.
[0.2.0] - 2026-05-01¶
Added¶
- Bare PEP 695 type aliases now translate transparently.
type Kind = Literal["a", "b", "c"]is accepted as a Model field annotation; the classifier unwraps the alias before dispatching. (#2) - Union of primitive scalars is a translatable field type.
int | str,float | str,int | float | str, and the same unions insidedict[str, V]andT | Noneare accepted. The synthesised panproto sort name is"Union <a> <b> ..."in canonical order; the encoder JSON-encodes the value, and the decoder dispatches on the resulting Python type. (#2, #3) - JSON-shaped recursive type aliases translate to a single opaque
panproto sort named after the alias. The motivating shape is the
canonical
JsonValuealias (str | int | float | bool | None | list[X] | tuple[X, ...] | dict[str, X]withXself-referential). The encoded form isjson.dumps(value); the decoder parses and recursively coerces lists to tuples to satisfy didactic's tuple-basedFieldValuetype. Recursive aliases that are not JSON-shaped (e.g. one admittingbytes) raiseTypeNotSupportedErrorwith a clear message rather than failing silently. (#2)
[0.1.0] - 2026-05-01¶
Added¶
dx.Modelanddx.BaseModelwith frozen, immutable instances backed by a panproto Theory built lazily on first__theory__access.dx.field(...)descriptor with default, default_factory, alias, description, examples, deprecated flag, nominal-id flag, custom converters (PEP 712), and pass-through extras.dx.ModelConfigfor class-level configuration.dx.Ref[T]non-owning cross-vertex references.dx.Embed[T]owned sub-vertex composition.dx.TaggedUniondiscriminated unions with subclass dispatch.dx.Lens[A, B],dx.Iso[A, B],dx.Mapping[A, B]lens classes with composition, identity, andinverse()forIsochains.@dx.computedderived attributes for serialisation.dx.axiom("...")class-level axioms collected oncls.__class_axioms__.@dx.validates(field_name)Python-side validators.- JSON / pickle serialisation with per-field re-coercion.
dx.register_migration(...)/dx.migrate(...): schema migrations as registered lenses keyed by structural fingerprint of the Theory spec, robust to class renames and re-imports.dx.save_registry(path)/dx.load_registry(path): human-readable registry dumps for diagnostic and audit purposes.dx.Repository.init(...)/Repository.open(...): filesystem-backed panproto repository wrapper.addaccepts either a panprotoSchemaor adx.Modelclass (synthesised viaProtocol.from_theories). Branches, refs, tags, and log all exposed.dx.DependentLens: schema-parametric lens family wrappingpanproto.ProtolensChain(auto-generation, JSON round-trip, composition, fusion, instantiation).dx.resolve_backrefs(target, candidates, *, via)plusdx.ModelPoolfor in-memory backref resolution.- Theory colimit on multiple inheritance:
class D(B, C)builds the panproto pushout over the lowest common Model ancestor. dx.testing.verify_iso(iso, strategy)for property-test law checks.didactic-pydantic.from_pydantic(...): structural conversion of a Pydantic v2BaseModelto adx.Modelsubclass.didactic-pydantic.to_pydantic(...): the inverse direction, for FastAPI / OpenAPI consumers.- mkdocs documentation site with narrative guides and a full API
reference, validated in CI under
--strictmode. - Runnable examples in
examples/.
Known issues¶
- A handful of files carry per-file pyright suppressions for noise that the strict-mode checker cannot resolve without a deeper refactor. Each suppression is inline-commented with the rule list and the reason. Tracked in issue #1; the suppressions will be replaced with the real fixes in a v0.1.x patch release.
Notes¶
- Targets Python 3.14 and panproto 0.40+.
- The structural fingerprint normalises a Model's display name in the spec before hashing, so two structurally identical Models share a registry entry.