Skip to content

geotiff: deprecate geographic-CRS GeoKey attrs in DataArray.attrs (#1984)#2011

Merged
brendancol merged 4 commits into
mainfrom
1984-pr7-deprecate-geographic-geokeys
May 18, 2026
Merged

geotiff: deprecate geographic-CRS GeoKey attrs in DataArray.attrs (#1984)#2011
brendancol merged 4 commits into
mainfrom
1984-pr7-deprecate-geographic-geokeys

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Summary

PR 7 of the issue #1984 attrs-contract plan. The PR 6 locking test (merged as #2004) proved that six geographic-CRS GeoKey-derived attrs never survive a write -> read round-trip: the writer's xrspatial/geotiff/_geotags.build_geo_tags only emits the primary GEOKEY_GEOGRAPHIC_TYPE plus citation, never the secondary geographic GeoKeys these attrs are derived from.

Since the contract documented the attrs as "preserved when the writer can reconstruct from canonical state" but the writer demonstrably cannot, this PR deprecates them. The deprecated attrs are:

  • crs_name (GTCitation / ProjCitation GeoKey; writer always overwrites from crs_wkt-derived data)
  • geog_citation (GeogCitationGeoKey)
  • datum_code (GeogGeodeticDatumGeoKey)
  • angular_units (GeogAngularUnitsGeoKey)
  • semi_major_axis (GeogSemiMajorAxisGeoKey)
  • inv_flattening (GeogInvFlatteningGeoKey)

This PR is the warning-only phase: the attrs still land in DataArray.attrs on read, but each emission fires a DeprecationWarning. Removal is scheduled for a later release. The contract-version bump and actual removal will come in a follow-up.

Changes

  • xrspatial/geotiff/_attrs.py: route the six attrs through a new _emit_deprecated_geographic_geokey helper in _populate_attrs_from_geo_info; move the attrs out of the best-effort pass-through docstring section and into a new "Deprecated" section.
  • xrspatial/geotiff/tests/test_attrs_pr7_deprecate_geographic_1984.py: 15 tests covering per-attr warning emission, per-attr value still landing in attrs, no-warning-when-absent guard, and warning-text shape.
  • CHANGELOG.md: deprecation note under Unreleased.

Warning text

xrspatial.geotiff: attrs[<name>] is deprecated; the writer cannot reconstruct it from the canonical CRS so it will not round-trip. It will be removed in a future release. See issue #1984.

Fires as DeprecationWarning with stacklevel=2.

Test plan

  • pytest xrspatial/geotiff/tests/test_attrs_pr7_deprecate_geographic_1984.py -v (15 passed)
  • pytest xrspatial/geotiff/tests/test_attrs_contract_passthrough_1984.py xrspatial/geotiff/tests/test_attrs_contract_canonical_1984.py -v (35 passed, no regressions)
  • Full pytest xrspatial/geotiff/tests/ sweep: 3541 passed, 8 pre-existing GPU failures unrelated to this change.

Refs

@github-actions github-actions Bot added the performance PR touches performance-sensitive code label May 18, 2026
@brendancol
Copy link
Copy Markdown
Contributor Author

PR Review: geotiff: deprecate geographic-CRS GeoKey attrs

Blockers (must fix before merge)

None.

Suggestions (should fix, not blocking)

  • xrspatial/geotiff/_attrs.py:75-85 — the new "Deprecated" docstring section says callers should "use crs / crs_wkt instead." For some of these attrs the migration is non-trivial: inv_flattening / semi_major_axis need pyproj.CRS(...).ellipsoid.inverse_flattening / .semi_major_metre, and datum_code needs pyproj.CRS(...).datum.to_epsg(). A short migration recipe in the docstring (or in docs/source/user_guide/attrs_contract.rst) would close the loop for users who get the warning and don't know where to look next.
  • xrspatial/geotiff/tests/test_attrs_pr7_deprecate_geographic_1984.py:51-58_DEPRECATED_CASES has (attr_name, field_name, value) but attr_name == field_name in every row. Either drop the duplicate column or add a comment explaining when they would differ; today it just looks like a typo waiting to happen.
  • xrspatial/geotiff/_attrs.py:96stacklevel=2 from inside _emit_deprecated_geographic_geokey points at _populate_attrs_from_geo_info, not at the user's open_geotiff call. Since DeprecationWarning is silenced by default for library code this only matters for tests today, but if the category later changes to FutureWarning (visible to end users), the stacklevel needs to walk past the backend frames to reach user code.

Nits (optional improvements)

  • xrspatial/geotiff/tests/test_attrs_pr7_deprecate_geographic_1984.py:74-84test_deprecated_cases_cover_all_attrs checks that the case list and the module-level tuple stay in sync. A len(_DEPRECATED_CASES) == len(_DEPRECATED_GEOGRAPHIC_GEOKEY_ATTRS) extra line would catch a duplicate-entry mistake that the set comparison silently absorbs.
  • The helper _emit_deprecated_geographic_geokey is naming-scoped to "geographic", but sibling PRs geotiff: deprecate vertical-CRS GeoKey attrs in DataArray.attrs (#1984) #2010 / geotiff: deprecate projected-CRS GeoKey attrs in DataArray.attrs (#1984) #2013 deprecate non-geographic attrs with identical mechanics. If this PR lands first, consider promoting the helper to a generic _emit_deprecated_geokey_attr(attrs, name, value, *, category) so the other two slices can reuse it without renaming.

What looks good

  • Helper + constant + warning-text factory is exactly the right shape. The other two PR 7 slices should adopt the same pattern.
  • Driving _populate_attrs_from_geo_info directly with a synthetic GeoInfo is shorter, faster, and tighter than rebuilding TIFFs on disk. Sibling PRs that go full-stack should consider this approach.
  • test_warning_message_format pins the canonical wording so a stylistic edit elsewhere does not silently break consumers that grep on the message.
  • test_no_warning_when_field_absent is the right guard against an unconditional warning that would spam every read of every file.

Checklist

brendancol added a commit that referenced this pull request May 18, 2026
PR review on #2014 flagged that the three colormap deprecation sites
copy-paste the same nine-line ``warnings.warn(...)`` block with only
the attr name and migration hint varying. Sibling PRs #2010 / #2013
have the same shape; PR #2011 already factors a slice-specific
helper. Introduce a generic helper in ``_attrs.py`` so all four PR 7
slices can share it.

``_emit_deprecated_geokey_attr(attrs, name, value, *, reason,
migration=None)`` builds the canonical warning text shape:

    xrspatial.geotiff: attrs['<name>'] is deprecated; <reason>.
    [<migration>.] It will be removed in a future release. See
    issue #1984.

then sets ``attrs[name] = value`` (read-side emission still alive for
the deprecation window). ``stacklevel=2`` lands the warning at the
caller of ``_populate_attrs_from_geo_info``; ``DeprecationWarning`` is
silenced for library code by default in Python's filters, so this is
mainly visible to test runners and developers who opt in.

The colormap-variants deprecation now routes ``cmap`` and the two
``colormap_rgba`` branches (matplotlib present + ImportError fallback)
through the helper, replacing 27 lines of copy-paste with three
calls. The emitted warning text is byte-identical to the previous
inline strings, so the existing tests (``pytest.warns`` plus the
TestPalette / test_metadata_round_trip filter regexes) keep passing
unchanged.

Sibling PRs #2010 and #2013 can adopt the helper on rebase; #2011's
existing geographic-specific helper can be inlined onto this one in
a follow-up.

Test plan:
- pytest xrspatial/geotiff/tests/test_attrs_pr7_deprecate_colormap_variants_1984.py -- 4 passed.
- pytest xrspatial/geotiff/tests/test_attrs_contract_*_1984.py xrspatial/geotiff/tests/test_metadata_round_trip_1484.py -- 57 passed.
- pytest xrspatial/geotiff/tests/test_features.py::TestPalette -- 7 passed.
)

The six geographic-CRS GeoKey-derived attrs (crs_name, geog_citation,
datum_code, angular_units, semi_major_axis, inv_flattening) were
documented in the attrs contract as best-effort pass-through, but the
PR6 locking test (#2004) showed they never round-trip: build_geo_tags
only emits the primary GEOKEY_GEOGRAPHIC_TYPE, so the secondary
GeoKeys these attrs come from are never written back.

PR 7 of the issue #1984 plan deprecates the read-side emission. Each
emission now fires a DeprecationWarning with a stable wording. The
values still land in attrs for one release cycle so external callers
can migrate; removal is scheduled for a later release.

Changes:

- _attrs.py: add _DEPRECATED_GEOGRAPHIC_GEOKEY_ATTRS tuple,
  _deprecated_geographic_geokey_warning text helper, and
  _emit_deprecated_geographic_geokey emission helper. Route the six
  attrs through the helper in _populate_attrs_from_geo_info.
- _attrs.py docstring: move the six attrs out of the best-effort
  pass-through tier and into a new "Deprecated" tier section.
- New test_attrs_pr7_deprecate_geographic_1984.py with parametrised
  warning assertions, still-emits-during-deprecation assertions, a
  no-warning-when-field-absent guard, and a warning-text shape check.
- CHANGELOG.md: document the deprecation under Unreleased.
Address review S2 + N1 on PR #2011.

S2: drop the redundant ``field`` column from ``_DEPRECATED_CASES``.
Every entry had ``attr == field`` because every deprecated attr is
stored on ``GeoInfo`` under the same name it lands in ``attrs`` under.
Collapse to ``(attr, value)`` and update the two parametrised tests
to match.

N1: add ``test_deprecated_cases_has_no_duplicates`` so a duplicate
row in ``_DEPRECATED_CASES`` can no longer be silently absorbed by
the set-based coverage check.

Test count goes 15 -> 16.
…1984)

Address review S3 on PR #2011.

The geographic-GeoKey deprecation helper passed ``stacklevel=2`` to
``warnings.warn``, which pointed at ``_populate_attrs_from_geo_info``
rather than the user's ``open_geotiff(...)`` call. The exact distance
to the user frame also varies by backend: the numpy path adds three
internal frames, the dask path adds four. A fixed integer can only be
right for one of those paths.

Replace the fixed level with a frame walk
(``_stacklevel_to_external_caller``) that skips every
``xrspatial.geotiff*`` frame -- minus ``xrspatial.geotiff.tests`` so
the unit tests can still pose as external callers -- and returns the
matching ``stacklevel``. The new
``test_warning_stacklevel_points_at_caller_file`` pins the
attribution.

Today the warning category is ``DeprecationWarning``, which Python
silences by default for library code, so the stacklevel mostly
affects test output. Get it right now so a later switch to
``FutureWarning`` does not surface the warning as if it came from
``_attrs.py``.

Scope note: the inline ``warnings.warn`` calls for the projected and
vertical tiers (still pass ``stacklevel=2``) are left alone in this
commit; the next commit folds them into the generic helper and picks
up the new walk transparently.

Test count goes 16 -> 17.
Address review S1 + N2 on PR #2011.

N2: promote ``_emit_deprecated_geographic_geokey`` to a generic
``_emit_deprecated_geokey_attr(attrs, name, value, *, reason)`` shared
by the geographic, projected, and vertical deprecation tiers
(introduced by sibling PRs #2010 and #2013 as inline
``warnings.warn`` calls during the rebase onto current main). The
per-category reason clauses live in two module-level constants
(``_GEOKEY_DEPRECATION_REASON_HORIZONTAL_CRS`` for geographic /
projected; ``_GEOKEY_DEPRECATION_REASON_VERTICAL_CRS`` for vertical),
which keeps the wording in lockstep and lets the existing
``test_warning_message_format`` assertion continue to pin the
canonical text verbatim. ``_emit_deprecated_geographic_geokey`` is
kept as a thin shim so the geographic call sites stay readable; the
projected and vertical sites in ``_populate_attrs_from_geo_info``
now call the generic helper directly and inherit the
external-caller stacklevel walk from commit B for free.

S1: the rebased docstring "Deprecated" section already grew a
migration recipe in the conflict resolution; this commit additionally
mirrors the recipe into ``docs/source/user_guide/attrs_contract.rst``
so the user-facing docs carry the same pyproj one-liners
(``crs.ellipsoid.inverse_flattening``, ``crs.datum.to_epsg()`` etc.)
that the docstring lists.

No new tests. The existing 17 deprecation tests plus the 42 contract
tests all still pass; the warning text and category are unchanged.

Sibling PRs #2010 and #2013 are already merged on main, so no
sibling rebase is needed -- this PR is the consolidation point.
@brendancol brendancol force-pushed the 1984-pr7-deprecate-geographic-geokeys branch from 7b65bef to caf0967 Compare May 18, 2026 04:07
@brendancol
Copy link
Copy Markdown
Contributor Author

Follow-up review fixes pushed. Rebased onto current main first so the docstring "Deprecated" section and the CHANGELOG "Unreleased" bullets merge cleanly with sibling PRs #2010 (vertical) and #2013 (projected); both are already on main.

Commits on top of the rebased PR commit (0d79dbf):

  • 05b0cff S2 + N1: drop the redundant field column from _DEPRECATED_CASES (every row had attr == field), and add test_deprecated_cases_has_no_duplicates so a duplicate row can no longer be absorbed by the set-based coverage check.
  • 0b5fbef S3: replace the fixed stacklevel=2 with _stacklevel_to_external_caller(), which walks past every xrspatial.geotiff* frame (excluding the test subpackage) and returns the matching level. Different backends produce different chain depths (numpy: 3 frames, dask: 5 frames), so any single integer would only be right for one path. test_warning_stacklevel_points_at_caller_file pins the attribution to the user's file.
  • caf0967 S1 + N2: promote the helper to _emit_deprecated_geokey_attr(attrs, name, value, *, reason). The geographic, projected, and vertical tiers now all go through the generic helper and inherit the corrected stacklevel for free. Per-category reason clauses live in two module-level constants. _emit_deprecated_geographic_geokey is kept as a thin shim. The migration recipe added to the module docstring during the rebase is also mirrored into docs/source/user_guide/attrs_contract.rst.

Sibling PRs #2010 / #2013: both already on main, so no rebase needed there. This PR is the consolidation point for the helper.

Test counts: deprecation suite goes 15 -> 17 (added 1 length-assert, 1 stacklevel pin); projected + vertical + canonical + pass-through contract suites unchanged (42 tests, all green).

Nothing skipped.

@brendancol brendancol merged commit 20751b5 into main May 18, 2026
4 of 5 checks passed
brendancol added a commit that referenced this pull request May 18, 2026
PR #2011 (geographic-CRS GeoKey deprecation) merged to main after my
first merge commit (ac908ab) and reshaped both the module docstring
and the helper layout. This commit reconciles the colormap-variants
slice with that landing.

xrspatial/geotiff/_attrs.py (helper layout)
    Origin/main now ships:
    * ``_DEPRECATED_GEOGRAPHIC_GEOKEY_ATTRS`` tuple of attr names.
    * Two per-category reason constants
      (``_GEOKEY_DEPRECATION_REASON_HORIZONTAL_CRS`` and
      ``_GEOKEY_DEPRECATION_REASON_VERTICAL_CRS``).
    * ``_deprecated_geokey_warning(name, *, reason)`` text builder
      with the canonical wording "...{reason} so it will not
      round-trip..." pinned by sibling tests.
    * ``_deprecated_geographic_geokey_warning(name)`` backward-compat
      shim that fixes the reason to the horizontal-CRS clause.
    * ``_stacklevel_to_external_caller()`` stack walker that walks
      past every ``xrspatial.geotiff*`` frame so deprecation warnings
      point at the user's ``open_geotiff(...)`` call rather than at
      internal read paths. Materially better than a fixed
      ``stacklevel=`` constant because the chain depth differs by
      backend (numpy: 3 frames; dask: 5).
    * ``_emit_deprecated_geokey_attr(attrs, name, value, *, reason)``
      emitter that pairs the text builder with the stack walker.
    * ``_emit_deprecated_geographic_geokey(attrs, name, value)``
      geographic-tier shim.

    HEAD had ``_DEPRECATED_COLORMAP_REASON`` / ``_DEPRECATED_CMAP_MIGRATION``
    / ``_DEPRECATED_COLORMAP_RGBA_MIGRATION`` constants plus an
    ``_emit_deprecated_attr(attrs, name, value, *, reason,
    migration=None)`` emitter with a fixed ``stacklevel=3``. The
    colormap helper needed migration-text support (e.g. "Reshape
    attrs['colormap'] to (n_colors, 3) ...") that the GeoKey-tier
    fixed-template helper does not support.

    Resolution: kept both helpers, switched the colormap helper from
    ``stacklevel=3`` to ``_stacklevel_to_external_caller()`` so all
    four PR 7 slices attribute warnings consistently. The two
    helpers live side-by-side because their warning-text contracts
    are pinned by separate test suites; convergence is left as a
    follow-up.

xrspatial/geotiff/_attrs.py (module docstring "Deprecated" section)
    Origin/main restructured the section into three per-axis
    subsections (Geographic-CRS / Projected-CRS / Vertical-CRS) plus
    a pyproj migration recipe. Added a fourth subsection
    "Colormap variants (different root cause: photometric gate, not
    GeoKey)" with the two colormap-derived entries.

Test plan:
- pytest xrspatial/geotiff/tests/test_attrs_pr7_deprecate_*_1984.py
  (colormap + vertical + projected + geographic) -- 23 passed.
- pytest xrspatial/geotiff/tests/test_attrs_contract_*_1984.py
        xrspatial/geotiff/tests/test_metadata_round_trip_1484.py
        xrspatial/geotiff/tests/test_features.py::TestPalette
  -- 94 passed total, no regressions.
brendancol added a commit that referenced this pull request May 18, 2026
…1984) (#2014)

* geotiff: deprecate matplotlib colormap variants in DataArray.attrs (#1984)

Issue #1984 PR 7. The reader emits ``attrs['cmap']`` (when matplotlib
is importable) and ``attrs['colormap_rgba']`` whenever the source file
declares Photometric=3 (palette). The writer never selects
Photometric=3 from attrs alone, so PR 6 (#2004) locked the round-trip
drop for both keys as part of the attrs-contract pass-through tier.

This PR deprecates the read-side emission of both keys for one
release cycle. Callers who want a matplotlib ListedColormap should
build one from the canonical ``attrs['colormap']`` (raw uint16 RGB
triples from TIFF tag 320) instead. ``attrs['colormap']`` is the
canonical-tier replacement and is intentionally kept: it round-trips
through ``_merge_friendly_extra_tags`` already.

During the deprecation window both attrs still land on the returned
DataArray; only a DeprecationWarning is added. Removal is a follow-up
PR. The new attrs-contract docstring split moves both keys into a
"Deprecated" section so the contract stays explicit about which keys
have a future.

The new test file
``xrspatial/geotiff/tests/test_attrs_pr7_deprecate_colormap_variants_1984.py``
pins three behaviours: the warning fires on a Photometric=3 fixture
for ``colormap_rgba`` (always) and for ``cmap`` (when matplotlib is
installed); the attrs are still emitted; the plain
``attrs['colormap']`` key is unaffected.

Existing tests that read palette fixtures (the TestPalette block in
``test_features.py`` and ``test_colormap_round_trip`` in
``test_metadata_round_trip_1484.py``) ignore the new DeprecationWarning
locally to keep their reports clean.

* geotiff: factor shared _emit_deprecated_geokey_attr helper

PR review on #2014 flagged that the three colormap deprecation sites
copy-paste the same nine-line ``warnings.warn(...)`` block with only
the attr name and migration hint varying. Sibling PRs #2010 / #2013
have the same shape; PR #2011 already factors a slice-specific
helper. Introduce a generic helper in ``_attrs.py`` so all four PR 7
slices can share it.

``_emit_deprecated_geokey_attr(attrs, name, value, *, reason,
migration=None)`` builds the canonical warning text shape:

    xrspatial.geotiff: attrs['<name>'] is deprecated; <reason>.
    [<migration>.] It will be removed in a future release. See
    issue #1984.

then sets ``attrs[name] = value`` (read-side emission still alive for
the deprecation window). ``stacklevel=2`` lands the warning at the
caller of ``_populate_attrs_from_geo_info``; ``DeprecationWarning`` is
silenced for library code by default in Python's filters, so this is
mainly visible to test runners and developers who opt in.

The colormap-variants deprecation now routes ``cmap`` and the two
``colormap_rgba`` branches (matplotlib present + ImportError fallback)
through the helper, replacing 27 lines of copy-paste with three
calls. The emitted warning text is byte-identical to the previous
inline strings, so the existing tests (``pytest.warns`` plus the
TestPalette / test_metadata_round_trip filter regexes) keep passing
unchanged.

Sibling PRs #2010 and #2013 can adopt the helper on rebase; #2011's
existing geographic-specific helper can be inlined onto this one in
a follow-up.

Test plan:
- pytest xrspatial/geotiff/tests/test_attrs_pr7_deprecate_colormap_variants_1984.py -- 4 passed.
- pytest xrspatial/geotiff/tests/test_attrs_contract_*_1984.py xrspatial/geotiff/tests/test_metadata_round_trip_1484.py -- 57 passed.
- pytest xrspatial/geotiff/tests/test_features.py::TestPalette -- 7 passed.

* geotiff: address self-review on shared deprecation helper

Self-review on PR #2014 flagged one blocker and two suggestions on
the shared-helper refactor. This commit addresses them.

Blocker -- ``stacklevel`` regression (``_attrs.py:179``):
    Wrapping the inline ``warnings.warn(..., stacklevel=2)`` call in
    a helper shifted the warning location by one frame; the warning
    was surfacing at ``_attrs.py`` (the helper call site) instead of
    at the backend that called ``_populate_attrs_from_geo_info``.
    Bumped ``stacklevel=2`` to ``stacklevel=3`` so the warning is
    attributed to the backend frame, matching the pre-refactor
    behaviour (verified empirically: warning now surfaces at
    ``xrspatial/geotiff/__init__.py:518`` instead of ``_attrs.py:350``).

Suggestion -- helper name (``_attrs.py:152``):
    Renamed ``_emit_deprecated_geokey_attr`` to ``_emit_deprecated_attr``
    because the colormap variants (cmap, colormap_rgba) come from
    TIFF tag 320 (ColorMap), not from a GeoKey. The new name covers
    every PR 7 slice; docstring notes why the ``_geokey_`` qualifier
    was dropped.

Suggestion -- user-guide doc:
    Added a new "Deprecated keys" section to
    ``docs/source/user_guide/attrs_contract.rst`` so users reading
    the contract page see the new tier alongside the existing
    canonical / compatibility-alias / pass-through tiers. Mirrors
    the module docstring.

Suggestion -- direct unit test for the helper:
    Added ``test_emit_deprecated_attr_with_migration_text`` and
    ``test_emit_deprecated_attr_without_migration_text``. The first
    pins the exact warning text shape; the second pins the
    ``migration=None`` branch which the existing colormap tests do
    not exercise.

Nit -- migration hints:
    Lifted ``_colormap_reason`` / ``_colormap_migration`` to module-
    level constants and split them into ``_DEPRECATED_COLORMAP_REASON``,
    ``_DEPRECATED_CMAP_MIGRATION``, and
    ``_DEPRECATED_COLORMAP_RGBA_MIGRATION``. The new
    ``colormap_rgba``-specific migration ("Reshape attrs['colormap']
    to (n_colors, 3) and append an alpha channel") is more direct
    than the previous shared ``ListedColormap`` hint, which only
    matched ``cmap`` cleanly.

Test plan:
- pytest test_attrs_pr7_deprecate_colormap_variants_1984.py (6 passed)
- pytest test_attrs_contract_*_1984.py test_metadata_round_trip_1484.py
  test_features.py::TestPalette -- 70 passed total
brendancol added a commit that referenced this pull request May 18, 2026
…2015)

The four PR 7 deprecation slices (#2010 vertical, #2011 geographic,
#2013 projected, #2014 colormap variants) moved 13 attrs from the
pass-through tier to a new deprecated tier in
``xrspatial/geotiff/_attrs.py`` but did not update the user-guide
page, which left ``docs/source/user_guide/attrs_contract.rst``
listing those attrs as round-trip-best-effort while the Python code
fires a DeprecationWarning every time one is emitted.

This is docs-only:

* Trim ``Pass-through keys`` table to ``image_description``,
  ``extra_samples``, ``colormap`` -- the three attrs that genuinely
  round-trip via ``_merge_friendly_extra_tags``. The 11 GeoKey-
  derived attrs plus the two colormap variants moved out.
* Merge the two separate deprecated sections that existed pre-PR
  (``Deprecated keys`` for colormap variants, ``Deprecated GeoKey
  attrs`` for the GeoKey slices) into one ``Deprecated keys``
  section with two subsections: GeoKey-derived attrs (with the
  shared pyproj migration recipe) and matplotlib colormap variants
  (with per-row migration in a list-table).
* Update the page's opening summary from "three tiers" to "four
  tiers" so the first paragraph matches the table-of-contents.
* Update the ``Round-trip invariants`` section: the pass-through
  subsection now reflects that only three attrs live there and all
  three round-trip; add a deprecated subsection that explains the
  warning-only phase and points at the future contract-version bump
  to 2 when removal lands.

No production-code change. Verified the file still parses with
docutils (Sphinx-specific ``:func:`` / ``:mod:`` role warnings are
the same pattern used across the rest of the docs).

Refs #1984.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance PR touches performance-sensitive code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant