Skip to content

feat: add built-in frozendict support for Python 3.15+ - #274

Open
XuehaiPan wants to merge 34 commits into
metaopt:mainfrom
XuehaiPan:frozendict
Open

feat: add built-in frozendict support for Python 3.15+#274
XuehaiPan wants to merge 34 commits into
metaopt:mainfrom
XuehaiPan:frozendict

Conversation

@XuehaiPan

@XuehaiPan XuehaiPan commented Mar 21, 2026

Copy link
Copy Markdown
Member

Description

Register frozendict (PEP 814, Python 3.15.0a7+) as a built-in PyTree node type with key-sorted traversal, matching the behavior of dict and collections.defaultdict. The dict_insertion_ordered context manager also affects frozendict.

Also improves the empty deque representation: deque() instead of deque([]).

Motivation and Context

Python 3.15 introduces frozendict as a built-in immutable mapping type (PEP 814). As a fundamental container type, it should be supported as a built-in PyTree node — just like dict, OrderedDict, and defaultdict.

  • I have raised an issue to propose this change (required for new features and bug fixes)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds core functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation (update in the documentation)

Implemented Tasks

  • C++ PyTreeKind::FrozenDict enum and PyFrozenDictTypeObject macro (guarded by PY_VERSION_HEX >= 0x030F00A7)
  • All treespec switch statements updated (flatten, unflatten, serialize, hash, compare, traverse, construct, paths, accessors, entries)
  • Python registry with sorted and insertion-ordered flatten/unflatten functions
  • treespec_frozendict() constructor, STANDARD_DICT_TYPES expansion, PyTree[T] type union
  • optree.treespec.frozendict namespace alias
  • README (linked to PEP 814), Sphinx docs, and spelling wordlist
  • Test data extended (3 frozendict entries in TREES fixture, subinterpreter test tree, registry size checks)
  • Empty deque repr improvement

Checklist

  • I have read the CONTRIBUTION guide. (required)
  • My change requires a change to the documentation.
  • I have updated the tests accordingly. (required for a bug fix or a new feature)
  • I have updated the documentation accordingly.
  • I have reformatted the code using make format. (required)
  • I have checked the code using make lint. (required)
  • I have ensured make test pass. (required)

@XuehaiPan XuehaiPan self-assigned this Mar 21, 2026
@XuehaiPan XuehaiPan added enhancement New feature or request cxx Something related to the CXX source code python Something related to the Python source code labels Mar 21, 2026
@codecov

codecov Bot commented Mar 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (d34f302) to head (eda1f6d).

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #274   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           15        15           
  Lines         1623      1655   +32     
  Branches       218       225    +7     
