Child schemas

A schema can declare child schemas to validate nested input Mappings. Each child schema is a free-standing definition with its own fields and constraints.

Child schemas describe nested data. Schema inheritance reuses definitions. A child schema need not inherit from its parent, and a derived schema need not be used as a child schema.

Declare and supply child data

from data_canon import as_dc_instance
from data_schemer.schema_projector import SchemaProjector

definitions = {
    "run": {
        "variables": {"name": {"type": "String"}},
        "child_schemas": ["solver", "report"],
    },
    "solver": {"variables": {
        "iterations": {"type": "Integer", "optional": True, "default": 20},
    }},
    "report": {"variables": {
        "filename": {"type": "String"},
    }},
}

projector = SchemaProjector(definitions, schema_name="run")
projection = projector.get_projection(as_dc_instance({
    "name": "example", "solver": {},
}))

assert projection.data == {"name": "example"}
assert projection.child_projections["solver"].data == {"iterations": 20}
assert projection.data_tree == {"name": "example", "solver": {"iterations": 20}}
assert "report" not in projection.child_projections

The supplied empty Mapping for solver receives defaults and validation. The absent report entry remains absent; its required filename is not checked until report data is supplied.

Presence is explicit

Input child entryProjection behavior
AbsentLeave it absent
Empty Mapping DataCanon Instance (DCI)Apply defaults, then validate and project
Nonempty Mapping DCIApply defaults to absent fields, then validate and project
Null or another non-Mapping DCIReject

A supplied Mapping can still fail because a required field has no value. Supplying a parent child Mapping does not activate absent entries for that schema’s children. The presence rule applies independently at every level.

child_schema_mode has no effect on projection. It controls only SchemaCommandLine (SCL) command navigation. Selecting a child command supplies its Mapping even without options, which requests the same default processing as an explicitly empty input Mapping.

Nest child schemas

definitions["solver"]["child_schemas"] = ["advanced"]
definitions["advanced"] = {"variables": {
    "threshold": {"type": "Float", "optional": True, "default": 0.01},
}}

projection = SchemaProjector(definitions, schema_name="run").get_projection(
    as_dc_instance({"name": "example", "solver": {"advanced": {}}})
)
advanced = projection.child_projections["solver"].child_projections["advanced"]
assert advanced.data == {"threshold": 0.01}

Validation stays local to each schema. An error for a nested DataCanon Type (DCT) mismatch includes its full data path, such as solver.advanced.threshold.

Inspect definitions and results separately

projection.child_projections contains per-input results for supplied children. Use a child projection’s data_dci, data_tree_dci, or get_instance() to work with that particular input.

projector.child_projectors contains reusable compiled child projectors. Compilation can happen during projection or through compile_child(name). Compiling a child does not supply input or activate it in later projections.

projector = SchemaProjector(definitions, schema_name="run")
projector.compile_child("solver").compile_child("advanced")
matches = projector.find_projectors_with_paths("advanced")
assert [path for path, compiled in matches] == [("solver", "advanced")]

The search covers already compiled projectors. It preserves distinct paths when a schema definition is reused in different branches, and does not fetch external content or convert DCIs to conventional objects.

SCL places an additional restriction on command definitions: a child schema cannot be directly declared by multiple parents. Standalone projection can reuse definitions across branches; SCL rejects ambiguous command ownership.

Structural restrictions

Every declared child name must identify an existing schema. A field name cannot also be a child-schema name at the same level. Child-schema cycles are rejected. A schema used as a child cannot declare non-None required_file metadata; required positional files belong to the SCL root.

The complete processed tree preserves the order of supplied child entries. For constructing objects from children, see Application construction.