Fix config_store map_annotation on Python 3.14 (read-only __args__)#10726
Conversation
map_annotation rebuilds Union/list/dict/tuple generics after remapping their inner types (e.g. torch.Tensor -> Any). For typing aliases it did `copy.copy(annotation); annotation.__args__ = args`. Python 3.14 unified typing.Union with the builtin X | Y union type (types.UnionType) -- they are now the same C-implemented class whose __args__ is a read-only slot, so the in-place assignment raises `AttributeError: readonly attribute` at import/registration time for any class registered via @register whose __init__ has a Union/Optional annotation. (CPython gh-105499; What's New in Python 3.14.) Rebuild without mutation instead: - Union/Optional -> Union[tuple(args)] - other typing aliases -> annotation.copy_with(tuple(args)), with the legacy in-place rewrite kept only as a fallback. Behavior-preserving on Python 3.10-3.13; unblocks 3.14. Adds Union/Optional regression assertions to test_map_annotation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Locks in the one behavioral improvement of the 3.14 __args__ fix: when all Union members map to the same type, the rebuilt annotation collapses to Any (old in-place mutation left a degenerate Union[Any, Any]). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #10726 +/- ##
==========================================
- Coverage 86.11% 79.68% -6.43%
==========================================
Files 496 513 +17
Lines 33655 37870 +4215
==========================================
+ Hits 28981 30177 +1196
- Misses 4674 7693 +3019 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Ready for review. Quick note on CI: the |
|
Thanks for the review @akihironitta! The only thing left is the required checks blocking merge — could you admin-merge / override when you get a chance, or let me know if there's a path you'd prefer? |
On Python 3.14,
config_store.map_annotationraisesAttributeError: readonly attribute: it assigns to__args__, which became read-only after thetyping.Unionunification (CPython gh-105499). This rebuilds the annotation instead of mutating it.Relationship to #10692
Same root cause as #10692 — thanks @ddelgadovargas-cyber. Opening this as an alternative because it:
typing.*form — usescopy_with(...)sotyping.List[...]staystyping.List[...](rather than normalizing tolist[...]), leaving the existingtest_map_annotationassertions unchanged.copy.copy(alias); alias.__args__ = argsdoesn't actually copy —copy.copy()on atypingalias returns the same interned object — so remapping one annotation mutates the shared global singleton (e.g.typing.List[int]becomestyping.List[Any]process-wide, and a laterUnionremap can crash). Rebuilding returns fresh objects and eliminates this.Happy to consolidate into whichever approach maintainers prefer.
Changes
Union/Optional→Union[args]; othertyping.*generics →annotation.copy_with(tuple(args))(legacy in-place path kept as a fallback); builtinlist[...]/dict[...]path unchanged.test_map_annotationcases forUnion/Optional/nested generics + Union-collapse-to-Any.Validation
test/test_config_store.pyandfill_config_store()pass on Python 3.10 and 3.14.(Draft — opening for discussion / alignment with #10692.)