=========================================
+ Hits          1623      1655   +32     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)
unittests-cp310-Linux 100.00% <100.00%> (ø)
unittests-cp310-Windows 100.00% <100.00%> (ø)
unittests-cp310-macOS 100.00% <100.00%> (ø)
unittests-cp311-Linux 100.00% <100.00%> (ø)
unittests-cp311-Windows 100.00% <100.00%> (ø)
unittests-cp311-macOS 100.00% <100.00%> (ø)
unittests-cp312-Linux 100.00% <100.00%> (ø)
unittests-cp312-Windows 100.00% <100.00%> (ø)
unittests-cp312-macOS 100.00% <100.00%> (ø)
unittests-cp313-Linux 100.00% <100.00%> (ø)
unittests-cp313-Windows 100.00% <100.00%> (ø)
unittests-cp313-macOS 100.00% <100.00%> (ø)
unittests-cp313t-Linux 100.00% <100.00%> (ø)
unittests-cp313t-Windows 100.00% <100.00%> (ø)
unittests-cp313t-macOS 100.00% <100.00%> (ø)
unittests-cp314-Linux 100.00% <100.00%> (ø)
unittests-cp314-Windows 100.00% <100.00%> (ø)
unittests-cp314-macOS 100.00% <100.00%> (ø)
unittests-cp314t-Linux 100.00% <100.00%> (ø)
unittests-cp314t-Windows 100.00% <100.00%> (ø)
unittests-cp314t-macOS 100.00% <100.00%> (ø)
unittests-cp315-Linux 100.00% <100.00%> (?)
unittests-cp315-Windows 100.00% <100.00%> (?)
unittests-cp315-macOS 100.00% <100.00%> (?)
unittests-cp315t-Linux 100.00% <100.00%> (?)
unittests-cp315t-Windows 100.00% <100.00%> (?)
unittests-cp315t-macOS 100.00% <100.00%> (?)
unittests-cp39-Linux 100.00% <100.00%> (ø)
unittests-cp39-Windows 100.00% <100.00%> (ø)
unittests-cp39-macOS 100.00% <100.00%> (ø)
unittests-pp311-Linux 100.00% <100.00%> (ø)
unittests-pp311-Windows 100.00% <100.00%> (ø)
unittests-pp311-macOS 100.00% <100.00%> (ø)
unittests-pydebug 100.00% <100.00%> (ø)
unittests-pydebug-cp310d-Linux 100.00% <100.00%> (ø)
unittests-pydebug-cp310d-Windows 100.00% <100.00%> (ø)
unittests-pydebug-cp310d-macOS 100.00% <100.00%> (ø)
unittests-pydebug-cp311d-Linux 100.00% <100.00%> (ø)
unittests-pydebug-cp311d-Windows 100.00% <100.00%> (ø)
unittests-pydebug-cp311d-macOS 100.00% <100.00%> (ø)
unittests-pydebug-cp312d-Linux 100.00% <100.00%> (ø)
unittests-pydebug-cp312d-Windows 100.00% <100.00%> (ø)
unittests-pydebug-cp312d-macOS 100.00% <100.00%> (ø)
unittests-pydebug-cp313d-Linux 100.00% <100.00%> (ø)
unittests-pydebug-cp313d-Windows 100.00% <100.00%> (ø)
unittests-pydebug-cp313d-macOS 100.00% <100.00%> (ø)
unittests-pydebug-cp313td-Linux 100.00% <100.00%> (ø)
unittests-pydebug-cp313td-Windows 100.00% <100.00%> (ø)
unittests-pydebug-cp313td-macOS 100.00% <100.00%> (ø)
unittests-pydebug-cp314d-Linux 100.00% <100.00%> (ø)
unittests-pydebug-cp314d-Windows 100.00% <100.00%> (ø)
unittests-pydebug-cp314d-macOS 100.00% <100.00%> (ø)
unittests-pydebug-cp314td-Linux 100.00% <100.00%> (ø)
unittests-pydebug-cp314td-Windows 100.00% <100.00%> (ø)
unittests-pydebug-cp314td-macOS 100.00% <100.00%> (ø)
unittests-pydebug-cp315d-Linux 100.00% <100.00%> (?)
unittests-pydebug-cp315d-Windows 100.00% <100.00%> (?)
unittests-pydebug-cp315d-macOS 100.00% <100.00%> (?)
unittests-pydebug-cp315td-Linux 100.00% <100.00%> (?)
unittests-pydebug-cp315td-Windows 100.00% <100.00%> (?)
unittests-pydebug-cp315td-macOS 100.00% <100.00%> (?)
unittests-pydebug-cp39d-Linux 100.00% <100.00%> (ø)
unittests-pydebug-cp39d-Windows 100.00% <100.00%> (ø)
unittests-pydebug-cp39d-macOS 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@XuehaiPan
XuehaiPan force-pushed the frozendict branch 4 times, most recently from 3426921 to 12a3276 Compare March 25, 2026 15:25
@XuehaiPan
XuehaiPan force-pushed the frozendict branch 4 times, most recently from fe42e10 to 0faccef Compare April 3, 2026 06:14
@XuehaiPan
XuehaiPan force-pushed the frozendict branch 5 times, most recently from 19952e3 to 7656326 Compare May 11, 2026 17:39
XuehaiPan added 9 commits May 12, 2026 01:59
Register `frozendict` (Python 3.15.0a7+) as a built-in PyTree node type
with key-sorted traversal, matching the behavior of `dict` and
`collections.defaultdict`. The `dict_insertion_ordered` context manager
also affects `frozendict`.

Changes across 29 files:
- C++ enum `PyTreeKind::FrozenDict` and `PyFrozenDictTypeObject` macro
  (guarded by `PY_VERSION_HEX >= 0x030F00A7`)
- All treespec switch statements updated (flatten, unflatten, serialize,
  hash, compare, traverse, construct, paths, accessors, entries)
- Python registry with sorted and insertion-ordered flatten/unflatten
- `treespec_frozendict()` constructor, `STANDARD_DICT_TYPES` expansion,
  `PyTree[T]` type union update
- `optree.treespec.frozendict` namespace alias
- README, Sphinx docs, and spelling wordlist
- Test data extended (3 frozendict entries in TREES fixture),
  subinterpreter test tree includes frozendict, registry size checks
