Model synthesis (inbound)

Reconstruct Model classes from panproto Theory specs: the faithful inverse of build_theory_spec. For a hand-written Model, the regenerated class's forward spec equals the original's.

Closed sum sorts round-trip: a dx.TaggedUnion field rebuilds into a union root with one variant per constructor, and a Model-ref recursive alias rebuilds into an equivalent type alias. Reconstruction is faithful at the Theory-spec level (structure), not at the level of Python annotations or field metadata; the function docstrings below spell out the recoverable / unrecoverable split.

didactic.api.model_from_spec

model_from_spec(
    spec: TheorySpec | object,
    *,
    name: str | None = None,
    base: type[Model] = Model,
    registry: dict[str, type[Model]] | None = None,
) -> type[Model]

Synthesise a didactic Model class from a single Theory spec.

PARAMETER DESCRIPTION
spec

Either a TheorySpec dict (the shape produced by build_theory_spec) or a panproto.Theory. A Theory is detected by the presence of a to_dict method and converted.

TYPE: TheorySpec | object

name

Class name override. Defaults to the spec's name field.

TYPE: str | None DEFAULT: None

base

Base class for the synthesised model. Defaults to Model. Overridden per-spec when the spec's extends names a model present in registry.

TYPE: type[Model] DEFAULT: Model

registry

Mapping of sort name to already-synthesised Model classes, used to resolve extends parents and Ref / Embed edge targets. Edge targets absent from the registry are left as string forward references so mutual recursion resolves once every model exists. The newly synthesised class is registered under its own name before return.

TYPE: dict[str, type[Model]] | None DEFAULT: None

RETURNS DESCRIPTION
type[Model]

The synthesised Model subclass.

RAISES DESCRIPTION
NotImplementedError

If the spec carries a discriminator-bearing closed sum sort whose discriminator key has been dropped (the panproto.Theory path); pass the build_theory_spec dict to recover it.

Notes

The reconstruction is faithful at the Theory-spec level only. See the module docstring for the recoverable / unrecoverable split.

didactic.api.models_from_specs

models_from_specs(
    specs: Iterable[TheorySpec | object],
    *,
    base: type[Model] = Model,
) -> dict[str, type[Model]]

Synthesise many models, sharing one registry for cross-references.

PARAMETER DESCRIPTION
specs

An iterable of TheorySpec dicts or panproto.Theory objects.

TYPE: Iterable[TheorySpec | object]

base

Base class for models that declare no extends parent.

TYPE: type[Model] DEFAULT: Model

RETURNS DESCRIPTION
dict[str, type[Model]]

Mapping of each model's sort name to its synthesised class.

Notes

Specs are topologically ordered by their extends dependency so a parent is always synthesised before its child. Ref / Embed edge targets that form cycles (mutually-referencing models) resolve through the shared registry: the first model to reference a not-yet synthesised target gets a string forward reference, which didactic's metaclass closes once both classes exist.

Ordering only sequences extends (inheritance) dependencies, not edge dependencies, because edge targets tolerate forward references while base classes do not.

didactic.api.model_from_theory

model_from_theory(
    theory: object, **kwargs: object
) -> type[Model]

Synthesise a Model from a panproto.Theory.

PARAMETER DESCRIPTION
theory

A panproto.Theory instance.

TYPE: object

**kwargs

Forwarded to model_from_spec (name, base, registry).

TYPE: object DEFAULT: {}

RETURNS DESCRIPTION
type[Model]

The synthesised Model subclass.