OvertureMaps / OvertureMaps/schema
[ENHANCEMENT (BUG?)] Introspection breaks on `segment`: no supported way to ask a feature type what fields it has
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 213
- Forks
- 22
- Avg merge
- 3d 3h
- Merged PRs (30d)
- 31
Description
Type
Task
Scope
Multi-theme or Platform
Skillset
engineering
Description
The question
Someone installs the packages and asks a feature type which fields it has and which of them are required. For 14 of our 15 feature types, the standard Pydantic idiom gets the answer:
from overture.schema.buildings import Building
sorted(n for n, f in Building.model_fields.items() if f.is_required())
# ['geometry', 'id', 'theme', 'type', 'version']
model_fields and FieldInfo.is_required() are both public Pydantic API.
Where it breaks
None of this works for segment:
from pydantic import TypeAdapter
from overture.schema.transportation import Segment
Segment.model_fields
# AttributeError: model_fields
Segment()
# TypeError: Cannot instantiate typing.Union
TypeAdapter(Segment).validate_python({})
# 1 validation error for tagged-union[function-after[__validate_ext_fields__(),
# function-wrap[__validate_with_geo_json_support__()]], ...
Segment is a discriminated union alias over RoadSegment, RailSegment, and WaterSegment, so it isn't a model class and has no fields of its own.
Why Pydantic doesn't handle this the way we might want
TypeAdapter is the supported way to work with a union alias, and it does answer some questions. TypeAdapter(Segment).json_schema() returns a document with a three-branch oneOf, and validate_python and validate_json both work fine.
But a TypeAdapter exposes only validation, serialization, and JSON Schema. There's no model_fields on it and no field-level introspection of any kind. So the only structural answer Pydantic can give us for segment is a JSON document, and a document you feed to another program isn't an answer to "what fields does this type have." It's also several hundred lines for one feature type, since every enum and shared structure gets inlined.
Workaround
Import the three segment types and ask each one:
from overture.schema.transportation import RoadSegment, RailSegment, WaterSegment
for cls in (RoadSegment, RailSegment, WaterSegment):
print(cls.__name__, sorted(n for n, f in cls.model_fields.items() if f.is_required()))
# RoadSegment ['class_', 'connectors', 'geometry', 'id', 'subtype', 'theme', 'type', 'version']
# RailSegment ['class_', 'connectors', 'geometry', 'id', 'subtype', 'theme', 'type', 'version']
# WaterSegment ['connectors', 'geometry', 'id', 'subtype', 'theme', 'type', 'version']
This works only if you already know those three types exist and what they're called. Getting to them from Segment takes two rounds of typing.get_args, and they come back as Annotated aliases whose __name__ is 'Annotated', so it takes a third round to recover the class names. Nobody will discover that.
The three also disagree, which is the interesting part. class is required on road and rail segments and doesn't exist on water segments. Seven fields are required on all three, and twelve of the twenty-three fields appear on all three.
Proposal
A small introspection API in overture-schema-system, alongside discover_models and filter_models. It SHOULD accept a model class, a union alias, or a registered type name, so that RoadSegment, Segment, and "segment" are all valid arguments:
required_fields(Building) # ('id', 'geometry', 'theme', 'type', 'version')
required_fields("segment") # see open question 1
fields("place") # every field, with required flag, type, and constraints
The codegen extraction layer already computes most of this to render the markdown reference, so this may be a matter of exposing what exists rather than writing something new.
Open questions
- What does a union return? For
segment, the fields common to all three types and the fields present on any of them differ by exactly one,class. The common set answers "what can I rely on for any segment." A per-type breakdown answers "what exists." We MAY need both. - Where does it live?
overture-schema-systemnext to discovery, oroverture-schema-validation? - Should the CLI answer this too? A user at the shell has no way to ask at all right now.
overture-schema describe segmentmay be the more valuable half of this issue, since it serves people who don't write Python and gives the docs something to link to. - How far does it go? Required fields only, or fields plus constraints plus enum values? The narrow version is easy to agree on and easy to ship. The broad version overlaps codegen and needs a design pass.
Verified against 2.0.0 from PyPI, Pydantic 2.13.5.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reviewing overture-schema-system's discover_models and filter_models, then inspect the codegen extraction layer that renders the markdown reference. Compare the proposed behavior for model classes, union aliases, and registered names, especially the open question about common versus per-type union fields. Done means the API scope and location are agreed and the selected introspection behavior is exposed; add the CLI only if that scope is chosen.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend-api-design, developer-experience
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100