- Minor: empty deque repr improved (`deque()` instead of `deque([])`)
Sphinx autodoc/autosummary imports symbols during the reading phase
regardless of ``.. only::`` directives, causing import errors on
Python < 3.15 where ``treespec_frozendict`` does not exist. Remove the
entries for now with TODO comments to add them back when building with
Python 3.15+.
@XuehaiPan XuehaiPan added the test-with-nightly-pybind11 Use nightly pybind11 in tests label May 29, 2026
XuehaiPan added 5 commits June 6, 2026 02:15
PyPy/Windows jobs intermittently fail in pip steps with transient network
errors (e.g. `OSError: Connection broken: IncompleteRead`) while a nested
build-isolation pip downloads build dependencies such as `scikit-build-core`
for nightly `pybind11`.

Set `PIP_RETRIES=10` (default 5) and `PIP_TIMEOUT=120` (default 15s) in the
`env:` block of all test/build/lint workflows. As environment variables these
propagate into nested build-isolation pip subprocesses (unlike CLI flags on the
outer `pip install`), covering connection setup, DNS, and metadata-fetch
hiccups that account for most CI pip failures. Also add both vars to
cibuildwheel's `environment-pass` so they cross into the isolated (manylinux)
wheel-build containers, which would otherwise not inherit the host environment.

Note: a mid-download `IncompleteRead` can still escape pip's internal retry;
a command-level retry would be the next lever if it recurs.
XuehaiPan added 3 commits July 4, 2026 15:02
The pyenv-win bundled `.versions_cache.xml` lags behind python.org, and the `pyenv update` command is unusable on GitHub runners: its VBScript updater parses release listings via the Internet Explorer `htmlfile` COM object, which modern Windows images reject with "This command is not supported".

Run the pyenv-win stdlib updater (`.github/scripts/update_versions_cache.py`) right after cloning so `pyenv install` can resolve the newest CPython release (3.15.0b3), which ships the gh-149786 fix for the free-threaded venv launcher that previously broke the 3.15 free-threaded debug build venv/ensurepip.
…urface

Extend three existing tests with `frozendict` cases (gated on Python 3.15+)
to exercise paths that were previously untested:

- `test_tree_broadcast_common`: the `PyTreeKind::FrozenDict` arm in
  `BroadcastToCommonSuffix`.
- `test_different_metadata`: the `frozendict:` key-mismatch message emitted
  by `FlattenUpTo`.
- `test_pytree_typing`: the `optree.typing.FrozenDict` alias, its `__all__`
  position, and the `frozendict[Any, ...]` member of the `PyTree` generic union.
@XuehaiPan
XuehaiPan force-pushed the main branch 2 times, most recently from 51ec4bb to b5d273b Compare July 8, 2026 10:51
Integrate the correctness sweep from metaopt#290 with the built-in `frozendict`
support for Python 3.15+ (PEP 814).

Explicit conflicts:

- `optree/ops.py`: `tree_broadcast_common()` was rewritten upstream to
  delegate to the new `_tree_broadcast_common_with_treespec()` helper.
  Took the upstream refactor and dropped our now-superseded inline body.
- `src/treespec/serialization.cpp`: `PyTreeSpec::FromPicklable()` was
  rewritten upstream into a validating, exhaustive per-kind switch. Wove
  `PyTreeKind::FrozenDict` through the new structure instead of the old
  `t[...]` accessors, and moved our cross-version rejection guard below
  upstream's new kind-range check.

Implicit conflicts (no textual overlap, both directions):

- `src/treespec/constructors.cpp`: upstream's new `depends_on_namespace`
  predicate listed only `Dict`/`DefaultDict`. `frozendict` is equally
  affected by `dict_insertion_ordered`, so a `frozendict` treespec built
  by `treespec_from_collection()` under an insertion-ordered namespace
  had its namespace erased, yielding a spec with insertion-ordered keys
  that claims the global namespace and that no `tree_flatten()` can
  produce.
- `src/treespec/serialization.cpp`: upstream's `ToPicklable()` now copies
  the node's mutable key list for `Dict`/`OrderedDict`/`DefaultDict` so
  the pickled state cannot alias the immutable spec. `FrozenDict` stores
  the same `py::list` and was left aliasing, reintroducing the bug
  upstream had just fixed.
