Skip to content

[FEATURE](codegen) Carry deprecation through extraction and render it in the Markdown reference - #757

Open
Seth Fitzsimmons (sethfitz) wants to merge 1 commit into
mainfrom
deprecation-carrier
Open

Seth Fitzsimmons (sethfitz) wants to merge 1 commit into
mainfrom
deprecation-carrier

Conversation

@sethfitz

@sethfitz Seth Fitzsimmons (sethfitz) commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Schema authors can already write Field(deprecated=...) and PEP 702's @deprecated(...) on a model, but nothing downstream sees it: the codegen IR drops it during extraction, so the generated Markdown reference renders a deprecated field exactly like a live one. A data consumer reading the docs has no way to know a field is on its way out.

This carries deprecation through extraction and renders it.

Extraction gains FieldSpec.is_deprecated / FieldSpec.deprecation_message and RecordSpec.deprecated. The field-level side normalizes all three forms Pydantic accepts on Field(deprecated=...): a bare True, a message string, and a deprecated(...) marker. The marker is also usable directly as Annotated metadata, and for that form Pydantic surfaces the marker object itself on field_info.deprecated rather than a string, so the prose has to be read off its .message attribute — a plain isinstance(..., str) test drops it.

The model-level side reads __deprecated__ off the class __dict__. Attribute lookup would walk the MRO and report a parent's deprecation on every subclass, which in this schema would put a banner on the three TransportationSegment subclasses and the five VehicleSelectorBase ones the moment a base is deprecated. typing_extensions.deprecated sets the attribute on our 3.10 floor; the stdlib warnings.deprecated sets the same one from 3.13, so the read does not change when the floor moves.

FieldSpec carries a flag and a message rather than a single str | None, because deprecated=True is deprecated-with-no-prose and str | None can only encode that as a sentinel string. RecordSpec.deprecated is str | None, since @deprecated()'s message argument is required.

The Markdown reference then renders both. A deprecated model gets a :::warning[Deprecated] admonition directly under the H1, above the docstring description — Docusaurus renders this reference, so an admonition is the richer form than bold text. A deprecated field gets a **Deprecated:** <message> note appended to its Description cell through the existing constraint-note mechanism, and a (deprecated) qualifier in its Type column alongside (optional), so the Type column reads `string` (optional, deprecated). The field note stays inline because an admonition is a block-level directive and the note lives in a table cell. A bare deprecated=True with no message renders a generic "This field is deprecated." rather than dropping the signal.

Enum members and bare type aliases are out of scope, per #674. Neither has a @deprecated hook to read: PEP 702's decorator applies to classes and functions, and an enum member is an instance of its enum class rather than a class of its own, while Annotated[...] and NewType produce objects the decorator does not accept. Marking either would need a project-specific convention rather than a native mechanism; #639 carries that open question.

Reference

  1. Closes [FEATURE] Carry deprecation through extraction and render it in the Markdown reference #674.
  2. [FEATURE] Mark schema fields and models as deprecated and propagate through docs, PySpark, and the changelog #639 — the parent issue this is one step of. Enum-member and type-alias deprecation stay there.
  3. [CHORE] Add a towncrier "deprecation" changelog type #675 — proposes a towncrier deprecation changelog type. Not landed, so the fragment here is 674.feature.md under the existing feature type; it would be reclassified if [CHORE] Add a towncrier "deprecation" changelog type #675 lands.
  4. [FEATURE] Opt-in strict deprecation mode in PySpark validation #676 and [FEATURE] Deprecation-manifest codegen target that emits changelog fragments in diff mode #677 — the two remaining consumers of these IR fields (strict PySpark validation, and a deprecation-manifest codegen target). Both read what this PR carries; neither is included here.

Checklist

Checklist of tasks commonly-associated with schema pull requests. Please review the relevant checklists and ensure you do all the tasks that are required for the change you made.

  1. Add relevant examples. — N/A: no schema change; this is codegen behaviour, exercised by inline test fixtures rather than by published examples.
  2. Add relevant counterexamples. — N/A, same reason.
  3. Update any counterexamples that became obsolete. — N/A: no published example or counterexample changes meaning here.
  4. Update in-schema documentation using plain English written in complete sentences, if an update is required. — N/A: no schema field changed.
  5. Update Docusaurus documentation, if an update is required. — Not required. The generated reference gains rendering for a state nothing in the published schema is currently in, so no page changes until something is actually marked deprecated.
  6. Review change with Overture technical writer to ensure any advanced documentation needs will be taken care of, unless the change is trivial and would not affect the documentation. — Worth a look at the two rendered strings, since they will appear verbatim in the public reference the first time a field is deprecated: the field note reads **Deprecated:** <message> and the bare-True fallback reads "This field is deprecated."

Documentation website

Update the hyperlink below to put the pull request number in.

Docs preview for this PR.

…markdown

FieldSpec had no slot for Pydantic's deprecated, so a field marked
Field(deprecated=...) extracted with the signal dropped -- before any
renderer saw it. RecordSpec had no equivalent for PEP 702's
@deprecated(...) on a model class either.

FieldSpec now carries is_deprecated/deprecation_message, normalized from
all three forms Pydantic admits: a bare True, a message string, or a
deprecated(...) marker usable directly as Annotated metadata (the marker
arrives as an object, so its .message needs unwrapping). The flag and
the message stay separate because deprecated=True is deprecated with no
prose, which a lone str | None can express only as a sentinel.

RecordSpec carries deprecated: str | None, read from the class __dict__
rather than by attribute lookup. __deprecated__ is a plain class
attribute, so getattr walks the MRO and reports a base's deprecation on
every subclass -- which would banner the three TransportationSegment
subclasses and the five VehicleSelectorBase ones the moment a base is
deprecated.

The Markdown renderer gives a deprecated model a :::warning[Deprecated]
admonition above its description, since Docusaurus renders this
reference and an admonition carries more than bold text. A deprecated
field gets a "Deprecated: <message>" note appended to its description
cell through the existing constraint-note appender and a (deprecated)
tag in its Type column; the note stays inline because an admonition is
block-level and a table cell cannot hold one. A bare deprecated=True
renders a generic message rather than dropping the signal silently.

Closes #674

Signed-off-by: Seth Fitzsimmons <seth@mojodna.net>
@sethfitz Seth Fitzsimmons (sethfitz) added the change type - minor 🤏 Minor schema change. See https://lf-overturemaps.atlassian.net/wiki/x/GgDa label Sep 22, 2026
@github-actions

Copy link
Copy Markdown

🗺️ Schema reference docs preview is live!

🌍 Preview https://staging.overturemaps.org/schema/pr/757/schema/index.html
🕐 Updated Sep 22, 2026 00:35 UTC
📝 Commit 15cab22
🔧 env SCHEMA_PREVIEW true

Note

♻️ This preview updates automatically with each push to this PR.

This branch was successfully deployed

1 active deployment
staging — 15cab22b Deployed Sep 22, 2026 by sethfitz via Deploy #569
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change type - minor 🤏 Minor schema change. See https://lf-overturemaps.atlassian.net/wiki/x/GgDa

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Carry deprecation through extraction and render it in the Markdown reference

1 participant