Skip to content

Fix alru_cache crash with deferred annotations on Python 3.14#781

Open
rodrigobnogueira wants to merge 6 commits into
masterfrom
fix/deferred-annotations-py314
Open

Fix alru_cache crash with deferred annotations on Python 3.14#781
rodrigobnogueira wants to merge 6 commits into
masterfrom
fix/deferred-annotations-py314

Conversation

@rodrigobnogueira

Copy link
Copy Markdown
Member

What do these changes do?

Fix alru_cache crashing at decoration time on Python 3.14 when an annotation refers to a name defined after the decorated function. PEP 649 makes annotations lazy, and copying fn.__annotations__ eagerly forces their evaluation; functools.update_wrapper copies the lazy __annotate__ function on 3.14 instead, so both wrapper classes now do the same, keeping the __annotations__ copy as the fallback for 3.13 and older. Laziness now matches functools.lru_cache: inspect.get_annotations() resolves once the name exists and raises NameError only on access when it never does.

Are there changes in behavior for the user?

Only on Python 3.14: the wrapper no longer exposes an eagerly evaluated __annotations__ attribute; annotations are reachable through inspect.get_annotations() and typing.get_type_hints(), matching functools.lru_cache there. No changes on 3.13 and older.

Is it a substantial burden for the maintainers to support this?

No.

Related issue number

Fixes #773

Checklist

  • I think the code is well written
  • Unit tests for the changes exist
  • Documentation reflects the changes
  • Add a new news fragment into the CHANGES folder (N/A; CHANGES.rst is updated in release PRs in this repo)

PEP 649 makes annotations lazy; copying fn.__annotations__ at
decoration time forces their evaluation and raises NameError for
names defined after the decorated function. Copy the lazy
__annotate__ function the way functools.update_wrapper does on 3.14,
keeping the __annotations__ copy as the fallback for older versions,
in both the function and the bound-method wrappers.
@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.39%. Comparing base (b5717c7) to head (26f7eda).
⚠️ Report is 5 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #781      +/-   ##
==========================================
+ Coverage   97.78%   98.39%   +0.60%     
==========================================
  Files          16       17       +1     
  Lines        1176     1246      +70     
  Branches       61       63       +2     
==========================================
+ Hits         1150     1226      +76     
+ Misses         23       17       -6     
  Partials        3        3              
Flag Coverage Δ
unit 97.35% <97.43%> (+0.49%) ⬆️

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.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@codspeed-hq

codspeed-hq Bot commented Jul 4, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 63 untouched benchmarks
⏩ 4 skipped benchmarks1


Comparing fix/deferred-annotations-py314 (26f7eda) with master (b5717c7)2

Open in CodSpeed

Footnotes

  1. 4 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on master (593715a) during the generation of this report, so b5717c7 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Covers the wrapper bodies the parity test previously only inspected,
and adds a partial-bound-as-method test so the attribute-copy
fallback that copies neither annotation attribute is exercised.
@rodrigobnogueira rodrigobnogueira marked this pull request as ready for review July 5, 2026 20:02
@rodrigobnogueira rodrigobnogueira requested a review from Vizonex July 9, 2026 00:50
Comment thread async_lru/__init__.py Outdated
# names that are not defined yet.
try:
self.__annotations__ = fn.__annotations__
self.__annotate__ = fn.__annotate__ # type: ignore[attr-defined]

@Vizonex Vizonex Jul 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@rodrigobnogueira I would recommend using sys.version_info as a faster workaround for this problem as this is 3.14+ related, this way this part of the code doesn't trigger a performance regression.

            if sys.version_info >= (3, 14): 
                self.__annotate__ = fn.__annotate__  # type: ignore[attr-defined]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in 73906f3. Both copy sites now branch on sys.version_info >= (3, 14); older interpreters do the plain __annotations__ copy with no try/except on a missing __annotate__. The narrow try/except stays inside the 3.14 branch for wrapped partials, which the partial test covers. The type: ignore[attr-defined] became unused under the gate and was removed. Suite green on 3.12 and 3.14, mypy clean on both.

Older interpreters take the plain __annotations__ copy directly
instead of raising and catching an AttributeError on every
decoration. Suggested by Vizonex in review.

@Vizonex Vizonex left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like this change. Thank you for this now there shouldn't be a performance regressions with older versions of python.

Comment thread async_lru/__init__.py Outdated
Comment on lines +347 to +348
# Python 3.14+ (PEP 649): prefer the lazy __annotate__ function,
# see the matching logic in _LRUCacheWrapper.__init__.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should really make both comments concise like this. I'd also drop the prefix so there's less maintenance. We can see the version check below.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in 26f7eda — both comments trimmed, prefix dropped.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

async-lru cares about the order of type definitions, unlike the rest of python 3.14

3 participants