- `optree/ops.py`, `optree/typing.py`: upstream introduced two new
  locals named `sentinel`, which Python 3.15 added as a builtin
  (PEP 661); the new comprehension variable is renamed and the new local
  suppressed. Confirmed load-bearing: pylint running on 3.15 reports
  `W0622 redefined-builtin` for a bare `sentinel`.

Follow-up fixes from the post-merge review:

- `tests/helpers.py`: `TREE_PATHS` and `TREE_ACCESSORS` were built
  BEFORE the version-gated block that rebinds the per-mode lists, so
  they kept the pre-`frozendict` 40-element objects while `TREES` grew
  to 43. Every `zip(TREES, ...)` consumer silently truncated, leaving
  `PyTreeKind.FROZENDICT` with zero path and accessor coverage. The
  tables are now built after the block, and an import-time assertion
  pins their lengths to `TREES`.
- `tests/helpers.py`: `is_dict()` used a bare `isinstance(dct, dict)`,
  but `frozendict` is not a `dict` subclass, so the `is_leaf` matrix
  skipped the `frozendict` trees entirely.
- `optree/typing.py`: the `PyTree` and `PyTreeTypeVar` docstrings pin
  the union rendering and omit the `frozendict` member inserted on
  3.15+; documented via a version note rather than in the doctest
  output, which is exercised on 3.13 where `frozendict` does not exist.
- `optree/ops.py`: the `treespec_frozendict()` fallback `raise` lacked a
  coverage pragma, so it read as uncovered on 3.15+ where it is
  unreachable.
- `optree/registry.py`: `DictMetaData` also backs `frozendict` nodes.

Tests:

- `test_treespec_frozendict_from_collection_retains_insertion_ordered_namespace`
  covers the namespace-retention fix.
- `test_treespec_frozendict_pickled_state_does_not_alias_keys` covers the
  `__getstate__`/`__setstate__` aliasing fix, mirroring the upstream
  tests for the other dict kinds. It compares the repr of the STATE, not
  of the treespec, since the latter never reads `original_keys`.
- `test_treespec_frozendict_setstate_rejected_on_build_without_support`
  covers the cross-version pickle guard, via both `__setstate__` and a
  hand-assembled pickle payload. It runs on every build WITHOUT
  `frozendict` support, i.e. the majority of the CI matrix.
- `test_treespec_setstate_rejects_builtin_custom_type` now includes
  `frozendict` in its built-in type enumeration, which its own comment
  declares exhaustive.

Validation, across three interpreters:

- CPython 3.15.0b4 (release, `frozendict` enabled): 100839 passed,
  7 skipped, 1 xfailed, no failures. `pylint optree` rates 10.00/10.
- CPython 3.14.6 (`frozendict` unavailable): 94197 passed, 5 skipped,
  1 xfailed. `pre-commit run --all-files` clean.
- CPython 3.16.0a0 free-threaded debug (`frozendict` enabled): 100838
  passed, 7 skipped, 1 xfailed, plus one pre-existing unrelated failure.

The 3.16-only failure is `test_dataclasses.py::test_same_signature`:
CPython 3.16 added a `qualname` parameter to `dataclasses.make_dataclass()`
that `optree.dataclasses` has not yet mirrored. It does not occur on
3.15 or 3.14, and `qualname` is absent at the merge base and on both
parents, so it is unrelated to this merge.
A four-way audit of the repository -- build/CI config, test suite,
library API surface, and documentation -- for places where a `dict` /
`OrderedDict` / `defaultdict` peer exists but `frozendict` was missing.

Correctness:

- `optree/registry.py`: `_frozendict_flatten()` and
  `_frozendict_insertion_ordered_flatten()` delegate to `_dict_flatten()`
  and `_dict_insertion_ordered_flatten()`, whose parameters were
  annotated `dict[KT, VT]`. `frozendict` is NOT a `dict` subclass, so
  this was a genuine type error; both now take `Mapping[KT, VT]`, which
  they already satisfied (the bodies only call `.items()`). Matches
  typeshed, which declares `frozendict(Mapping[_KT, _VT])`.
- `include/optree/pymacros.h`: `OPTREE_HAS_FROZENDICT` lacked the
  `!defined(PYPY_VERSION)` guard that `OPTREE_HAS_SUBINTERPRETER_SUPPORT`
  carries. `PyFrozenDict_Type` lives in CPython's
  `Include/cpython/dictobject.h`, and PyPy reports the `PY_VERSION_HEX`
  of the CPython level it emulates, so a future PyPy claiming 3.15 would
  enable the macro and fail to link.
