[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"). |
|
Victor Schappert (@vcschapp) To confirm from the product side: yes, the design assumption is that extensions are useful standing alone. The framework the SC signed off on is to "enable the schema, don't host the data." For verified extensions, Overture standardizes and verifies the schema, but the data stays owned, hosted, and published by the provider, because it might be proprietary and/or on a refresh cycle faster than our release cadence (opening hours). The consumer does the join. The MVP path is literally "download the extension data, column-join to the reference map on GERS." On the ID requirement: from the verified-extension criteria Albi put together, the framework already assumes the extension schema itself includes a GERS ID field, with generated UUIDs filling in for features of the same type that aren't GERS-enabled. Requiring the target be Identified is enforcing what we've already committed to. It's your call (and Seth and Roel's call) about how to implement it. One more thing to keep in mind: this mechanism is columns on existing GERS features. Adding rows (new features without GERS ID) is the supplemental datasets path, which has its own process and hosting story. |
|
Victor Schappert (@vcschapp) On merge default I don't have a strong preference, but here's context that might be helpful: Part of our vision is for the UX to be "seamless, as if the extension's data is an integrated part of the core data, without requiring extra work from the developer to merge them." I don't think that's an argument for merge by default. We are trying to remove the burden on the user of figuring out which of their rows corresponds to which of ours. Installing an extension and applying it to a model are two different things. Some people will install a package just to check extension data on its own — that's the standalone case from my earlier comment — and they haven't asked for an extra field to appear on their Place model. Also, I'd want per extension opt in to exist no matter what the default is. We have two tiers, and community extensions are unreviewed on purpose and we tell people to check the data themselves before relying on it. If installing a package silently adds fields to every matching model, that check happens at install time instead of when someone decides whether to trust the data. None of that settles default-on vs. default-off for a single verified extension, which is your call. But I think per extension opt in should exist alongside any apply-all option, and merge behavior should be the same for verified and community extensions. Verified means Overture has reviewed the schema; it shouldn't change what the code does. |
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.