Application construction
DataSchemer validates input DataCanon Instances (DCIs) and applies schema defaults. Application
constructors receive instances of conventional types, such as int, list,
and NumPy arrays. A constructor can then perform the transformations its
application requires.
Keep validation and application transformations separate
import numpy as np
from data_canon import as_dc_instance
from data_schemer.schema_projector import SchemaProjector
class Calculation:
def __init__(self, matrix, iterations, tolerance=1e-8):
self.matrix = np.asarray(matrix, dtype=np.float64)
self.iterations = iterations
self.tolerance = tolerance
schema = {
"variables": {
"coefficients": {"code_alias": "matrix"},
"iterations": {"type": "Integer"},
"tolerance": {"type": "Float", "optional": True},
},
}
input_dci = as_dc_instance({
"coefficients": [[1, 0], [0, 1]],
"iterations": 20,
})
projection = SchemaProjector(schema).get_projection(input_dci)
calculation = projection.get_instance(Calculation)
assert calculation.matrix.shape == (2, 2)
assert calculation.matrix.dtype == np.dtype("float64")
assert calculation.tolerance == 1e-8
assert projection.data_dci == input_dci
The schema requires an Integer DCI for iterations. The untyped coefficients
field can accept a nested List DCI or an Array DCI. Conventional conversion
produces the corresponding objects; Calculation chooses its own array dtype.
That transformation does not alter the processed DCI. The code_alias maps the
external field name coefficients to constructor argument matrix.
Schema defaults and constructor defaults
A schema default becomes part of the processed DCI and is passed to a matching constructor argument. An absent optional field without a schema default remains absent, allowing the constructor’s own default to apply. Explicitly supplied values, including Null, take precedence over defaults when allowed by the schema.
In the example, tolerance remains absent from projection.data_dci, while the
constructed object uses Calculation’s default of 1e-8. Adding
"default": 1e-6 to the field definition would put that value in the processed
DCI and pass it to the constructor instead.
A default does not infer a type constraint:
variables = {"value": {"optional": True, "default": 1}}
The default is an Integer DCI, but an explicitly supplied value may have any
DataCanon Type (DCT). Add "type": "Integer" to constrain both supplied values and the default.
DataSchemer (DS) never uses constructor annotations or constructor defaults to infer DCT
constraints. A constructor default cannot satisfy a required schema field;
schema validation happens before application construction.
Constructor argument selection
get_instance() accepts a class or callable factory with an inspectable
signature. It selects matching local fields after applying code_alias,
and passes arguments by keyword. Keyword-only parameters are supported. An
omitted parameter retains its callable default.
Other local fields are ignored unless the callable accepts **kwargs, in which
case additional fields are passed using their aliases. An unused external
reference is not materialized merely to construct an object. Similarly, a
registered Record conversion handler runs only if its value is selected for
conversion.
Signature binding happens before conventional conversion. Missing constructor
arguments raise DSUserError without first retrieving external payloads or
calling Record conversion handlers. A required positional-only argument needs
a wrapper that accepts keyword arguments:
obj = projection.get_instance(lambda matrix, iterations: factory(matrix, iterations))
A callable without an inspectable signature also needs an inspectable wrapper. Exceptions raised by the application constructor or by conventional conversion propagate to the caller; DS does not disguise them as schema validation errors.
Child projections
Root construction uses local fields only, even if the root constructor accepts
**kwargs. Child data is not automatically passed to a root constructor, nor
does DS choose classes for child schemas.
Construct a child object explicitly from its projection:
solver_projection = projection.child_projections["solver"]
solver = solver_projection.get_instance(Solver)
The application decides how to assemble independently constructed objects.
data_tree_dci and data_tree remain available when the application wants the
complete nested data instead. A child projection exists only when its Mapping
was supplied; an empty supplied Mapping receives defaults and validation.
SchemaCommandLine
Passing target_class=Calculation to SchemaCommandLine uses the same
construction contract and makes the result available through target_obj.
Standalone projection and SchemaCommandLine (SCL) share one implementation of argument selection,
alias handling, deferred conversion, and signature checking.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.