- Six stale `# type: ignore[import]` / `[type-arg]` comments on
  `frozendict` lines: typeshed already declares `frozendict`, and
  declares it generic, so these were unnecessary and would become hard
  errors under `warn_unused_ignores`.
- `optree/typing.py`: the `FrozenDict` union member carried
  `# type: ignore[valid-type]` where mypy reports `arg-type` (it goes
  through `list.append`, not a list literal like its neighbours).

Together these take `mypy --python-version=3.15 optree` from 15 errors
to 5; the remaining 5 are pre-existing and unrelated. They were hidden
because `[tool.mypy] python_version = "3.9"` is rejected by mypy >= 2.3,
which falls back to 3.10 and prunes every `sys.version_info >= (3, 15)`
block as unreachable. That setting is deliberately left alone here:
correcting it also surfaces the 5 unrelated errors and is a project-wide
decision, not a `frozendict` one.

Tests. Three gaps were proven undetected by mutation testing -- each
mutation passed the entire suite before these were added, and is caught
after (1, 4 and 1 failures respectively):

- `_frozendict_unflatten()` rebuilding in sorted rather than original
  insertion order, now covered by
  `test_python_frozendict_unflatten_preserves_original_insertion_order`
  and `test_python_frozendict_flatten_returns_dict_metadata`.
- `register_pytree_node.get(frozendict)` ignoring
  `dict_insertion_ordered()`, now covered in
  `test_pytree_node_registry_get`.
- `AutoEntry` dispatching `frozendict` to `FlattenedEntry` instead of
  `MappingEntry`. `frozendict` is a virtual `Mapping`, not a `dict`
  subclass, so it relies on the `issubclass(type, Mapping)` branch; a
  user registering a `frozendict` subclass was silently mis-dispatched.

Cross-version pickling, in `test_treespec_frozendict_pickle_cross_version`:
a treespec pickled on a 3.15+ build names `PyTreeKind::FrozenDict`, whose
enum value exists everywhere. The test runs on every interpreter and
asserts whichever half applies -- rejection without `frozendict` support,
faithful reconstruction with it. The payload is built by retagging a
plain `dict` spec's state rather than checked in as an opaque blob, and
on supporting builds the retagged state is asserted equal to the one that
build emits natively, so the stand-in is provably faithful. Verified by
deleting the `#if !defined(OPTREE_HAS_FROZENDICT)` guard from
`FromPicklable` and rebuilding: the test fails.

Further coverage, correct today but previously unpinned:

- The negative contract on builds WITHOUT `frozendict` support, which is
  most of the CI matrix: `test_frozendict_unsupported_build_surface`
  pins that no dict-family surface claims `frozendict`, that no tree can
  flatten to a `FROZENDICT` node, and that the always-importable
  constructors raise rather than silently degrading (verified by
  mutation). `test_frozendict_kind_is_defined_on_every_build` pins the
  enum value at 11 on every build, since it is part of the pickle
  format. `test_pytree_type` gained the matching `else` branch.
- A `FROZENDICT` arm in the malformed-`__setstate__` matrix.
  `FrozenDict` rides on shared `||` conditions with `Dict` at ~10 sites
  in `PyTreeSpec::FromPicklable`; dropping one would let a crafted
  pickle build a corrupt spec unnoticed.
- `test_treespec_pickle_all_protocols_roundtrip` covered only a plain
  `dict`, despite protocols 0/1 taking the `copyreg.__newobj__` path
  that upstream had just repaired; it now covers all four dict kinds and
  asserts the restored spec still unflattens identically.
- Cross-kind broadcasting. The existing assertions were
  frozendict-vs-frozendict only; `frozendict` now broadcasts against
  `dict` / `OrderedDict` / `defaultdict` in both operand orders with the
  result types asserted explicitly -- `frozendict({'a': 1}) == {'a': 1}`
  is `True`, so `==` alone would pass even if the node kinds collapsed.

Documentation:

- `dict_insertion_ordered()` -- the canonical doc for the feature -- said
  only "a dictionary", which reads as including `OrderedDict`
  (unaffected) and omits `frozendict` (affected).
- `treespec_frozendict()` had 2 doctest examples where every sibling
  constructor has 6; the four missing ones are added, each executed on
  3.15 first. `+SKIP` is retained: the symbol is always defined but only
  callable on 3.15+, and `make doctest` runs on every supported Python.
