OvertureMaps / OvertureMaps/schema
[ENHANCEMENT](schema) Remove the non-null defaults from the schema models
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
Companion to #695, which adopts the policy that Overture schema fields carry no non-null default. That issue is the why; this one removes the ones that exist.
Eleven fields carry a non-null default. They resolve to four declaration sites in four files -- the rest are inherited and re-reported by subclasses.
#692 already did this once, removing the connectors = [] default and making the field required. These four are what remain.
Verified on origin/vnext at b893ab09, which carries #692; origin/main does not yet.
| Declaration | Default | Fields it produces | Becomes |
|---|---|---|---|
common/level.py:23 Stacked.level |
0 |
6 | Level | None = None |
transportation/segment/_common.py:318 SpeedLimitRule.is_max_speed_variable |
False |
1 | bool | None with no default |
base/land.py:142,145 Land.class_, Land.subtype |
LandClass.LAND, LandSubtype.LAND |
2 | required |
base/water.py:141,148 Water.class_, Water.subtype |
WaterClass.WATER, WaterSubtype.WATER |
2 | required |
Land and Water class and subtype
Published data already satisfies the tightened constraint, the same justification #692 used for connectors. In release 2026-08-19.0, one partition per type:
| Type | Rows | subtype null |
class null |
|---|---|---|---|
| land | 1,174,629 | 0 | 0 |
| water | 1,985,644 | 0 | 0 |
No published feature relies on the default.
subtype = 'water' reads as the identity of the model already chosen, which is what makes the default look harmless. It is not an identity: in the water partition it is one of 8 values, covering 1,200,960 of 1,985,644 rows, and in land, class = 'land' covers 68,679 of 1,174,629. They are catch-all values that happen to be common. Required is the right shape, not defaulted.
SpeedLimitRule.is_max_speed_variable
In the segment partition measured, false appears in 0 of 601,764 speed limit rules; 601,497 are null and 267 are true. Absent already means "not a variable speed corridor" to every reader of the release, and the description should say so rather than leaving a default to imply it.
With the default in place, absent, null, and false are three encodings of two meanings, and which pair collapses depends on whether you came through Pydantic or through Parquet.
After removing a default, fix the description
A default often carries meaning that nothing else states. Remove it without updating the field description and that meaning is gone silently. level and is_max_speed_variable both need a description of what absence means; class_ and subtype need nothing, since becoming required leaves no absence to explain.
The level sentence belongs on the Stacked.level field annotation, not on the Level NewType. Level is also the type of LevelRule.value (transportation/segment/_common.py:135), which is required -- absence is impossible there, so a NewType-level sentence about absence renders as nonsense into the segment JSON Schema and Markdown reference.
The description is what resolves this, and that is the whole point of the step. An absence with nothing said about it is itself a default -- the reader supplies a meaning, and different readers supply different ones. Removing a default without describing absence relocates the ambiguity instead of removing it.
is_max_speed_variable is where that bites. Absence could mean false, or could mean unknown, and the two make different claims about roads nobody surveyed. The published data bears on it without settling it: 267 rules say true, 601,497 say nothing, and none say false, so the publisher records only positives -- and that is not an artefact of the Pydantic default, since the data pipeline never consults it. Writing the description is the act of deciding, so it needs whoever knows what the flag asserts rather than an inference from the counts.
Breaking change classification
No consumer sees a difference: the release already carries what the models will require, and nobody reads Overture data through the Python models today.
The model contract still changes, so this is a breaking package change and lands in the 2.0.0 window.
| Change | Effect on the release | Change to the model contract |
|---|---|---|
level default dropped |
none; already null on all but a handful of rows | input that omits level parses to None instead of 0 |
class_ / subtype required |
none; always written | constructing Land() / Water() without those arguments raises |
is_max_speed_variable default dropped |
none; false never written |
input that omits it parses to None instead of False |
Nothing changes for anyone querying Parquet. Requiredness never reaches the emitted schema -- schema_builder.py:140 renders every field as StructField(name, type, True), nullable regardless of is_required -- so a required field is a validation rule, not a physical constraint. The columns and their nullability are identical before and after. The only difference is that validation now reports a null where it previously accepted one, and no published row has one.
Downstream effects
- Markdown reference.
class_andsubtypemove from Optional to Required for Land and Water. Expected; confirm the rendered tables read correctly. - Generated PySpark. Required-field checks appear for the four newly-required fields. Regenerate and read the diff rather than accepting it.
- JSON Schema output. The
defaultannotations disappear, andclass/subtypejoin therequiredarrays. Confirm the annotations are gone rather than relocated.
Acceptance criteria
- No schema field carries a non-null default.
- Every meaning a removed default carried is stated in the field description, in the same change.
- Changelog fragments filed under each affected package.
- A published partition from
2026-08-19.0per touched type validates clean against the changed models, withoverture-validate --skip-schema-checkto clear the unreleased-field gate. -
make checkgreen; regenerated PySpark and Markdown diffs reviewed, not just regenerated.
Related
- #695 -- the policy this implements.
- #692 -- removed the
connectors = []default and made the field required; merged tovnext, and the precedent for these four. - #669 -- the
connectors = []correctness bug that motivated #692. - #640 --
defaultis theField()kwarg decision this pair of issues resolves. - #354 -- the
operating_statusprecedent: default removed, replacement never landed.
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 with common/level.py, transportation/segment/_common.py, base/land.py, and base/water.py, then inspect schema_builder.py:140 and the generated Markdown, PySpark, and JSON Schema diffs. Confirm the affected descriptions and requiredness, validate a 2026-08-19.0 partition with overture-validate --skip-schema-check, and run make check; done means the acceptance criteria and reviewed generated outputs pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- data
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100