Tagged model serialization: let a document say which class it holds #888
d-chambers
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
DASCore's models do not say what they are when serialized, so a document can only be read back by something which already knows what it holds. Position supplies that knowledge inside a nested document, and nothing supplies it anywhere else. Three things want that fixed, one of them already broken today.
Writing this up as a direction rather than a plan; the parts marked open at the bottom are genuinely open.
1. A custom
PatchAttrssubclass does not survive a round tripThere are 17
PatchAttrssubclasses underdascore/io/, one per format that carries fields the base class does not model. None of them come back:Through DASDAE the values survive, because HDF5 keeps the numpy types, so today the loss is
isinstanceand the subclass's own validation. Through a text format the coercion goes as well: aDateTime64field arrives as a string and nothing re-coerces it, because the class that declared it is not the class being built.2. Some of those subclasses cannot round trip through a text format at all
Independent of the above, and a prerequisite for it.
AI4EPSPatchAttrscannot be reconstructed from its ownmodel_dump_json():magnitude: float = np.nanserializes tonull, and afloatfield then refusesNone. The pattern appears in roughly ten IO modules (odh4,ai4eps,ap_sensing,optodas,febus,sr4731,sintela,prodml,xml_binary). The inventory models avoided it by spelling an optional numberFiniteFloat | None, which serializes tonulland reconstructs; migrating to that is the fix, and it is a behavior change for anyone reading such a field expecting NaN.Naming the class is worth nothing until this is fixed: dispatch would find the right class, which would then refuse to build.
3. What is coming
Literaldiscriminator cannot enumerate what it does not know.Proposed direction
A tag naming the class, resolved through a registry.
A registered name, never an import path.
dascore.core.inventory.Cablewelds stored documents to today's module layout — moving a class between modules would break every file on disk — and resolving a dotted path out of user data is an arbitrary-import surface in a format meant to be read from someone else's archive. A registry lookup costs the same to write and has neither property.FiberIOis the in-house precedent.Namespaced value, bare name means dascore.
type: Cablekeeps working and stays hand-authorable, which matters because the inventory authoring format has atypecolumn in its union CSVs that a field crew fills in per row. A plugin registers under its own prefix,myplugin:Square.A plain
typekey, not a YAML tag. YAML's native tag syntax (!core/ndarray-1.0.0) has no JSON equivalent, and.jsonand.yamlare interchangeable spellings of one data model. A plain key survives both.A serializer concern on
DascoreBaseModel, not a field on every model. Amodel_serializerplus a before-validator can inject and check the tag universally, so it round trips everywhere without landing inmodel_fields,get_summary_df, or the generated reference tables. The two genuinely discriminated unions (_Resource,OpticalComponent) keep theirLiteralfield, which pydantic needs to pick a class before an object exists; the base-class check passes over them. This generalizes what already exists rather than replacing it.Version in the tag, if ever. No separate
versionfield: it would land in every nested object's dump, join__eq__/__hash__(which hash field values, per #878), and be per-class rather than per-instance if it means a schema version. Room fordascore:Cable-0.0.1later, with an absent version meaning the earliest — which needs nothing today and never requires assigning one retroactively.Where it lives.
dascore/utils/models.py, onDascoreBaseModel, since the first three customers arePatchAttrs, the inventory models, and ROIs. It is DASCore's tagged-model serialization, not an inventory feature borrowed later.Relationship to the inventory authoring format
Independent, and deliberately so. The authoring-format loader already requires
typein every object file and reads it as a property of the format, popping it for the models where it is not a discriminating field. Its entire coupling to this question is two lines and one test pinning which models carry the field. When universal tags land, the pop disappears and the pin inverts.Open questions
PatchAttrsget a tag? It is the first customer and the reason this is worth doing, but it is also on the hot path and feeds the index, so the blast radius is larger than for the inventory models.All reactions