- `PyTreeKind.FROZENDICT`'s enum docstring in `src/optree.cpp` renders
  into the docs on every Python and now carries the same `(Python 3.15+)`
  caveat as `optree/_C.pyi`.
- `DictMetaData`, `optree.treespec`'s module docstring, `CHANGELOG.md`,
  and the `Returns:` role in `treespec_frozendict()`.

Validation: 100858 passed / 7 skipped / 1 xfailed on CPython 3.15.0b4;
94199 passed / 22 skipped / 1 xfailed on CPython 3.14.6.
`pre-commit run --all-files` clean.
@Benjamin-eecs
Benjamin-eecs requested a review from Copilot August 2, 2026 18:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment review across the files this branch changes.

Trimmed, all of them cases where the comment restated the code below it or
repeated a rationale already given elsewhere:

- `test_treespec_frozendict_pickle_cross_version`: a 10-line header with a
  bulleted list, plus the payload-construction rationale stated twice.
- Four comments that paraphrased the assertion immediately following them.
- The `FromPicklable` cross-version guard and the `OPTREE_HAS_FROZENDICT`
  PyPy guard, both longer than the surrounding house style.

Corrected, not merely shortened: the `MakeNode` and `GetType` guards claimed
they were "reachable only via a treespec deserialized from a Python 3.15+
build". That stopped being true once `FromPicklable` gained its own guard,
which now rejects those states first, so both are unreachable defence in
depth. The comments say so.

Em-dashes and ASCII `--` used as dashes are replaced with ordinary
punctuation, including the pre-existing occurrences in `README.md`,
`optree/registry.py` and `src/CMakeLists.txt`.

No functional change. 100858 passed on CPython 3.15.0b4, 94199 on 3.14.6,
`pre-commit run --all-files` clean.
The Windows jobs wrap pytest in `cdb -gG -o -c "..."`. cdb's default for
the `eh` event is "notify", so it halts and resumes the debuggee, and
prints a line, for every first-chance C++ exception.

optree raises a great many of those on entirely successful paths:
`PyTreeIter::Next()` ends iteration with `throw py::stop_iteration()`
(`src/treespec/traversal.cpp:164`), which is the only way a native
`__next__` can terminate a Python loop. On the 3.13td Windows run of
metaopt#274 that produced 175,715 notifications out of 274,593 log lines, 59%
of them from `test_tree_iter_thread_safe` alone and a further 17% from
one `range(5000)` unregistration stress test. Error-path tests using
`pytest.raises` account for well under 1%.

That job reached 99% of the suite before hitting the 3h cap. It is
chronically close to the limit: 123 min on `main`, 153 on another PR
branch, 174 and then 180 on this one.

`-xi eh` sets first-chance handling for C++ exceptions to "ignore".
Access violations and other genuine faults are separate events and still
break, dump and report, so crash diagnostics are unaffected. The flag
cannot be moved into the `-c` string, which `-g -G` defer until the
first break.

Applied to the five invocations that run a program. The two `cdb -z`
post-mortem analyzers are left alone: they read an existing dump and
need full exception detail for `!analyze -vv` and `.ecxr`.
cibuildwheel 4.2.0 builds CPython 3.15 by default, bundling 3.15.0rc1,
which upstream guarantees is ABI compatible with the final release.

That removes the reason this branch set `ALLOW_PRERELEASES: "true"`. The
variable does double duty: it adds `cpython-prerelease` to `CIBW_ENABLE`,
which the cp315/cp315t targets needed, but it also gates off the TestPyPI
and PyPI publish steps, sets `CIBW_ALLOW_EMPTY` and downgrades
`if-no-files-found` to a warning. A tag push from this branch would
therefore have run the whole matrix and uploaded nothing, and any target
producing zero wheels would have passed silently.

Verified with `cibuildwheel --print-build-identifiers` against this
`pyproject.toml`, with no `CIBW_ENABLE` set:

  4.1.1  cp313, cp314, cp314t
  4.2.0  cp313, cp314, cp314t, cp315, cp315t

and that no target goes empty under the stricter settings: `cp315`
identifiers are produced for the ios and android platforms too. The
separate cp313t job stays on `cibuildwheel~=3.4` (4.0 dropped that
target) and still resolves `cp313t-*` from `pypy cpython-freethreading`
alone, so it is unaffected by dropping the prerelease enable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cxx Something related to the CXX source code enhancement New feature or request python Something related to the Python source code test-with-nightly-pybind11 Use nightly pybind11 in tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants