Schema projection

SchemaProjector compiles a schema for repeated use. A DataCanon Instance (DCI) is a value in DataCanon’s data model. Calling get_projection(input_dci) validates one Mapping DCI and returns a SchemaProjection containing the original input and processed output.

Compile once, project multiple inputs

from data_canon import as_dc_instance
from data_schemer.schema_projector import SchemaProjector

schema = {"variables": {
    "iterations": {"type": "Integer"},
    "method": {"optional": True, "default": "direct", "choices": ["direct", "iterative"]},
}}
projector = SchemaProjector(schema)
first = projector.get_projection(as_dc_instance({"iterations": 10}))
second = projector.get_projection(as_dc_instance({"iterations": 20, "method": "iterative"}))

assert first.data == {"iterations": 10, "method": "direct"}
assert second.data == {"iterations": 20, "method": "iterative"}

Schema compilation resolves field definitions and validates their metadata. Projection validates each input separately. A projector can also receive an initial input through the keyword-only input_data argument; its projection, data_dci, and data properties then refer to that initial projection.

Optional type constraints

type names a DataCanon Type (DCT) using its canonical name, such as Integer, Float, List, or Array. The names are case-sensitive. Omitting type accepts any DCI.

schema = {"variables": {
    "iterations": {"type": "Integer"},
    "matrix": {},
}}

The processor rejects a Float DCI for iterations, even if its value is 20.0. An Integer DCI is likewise rejected for a field constrained to Float. Boolean and Integer remain distinct DCTs. DataSchemer (DS) performs no implicit numerical coercion.

The matrix field can accept a nested List, an Array, or another DCI. The application constructor is responsible for the domain-specific interpretation.

Constrain an Array’s element type

An Array DCI has a portable Array element type in addition to its DCT. Declare array_type when the application requires a particular one:

import numpy as np

array_projector = SchemaProjector({"variables": {
    "matrix": {"type": "Array", "array_type": "float64"},
}})
node = as_dc_instance({"matrix": np.eye(2, dtype=np.float64)})
assert array_projector.get_projection(node).data_dci == node

This accepts only an Array DCI with portable type float64. A float32 Array fails, as does a nested List. Validation preserves the supplied shape and elements. Omitting array_type allows every portable Array element type.

Supported names are bool, int8, int16, int32, int64, uint8, uint16, uint32, uint64, float16, float32, float64, complex64, and complex128. The names are case-sensitive DC names; NumPy aliases such as f8 are rejected.

array_type requires an explicit type: "Array"; it does not infer a DCT. The constraint applies to defaults and supplied values, including through SCL. It follows normal field-property inheritance, copying, overrides, and removal. It is checked after final schema composition, so delete_properties can remove an inherited constraint. ExternalArray is a separate DCT and is not covered by this Array constraint.

Required fields and defaults

Fields are required by default. Mark a field optional: True to permit absence. Only optional fields may declare a default.

schema = {"variables": {
    "iterations": {"type": "Integer"},
    "tolerance": {"optional": True, "default": 1e-8},
    "label": {"optional": True},
}}

An absent tolerance receives the default; an absent label stays absent. Supplying a default does not infer a DCT constraint. Add "type": "Float" if tolerance should accept only Float DCIs.

Defaults are converted to DCIs during compilation. Explicit defaults can already be DCIs. A supplied value always takes precedence over the default, including Null or false when allowed by the field’s constraints.

The original input is preserved. Defaults appear only in the processed result.

Choices, dependencies, and conflicts

choices compares values using DCI equality. Choice values may be conventional objects or explicit DCIs; schema compilation converts them to DCIs. An Integer choice does not admit the corresponding Float DCI.

requires lists companion fields that must be present when the field is present. Schema-level conflicts lists groups in which at most one field may be present.

schema = {
    "variables": {
        "method": {"optional": True, "default": "direct", "choices": ["direct", "iterative"]},
        "restart": {"optional": True, "requires": ["checkpoint"]},
        "checkpoint": {"optional": True},
        "fresh_start": {"type": "Boolean", "optional": True},
    },
    "conflicts": [["restart", "fresh_start"]],
}

Defaults are present for requires and conflicts. A false Boolean or Null is also present; presence is determined by the field’s existence, not its truth value. Unknown fields are rejected at every projected schema level.

Original input, local output, and complete output

Property on SchemaProjectionMeaning
input_dciComplete input Mapping DCI, unchanged
data_dciProcessed fields at this level, retaining external field names
dataConventional conversion of the local fields
data_tree_dciProcessed Mapping DCI including supplied child-schema entries
data_treeConventional conversion of the complete processed tree
child_projectionsMapping of supplied child names to their projections
child_dataConventional local fields for each immediate child projection

Without child schemas, the local and complete processed views have the same contents. Supplied field order is preserved; added defaults follow in schema order. Child Mapping positions are preserved in the complete output.

Conventional views are computed on access and are not cached. Mutating a returned dictionary does not mutate the processed Mapping DCI. Conventional conversion follows DataCanon (DC)’s rules and may simplify values according to their destination types.

Deferred external resources and custom Records

Projection checks external-reference metadata without fetching the payloads. It also leaves custom Record conversion handlers uncalled. Accessing data, data_tree, or selected constructor arguments can retrieve external resources or invoke explicitly registered handlers.

Pass registry and source to the projector when needed:

# registry is a configured DataCanon ConversionRegistry.
# source is the namespace containing any referenced external resources.
projector = SchemaProjector(schema, registry=registry, source=source)

Those arguments are forwarded to DC conversion. Conversion failures are separate from successful schema validation.

Handle user errors

from data_schemer.errors import DSUserError

try:
    projection = projector.get_projection(input_dci)
except DSUserError as error:
    print(error.user_message())

See Errors for DCT diagnostics and exception classes. Continue with schema composition, child schemas, or application construction.