[FEATURE] Add an extension mechanism with discovery and codegen support - #655
[FEATURE] Add an extension mechanism with discovery and codegen support#655Roel Bollens (RoelBollens-TomTom) wants to merge 1 commit into
Conversation
Signed-off-by: Roel <75250264+RoelBollens-TomTom@users.noreply.github.com>
🗺️ Schema reference docs preview is live!
Note ♻️ This preview updates automatically with each push to this PR. |
|
My high-level discussion points. Claim: Extensions need to be useful standing alone. I think there's a requirement that extensions should be useful standing alone. Because the typical delivery model for extension on the data side will not be as a giant superparquet that clumps it all together; but instead probably just the extra fields keyed by GERS ID. The above claim at a high level is a Q for: TristanDiet-TomTom Albi (@wiedersberg) Dana Bauer (@danabauer). If the above claim is right, I think it leads to further questions:
Should there be an ID requirement for extensions? In my mind there should be an ID requirement for extensions. (I think this tracks Albi (@wiedersberg)'s product vision that the primary extension mechanism from an Overture standpoint is by GERS ID.) What if:
Merge to base model as default on with opt out, or default off with opt in? If I'm right in my claim that supporting data for extensions will often be delivered standalone, I think the merging should be default off, but with opt in. Maybe both granular opt in ("apply this extension") plus blanket opt in ("apply all extensions"). |
closes #634
This PR adds a model extension mechanism that lets packages contribute optional fields to models they do not own.
Extensions use the existing
overture.modelsentry-point group. Discovery identifies them throughExtendsmetadata.A model extension uses
@extends:Non-model types use
ExtendsinsideAnnotated, optionally behind aNewType:Both are registered like ordinary models:
During discovery, each extension becomes a standalone one-field wrapper model. This wrapper can validate extension-only payloads when extension data is stored separately from the base feature.
By default,
discover_models()also adds the optional extension field—named after its entry point—to every matching registered model.Target resolution
Targets can be model classes or model-bearing unions,
Annotated,NewType, andRootModelexpressions.Place | intis rejected.RootModelis treated as an alias for its root annotation. Targeting it targets the models in its root, while extending a registeredRootModelrebuilds the root with the extended models.RootModelover a scalar does not resolve to a model and cannot be used as a target.list[Place]anddict[str, Place]are not traversed.Stacked
@extendsdecorators and multipleExtendsdeclarations in oneAnnotatedlayer merge their targets. A subclass’s own declaration shadows an inherited declaration. Field-name collisions are skipped with a warning, while invalid entry-point names are rejected during wrapping.Codegen changes
Extracted fields record whether they came from an extension. Generated Markdown marks these fields with (extension), and union deduplication keeps native and extension fields separate even when their shapes match.
Extendsdeclarations are metadata rather than validation constraints, so they are excluded from constraint collection.Follow-up branches
Two branches build on this one:
extensions-examplesadds two example packages:overture-schema-extensions-operating-hours, a model extension targetingPlace, andoverture-schema-extensions-capacity, a scalarNewTypeextension targetingPlaceandBuilding. Both include valid and invalid GeoJSON examples and tests covering the complete wrap-and-merge path.extensions-typing-refactortypes the declaration API withExtensionTarget: TypeAlias = TypeForm[BaseModel]. It also raises the mypy floor to 2.2.0, the first release that supportsTypeFormwithout an experimental flag.Trying the examples
Extensions are ordinary packages connected through entry points, so they can be installed without changing application code:
This installs them into the current environment only.
The extension entries and extended
Placemodel are then available through the existing CLI:Changing
capacityto300violates theuint8range. Changingopento"9:00"violates the time pattern. Extension fields are validated in the same way as native fields.Each example package also includes ready-made valid and invalid GeoJSON files. Their tests cover the complete wrap-and-merge path, including validation of extension-only payloads against the standalone wrappers.