Skip to content

[fix] Python 3.14 PEP 649 annotation compatibility across inspect.signature() calls#14327

Merged
lukasmasuch merged 8 commits intodevelopfrom
lukasmasuch/issue-14324
Mar 13, 2026
Merged

[fix] Python 3.14 PEP 649 annotation compatibility across inspect.signature() calls#14327
lukasmasuch merged 8 commits intodevelopfrom
lukasmasuch/issue-14324

Conversation

@lukasmasuch
Copy link
Copy Markdown
Collaborator

@lukasmasuch lukasmasuch commented Mar 11, 2026

Describe your changes

Fixes Python 3.14 PEP 649 compatibility across multiple inspect.signature() call sites. On Python 3.14+, deferred annotation evaluation can raise NameError for functions using type annotations that reference types imported under if TYPE_CHECKING:.

Implementation:

  • Added centralized get_func_parameters() helper in type_util.py with annotation_format=Format.STRING on Python 3.14+
  • Added _get_arg_keywords() helper in metrics_util.py for PEP 649-safe argument name extraction

Fixed locations:

  • @st.cache_data / @st.cache_resource — Uses type_util.get_func_parameters()
  • st.help() — Added NameError handling in _get_signature()
  • @st.fragment — Added NameError to contextlib.suppress()
  • Internal metrics decorator — Uses _get_arg_keywords() and added NameError to contextlib.suppress()

GitHub Issue Link (if applicable)

Testing Plan

  • Unit Tests (Python)
    • lib/tests/streamlit/runtime/caching/cache_utils_test.py — Tests get_func_parameters() via _get_positional_arg_name() with PEP 649 simulation
    • lib/tests/streamlit/elements/help_test.py — Tests _get_signature() handling of PEP 649 annotations
    • lib/tests/streamlit/runtime/fragment_test.py — Tests fragment decorator handling of PEP 649 annotations
    • lib/tests/streamlit/runtime/metrics_util_test.py — Tests _get_arg_keywords() and gather_metrics() decorator handling

Contribution License Agreement

By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.

Agent metrics
Type Name Count
skill checking-changes 1
skill finalizing-pr 1
skill updating-internal-docs 1
subagent fixing-pr 2
subagent general-purpose 7
subagent reviewing-local-changes 2
subagent simplifying-local-changes 2

On Python 3.14+, PEP 649 causes annotation evaluation to be deferred.
When annotations reference types imported under TYPE_CHECKING,
inspect.signature() raises NameError. This fix uses annotation_format=STRING
to avoid evaluation since we only need parameter names and kinds.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Copilot AI review requested due to automatic review settings March 11, 2026 11:00
@lukasmasuch lukasmasuch added change:bugfix PR contains bug fix implementation impact:users PR changes affect end users labels Mar 11, 2026
@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Mar 11, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 11, 2026

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-14327/streamlit-1.55.0-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-14327.streamlit.app (☁️ Deploy here if not accessible)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a Python 3.14 (PEP 649) compatibility issue where @st.cache_data / @st.cache_resource could crash with NameError when introspecting decorated functions that use annotations referencing TYPE_CHECKING-only imports.

Changes:

  • Added _get_func_parameters() to retrieve signature parameters without forcing annotation evaluation on Python 3.14+.
  • Updated _get_positional_arg_name() to use _get_func_parameters() instead of calling inspect.signature() directly.
  • Added unit tests for _get_positional_arg_name() behavior and for the PEP 649 regression scenario.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
lib/streamlit/runtime/caching/cache_utils.py Avoids PEP 649-triggered annotation evaluation during cache key construction by using annotation_format=Format.STRING on Python 3.14+.
lib/tests/streamlit/runtime/caching/cache_utils_test.py Adds coverage for positional-arg name resolution and a Python 3.14-specific regression test for deferred annotations.

@lukasmasuch lukasmasuch added the update-snapshots Trigger snapshot autofix workflow label Mar 11, 2026
@github-actions github-actions bot removed the update-snapshots Trigger snapshot autofix workflow label Mar 11, 2026
lukasmasuch and others added 2 commits March 11, 2026 12:22
The previous test used exec() which doesn't trigger PEP 649 deferred
annotation behavior. Instead, create a function with a custom
__annotate__ method that raises NameError when annotations are evaluated,
which properly simulates the real PEP 649 behavior for undefined types.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Update docstring to reference Format.STRING enum value (copilot suggestion)
- Use Callable[..., Any] type annotation for func parameter instead of object,
  removing the need for type: ignore comment (copilot suggestion)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@lukasmasuch lukasmasuch added the ai-review If applied to PR or issue will run AI review workflow label Mar 11, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Mar 11, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Summary

This PR fixes a Python 3.14 compatibility crash in @st.cache_data and @st.cache_resource decorators caused by PEP 649 deferred annotation evaluation. When decorated functions use type annotations referencing types imported under if TYPE_CHECKING:, inspect.signature() raises NameError on Python 3.14+ because annotations are now lazily evaluated.

The fix introduces a _get_func_parameters() helper in lib/streamlit/runtime/caching/cache_utils.py that uses annotation_format=Format.STRING on Python 3.14+ to retrieve parameter names and kinds without evaluating annotations. This is correct because cache key computation only needs parameter names, not annotation values.

Changed files:

  • lib/streamlit/runtime/caching/cache_utils.py — Added _get_func_parameters() helper; updated _get_positional_arg_name() to use it.
  • lib/tests/streamlit/runtime/caching/cache_utils_test.py — Added parametrized tests for _get_positional_arg_name() and a Python 3.14-specific regression test for the PEP 649 scenario.

Code Quality

All three reviewers agree the implementation is clean, minimal, and well-targeted. Specific points of consensus:

  • The _get_func_parameters() helper is properly prefixed with _ to indicate module-private scope, consistent with project conventions.
  • The docstring clearly explains the rationale for the version-conditional behavior.
  • The conditional import of annotationlib inside the sys.version_info >= (3, 14) guard is correct since annotationlib is only available in Python 3.14+.
  • The version check follows the project's "prioritize new features in Python 3.10+" guideline.

Minor style note (non-blocking, all reviewers agree): gemini-3.1-pro noted that project guidelines prefer importing entire modules over single functions (import annotationlib vs from annotationlib import Format). However, since this is scoped within a version-guarded block, all reviewers found the current approach acceptable.

Minor style note (non-blocking): opus-4.6-thinking noted that import inspect and import types (lines 117-118 of the test) are inside the test function body rather than at the top level per lib/tests/AGENTS.md. However, keeping them grouped with the annotationlib import for readability is a reasonable trade-off. No changes required.

Test Coverage

All reviewers agree the test coverage is strong and adequate:

  1. test_get_positional_arg_name — Well-structured parametrized test with 9 cases covering: positional args (first/second/third), negative index, out-of-range, keyword-only parameters, and varargs. Follows the @pytest.mark.parametrize pattern from project guidelines.

  2. test_get_func_parameters_handles_pep649_annotations — Appropriately skipped on Python < 3.14. Creates a realistic simulation of PEP 649 behavior by constructing a function with a custom __annotate__ that raises NameError for Format.VALUE but returns strings for Format.STRING. Verifies both the problem (standard inspect.signature() raises NameError) and the fix (_get_func_parameters handles it gracefully).

  3. All new test functions have docstrings, are type-annotated, and use pytest-native patterns as required by the project test guidelines.

Consensus: No E2E tests needed — this is a backend-only internal bug fix.

Optional suggestion (from gpt-5.3-codex-high, non-blocking): A second regression test using a real if TYPE_CHECKING: import pattern (in addition to the synthetic __annotate__ simulation) could further document the user-facing failure mode.

Backwards Compatibility

All reviewers unanimously agree there are no backwards compatibility concerns:

  • On Python < 3.14, behavior is identical — inspect.signature(func) is called with no extra arguments.
  • On Python 3.14+, annotation_format=Format.STRING only changes how annotations are represented, but since the code only inspects Parameter.name and Parameter.kind, observable behavior is unchanged.
  • Both _get_func_parameters and _get_positional_arg_name are private functions, so no public API surface is affected.

Security & Risk

All reviewers agree: no security concerns. The change is purely about parameter introspection for cache key computation. No network, WebSocket, authentication, file serving, HTML/Markdown rendering, or external dependency changes.

Risk is very low and localized to function signature introspection for cache key argument mapping.

External test recommendation

  • Recommend external_test: No
  • Triggered categories: None
  • Evidence:
    • lib/streamlit/runtime/caching/cache_utils.py: Pure internal Python utility change for parameter introspection. No network, auth, embedding, asset serving, or cross-origin impact.
    • lib/tests/streamlit/runtime/caching/cache_utils_test.py: Test-only additions.
  • Suggested external_test focus areas: None applicable.
  • Confidence: High
  • Assumptions and gaps: None. All three reviewers independently reached the same conclusion.

Accessibility

No frontend changes in this PR. No accessibility considerations apply. (All reviewers agree.)

Recommendations

  1. (Non-blocking, follow-up) opus-4.6-thinking identified three other inspect.signature() call sites in the codebase that are likely affected by the same PEP 649 issue on Python 3.14+. I verified these observations against the codebase and they are correct:

    • lib/streamlit/elements/help.py:193_get_signature() catches ValueError and TypeError but not NameError. If st.help() is called on a function with TYPE_CHECKING-only annotations, it will crash on Python 3.14+.
    • lib/streamlit/runtime/fragment.py:274@st.fragment assigns inspect.signature(non_optional_func) wrapped only in contextlib.suppress(AttributeError), which won't catch NameError.
    • lib/streamlit/runtime/metrics_util.py:556 — Same pattern as fragment.py.

    A follow-up PR to address these sites (possibly with a shared helper) is recommended.

  2. (Optional, non-blocking) Consider adding a real-function-based regression test alongside the synthetic __annotate__ simulation to document the user-facing failure mode (suggestion from gpt-5.3-codex-high).

Reviewer Agreement

Aspect gemini-3.1-pro gpt-5.3-codex-high opus-4.6-thinking
Code Quality Approve Approve Approve
Test Coverage Adequate Adequate Adequate
Backwards Compat No concerns No concerns No concerns
Security & Risk Low risk Low risk Low risk
External Tests Not needed Not needed Not needed
Overall Verdict APPROVED APPROVED APPROVED

All three reviewers are in full agreement. No conflicts to resolve.

Verdict

APPROVED: All three reviewers unanimously approve. The fix is clean, well-targeted, backwards-compatible, and includes strong test coverage for the Python 3.14 PEP 649 compatibility issue in cache decorators. No critical or blocking issues were raised.


This is a consolidated AI review by opus-4.6-thinking. All expected models completed their reviews successfully.


📋 Review by `gemini-3.1-pro`

Summary

This PR fixes a NameError crash in @st.cache_data and @st.cache_resource on Python 3.14. The crash occurs when decorated functions use type annotations referencing types imported under if TYPE_CHECKING:, due to PEP 649 deferred annotation evaluation. The fix introduces a _get_func_parameters() helper that uses annotation_format=Format.STRING on Python 3.14+ to avoid evaluating the annotations when inspecting the function signature.

Code Quality

The code changes are well-structured, elegant, and adhere to the Streamlit Python guidelines. The new _get_func_parameters helper is correctly marked as private with a leading underscore and includes type annotations.

One minor note regarding imports: The Streamlit guidelines prefer importing entire modules instead of single functions (import annotationlib instead of from annotationlib import Format). However, given this is scoped within an if sys.version_info >= (3, 14): block, the current approach is acceptable.

Test Coverage

The changes are well-tested.

  • A parameterized test test_get_positional_arg_name was added to cover various function signatures (positional, kwonly, varargs).
  • A specific anti-regression test test_get_func_parameters_handles_pep649_annotations was added for Python 3.14+. It cleverly mocks the __annotate__ behavior to simulate the NameError and verifies that the new helper handles it gracefully.
  • The inline imports within the test function are correctly used to prevent ImportError on Python versions older than 3.14.

Backwards Compatibility

The changes are fully backwards compatible. The new behavior is strictly gated behind a sys.version_info >= (3, 14) check, ensuring no impact on Python 3.10-3.13.

Security & Risk

No security concerns or regression risks identified. The changes are limited to inspecting function signatures for caching decorators and do not interact with any security-sensitive areas.

External test recommendation

  • Recommend external_test: No
  • Triggered categories: None
  • Evidence:
    • lib/streamlit/runtime/caching/cache_utils.py: Only modifies internal function signature inspection logic for caching decorators.
  • Suggested external_test focus areas: N/A
  • Confidence: High
  • Assumptions and gaps: None

Accessibility

N/A - No frontend changes.

Recommendations

No changes required. The implementation and tests are solid.

Verdict

APPROVED: The PR successfully fixes a Python 3.14 compatibility issue with caching decorators and includes excellent test coverage.


This is an automated AI review by gemini-3.1-pro.

📋 Review by `gpt-5.3-codex-high`

Summary

This PR fixes a Python 3.14 compatibility issue in cache decorators where deferred annotation evaluation (PEP 649) can raise NameError for type names imported under TYPE_CHECKING. The implementation adds _get_func_parameters() in lib/streamlit/runtime/caching/cache_utils.py and updates _get_positional_arg_name() to use it, plus focused unit tests in lib/tests/streamlit/runtime/caching/cache_utils_test.py.

Code Quality

The change is small, targeted, and maintainable.

  • lib/streamlit/runtime/caching/cache_utils.py (around lines 605-620) uses a clear version gate (sys.version_info >= (3, 14)) and avoids annotation evaluation by using inspect.signature(..., annotation_format=Format.STRING).
  • lib/streamlit/runtime/caching/cache_utils.py (around lines 582-602) keeps existing behavior for positional argument name resolution and only swaps signature parameter retrieval to the new helper.
  • lib/tests/streamlit/runtime/caching/cache_utils_test.py follows project test conventions (typed tests, docstrings, parametrize for matrixed behavior).

No code-quality issues requiring changes were identified.

Test Coverage

Coverage for the modified behavior is good at the unit level.

  • test_get_positional_arg_name validates positional, kw-only, varargs, negative index, and out-of-range cases.
  • test_get_func_parameters_handles_pep649_annotations (Python 3.14-only) specifically validates the regression condition by simulating deferred annotation evaluation that raises NameError, then verifying _get_func_parameters() handles it.

Given the scope (internal caching utility behavior), unit tests are adequate. No e2e test additions are required for this change.

Backwards Compatibility

No breaking API changes were introduced.

  • The change is internal to cache argument/signature handling.
  • For Python 3.10-3.13, behavior remains on the existing inspect.signature(func) path.
  • For Python 3.14+, behavior is adjusted only to avoid annotation evaluation side effects while preserving parameter extraction semantics.

This appears backwards compatible for existing Streamlit users.

Security & Risk

No security-sensitive surfaces are modified (no auth/session transport, routing, cookie/CSRF logic, file serving, or frontend runtime execution changes).

Regression risk is low and localized to function signature introspection used for cache key argument mapping.

External test recommendation

  • Recommend external_test: No
  • Triggered categories: None
  • Evidence:
    • lib/streamlit/runtime/caching/cache_utils.py: Internal Python signature-introspection logic only; no routing/auth/websocket/embedding/storage/security-header changes.
    • lib/tests/streamlit/runtime/caching/cache_utils_test.py: Unit-test-only additions validating cache utility behavior.
  • Suggested external_test focus areas:
    • None required for this PR scope.
    • Optional sanity check (non-blocking): verify @st.cache_data with TYPE_CHECKING-only annotation imports in a real app on Python 3.14.
  • Confidence: High
  • Assumptions and gaps: Assessment is based on the PR diff and changed files only; no runtime execution was performed in this review.

Accessibility

Not applicable for this PR: no frontend UI, DOM, keyboard interaction, ARIA, or visual rendering changes.

Recommendations

  1. Optional: add a second regression test using a real function definition pattern with if TYPE_CHECKING: imports (in addition to the synthetic __annotate__ simulation) to further document the user-facing failure mode.

Verdict

APPROVED: The fix is focused, low risk, adequately unit-tested, and ready to merge.


This is an automated AI review by gpt-5.3-codex-high. Please verify the feedback and use your judgment.

📋 Review by `opus-4.6-thinking`

Summary

This PR fixes a Python 3.14 compatibility issue where @st.cache_data and @st.cache_resource decorators crash with NameError when decorated functions use type annotations referencing types imported under if TYPE_CHECKING:. On Python 3.14+, PEP 649 introduces deferred annotation evaluation, causing inspect.signature() to fail when it tries to evaluate annotations that reference types not available at runtime.

The fix adds a _get_func_parameters() helper that uses annotation_format=Format.STRING on Python 3.14+ to retrieve parameter names and kinds without evaluating annotations. This is correct because the cache key computation only needs parameter names, not annotation values.

Changed files:

  • lib/streamlit/runtime/caching/cache_utils.py — Added _get_func_parameters() helper; updated _get_positional_arg_name() to use it.
  • lib/tests/streamlit/runtime/caching/cache_utils_test.py — Added parametrized tests for _get_positional_arg_name() and a Python 3.14-specific regression test for the PEP 649 scenario.

Code Quality

The implementation is clean, minimal, and well-targeted:

  • The new _get_func_parameters() helper is properly prefixed with _ to indicate it's module-private, consistent with the project's conventions (lib/AGENTS.md).
  • The docstring clearly explains the rationale for the version-conditional behavior.
  • The conditional import of annotationlib inside the sys.version_info >= (3, 14) guard is correct since annotationlib is a new standard library module only available in Python 3.14+.
  • The version check is appropriate and follows the "prioritize new features in Python 3.10+" guideline.
  • The function correctly uses from __future__ import annotations at the module level per project conventions.

Minor style note (non-blocking): In the test file, import inspect and import types (lines 117-118 of the test) are inside the test function body. Per lib/tests/AGENTS.md, imports should be at the top-level unless there is a specific reason. These standard library modules are available on all Python versions, so they could be top-level. However, since from annotationlib import Format (line 120) must be inside the function (Python 3.14+ only), keeping the related imports together in the same function is a reasonable readability choice.

Test Coverage

Test coverage is good:

  1. test_get_positional_arg_name — Well-structured parametrized test with 9 cases covering positional args (first/second/third), negative index, out-of-range, keyword-only parameters, and varargs. Follows the @pytest.mark.parametrize pattern recommended by lib/tests/AGENTS.md.

  2. test_get_func_parameters_handles_pep649_annotations — Appropriately skipped on Python < 3.14. The test creates a realistic simulation of PEP 649 behavior by constructing a function with a custom __annotate__ that raises NameError for Format.VALUE but returns strings for Format.STRING. It verifies both the problem (standard inspect.signature() raises NameError) and the fix (_get_func_parameters handles it gracefully).

  3. All new test functions have docstrings, are type-annotated, and use pytest-native patterns as required.

One observation: There are no E2E tests for this change, which is acceptable since this is a backend-only internal bug fix that doesn't affect the frontend or any public API behavior visible in the UI. The unit tests adequately cover the fix.

Backwards Compatibility

No backwards compatibility concerns:

  • On Python < 3.14, the behavior is identical — inspect.signature(func) is called with no extra arguments.
  • On Python 3.14+, annotation_format=Format.STRING only changes how annotations are returned (as strings instead of evaluated objects), but since the code only inspects Parameter.name and Parameter.kind, the observable behavior is unchanged.
  • Both _get_func_parameters and _get_positional_arg_name are private functions (underscore-prefixed), so no public API surface is affected.

Security & Risk

No security concerns. This change is purely about parameter introspection for cache key computation. It does not touch:

  • Network, WebSocket, or authentication behavior
  • File serving or path handling
  • HTML/Markdown rendering or sanitization
  • Any external dependencies or services

The risk is very low. The worst case on a regression would be returning incorrect parameter names (affecting cache key uniqueness), but the implementation is straightforward and well-tested.

Potential follow-up (outside scope of this PR): Other inspect.signature() call sites in the codebase may also be affected by PEP 649 on Python 3.14+:

  • lib/streamlit/elements/help.py:193_get_signature() catches ValueError and TypeError but not NameError. If st.help() is called on a user function with TYPE_CHECKING-only annotations, it could crash on Python 3.14+.
  • lib/streamlit/runtime/fragment.py:274@st.fragment decorator copies the user function's signature wrapped in contextlib.suppress(AttributeError), which won't catch NameError.
  • lib/streamlit/runtime/metrics_util.py:556 — Same pattern as fragment.py.

These are separate issues and not blockers for this PR, but worth tracking.

External test recommendation

  • Recommend external_test: No
  • Triggered categories: None
  • Evidence:
    • lib/streamlit/runtime/caching/cache_utils.py: Pure internal Python utility change for parameter introspection. No network, auth, embedding, asset serving, or cross-origin impact.
    • lib/tests/streamlit/runtime/caching/cache_utils_test.py: Test-only additions.
  • Suggested external_test focus areas: None applicable.
  • Confidence: High
  • Assumptions and gaps: None. The change is entirely within internal caching utilities with no external-facing behavior changes.

Accessibility

No frontend changes in this PR. No accessibility considerations apply.

Recommendations

  1. (Non-blocking, follow-up) Consider auditing the other inspect.signature() call sites in the codebase for the same PEP 649 issue — particularly lib/streamlit/elements/help.py:193 (_get_signature()), lib/streamlit/runtime/fragment.py:274, and lib/streamlit/runtime/metrics_util.py:556. These could cause similar NameError crashes on Python 3.14+ for user functions with TYPE_CHECKING-only annotations. A shared helper or centralized fix pattern could address all sites.

  2. (Non-blocking, minor style) The import inspect and import types in test_get_func_parameters_handles_pep649_annotations (lines 117-118) could be moved to top-level per the test guidelines, since they're standard library modules. However, keeping them grouped with the annotationlib import for readability is a reasonable trade-off.

Verdict

APPROVED: Clean, well-targeted fix for Python 3.14 PEP 649 compatibility in cache decorators with good test coverage. The implementation is minimal, backwards-compatible, and follows project conventions.


This is an automated AI review by opus-4.6-thinking.

On Python 3.14+, inspect.signature() can raise NameError for functions
with TYPE_CHECKING-only annotations due to PEP 649 deferred evaluation.

- st.help: Add NameError handling alongside ValueError
- st.fragment: Add NameError to contextlib.suppress
- metrics_util: Add NameError to contextlib.suppress

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@lukasmasuch lukasmasuch changed the title [fix] Python 3.14 PEP 649 annotation compatibility for cache decorators [fix] Python 3.14 PEP 649 annotation compatibility across inspect.signature() sites Mar 11, 2026
@lukasmasuch lukasmasuch added the ai-review If applied to PR or issue will run AI review workflow label Mar 11, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Mar 11, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Summary

This PR fixes Python 3.14 compatibility issues caused by PEP 649 (deferred evaluation of annotations). When inspect.signature() evaluates annotations that reference types imported under if TYPE_CHECKING:, it now raises NameError on Python 3.14. The fix addresses four call sites:

  • @st.cache_data / @st.cache_resource — New _get_func_parameters() helper using annotation_format=Format.STRING to avoid annotation evaluation.
  • st.help() — Added NameError to the except clause in _get_signature().
  • @st.fragment — Added NameError to contextlib.suppress().
  • Internal @gather_metrics decorator — Added NameError to contextlib.suppress().

Code Quality

All three reviewers agree: the code is clean, well-structured, targeted, and follows existing codebase patterns. The _get_func_parameters() helper correctly uses annotation_format=Format.STRING on Python 3.14+ (via annotationlib.Format) to retrieve parameter names/kinds without evaluating annotations. The version guard (sys.version_info >= (3, 14)) and conditional import of annotationlib are appropriate. The NameError additions to exception handlers in help.py, fragment.py, and metrics_util.py are minimal and narrowly scoped, making accidental suppression of unrelated NameErrors very unlikely.

Comments explaining the PEP 649 rationale at each call site are justified — they document non-obvious intent.

Unique observation (opus-4.6-thinking): inspect.getfullargspec() at metrics_util.py line 373 internally invokes inspect.signature() and could similarly raise NameError on Python 3.14 for user-defined functions with deferred annotations. However, it is already protected by a broad try/except Exception (lines 527–530), so it would cause silent telemetry loss rather than a user-visible error. This is acceptable but may warrant a follow-up.

Test Coverage

Agreement: All reviewers agree the new tests in cache_utils_test.py are well-written:

  1. test_get_positional_arg_name — Parametrized test covering positional args, negative index, out-of-range, keyword-only, and varargs cases.
  2. test_get_func_parameters_handles_pep649_annotations — Simulates PEP 649 behavior via a custom __annotate__ function; appropriately skipped on Python < 3.14; validates both that inspect.signature() raises NameError (confirming test setup validity) and that _get_func_parameters() handles it gracefully.

Minor disagreement on test gaps:

  • gemini-3.1-pro rated coverage as "comprehensive" with no recommendations.
  • gpt-5.3-codex-high and opus-4.6-thinking both noted that the NameError handling paths in help.py, fragment.py, and metrics_util.py lack dedicated unit tests. Both agree this is non-blocking given the defensive and simple nature of the changes, and that existing E2E tests would catch regressions.

Resolution: The gap is real but low-risk. Adding targeted tests for these paths in a follow-up would strengthen long-term regression protection, but should not block this PR.

Backwards Compatibility

Unanimous agreement: No breaking changes. On Python < 3.14, all code paths remain identical. The added NameError exception types are inert on older Python versions since inspect.signature() wouldn't raise NameError. On Python 3.14+, the new behavior correctly handles deferred annotation evaluation while preserving the same external behavior.

Security & Risk

Unanimous agreement: No security concerns. Changes are limited to Python introspection and decorator metadata handling. No auth, session, routing, WebSocket, embedding, external resource, or code execution surfaces are modified. Regression risk is very low and localized.

External test recommendation

Unanimous agreement: No external test needed.

  • Triggered categories: None
  • Evidence: All changes are Python-side error handling for inspect.signature() annotation evaluation. No changes to routing, auth, WebSocket, embedding, assets, cross-origin, SiS, storage, or security headers.
  • Confidence: High

Accessibility

N/A — No frontend changes in this PR.

Recommendations

  1. (Non-blocking) Consider adding focused regression tests for the NameError handling paths in help.py, fragment.py, and metrics_util.py in a follow-up to mirror the strong coverage added for caching. (Raised by gpt-5.3-codex-high and opus-4.6-thinking)
  2. (Non-blocking) Consider a follow-up for inspect.getfullargspec() in metrics_util.py line 373: while already protected by a broad try/except Exception, applying a similar annotation_format=Format.STRING approach could preserve telemetry accuracy on Python 3.14. (Raised by opus-4.6-thinking)

Reviewer Consensus

Reviewer Verdict Notes
gemini-3.1-pro APPROVED No issues identified
gpt-5.3-codex-high APPROVED Non-blocking test gap noted
opus-4.6-thinking APPROVED Non-blocking follow-up suggestions

All three reviewers approved unanimously. No critical or blocking issues were raised.

Verdict

APPROVED: Clean, well-tested fix for Python 3.14 PEP 649 compatibility. All three reviewers approve unanimously. The changes are minimal, correctly scoped, backwards compatible, and carry no security or regression risk. Minor recommendations for follow-up test coverage are non-blocking.


This is a consolidated AI review by opus-4.6-thinking.


📋 Review by `gemini-3.1-pro`

Summary

This PR fixes compatibility issues with Python 3.14's implementation of PEP 649 (deferred evaluation of annotations). It addresses NameError exceptions that can occur when inspect.signature() evaluates annotations that reference types imported under if TYPE_CHECKING:. The fix involves suppressing NameError in decorators where the signature is just used for metadata, and using annotation_format=Format.STRING when only parameter names/kinds are needed (e.g., in caching).

Code Quality

The code quality is excellent. The changes are targeted, well-commented, and use the correct Python 3.14+ API (annotationlib.Format.STRING) to avoid evaluating annotations when only parameter names are needed. The suppression of NameError in lib/streamlit/runtime/fragment.py and lib/streamlit/runtime/metrics_util.py is appropriate since setting __signature__ is a nice-to-have for decorator transparency.

Test Coverage

The unit test coverage is comprehensive. The author added tests for _get_positional_arg_name and a specific test test_get_func_parameters_handles_pep649_annotations that cleverly simulates the PEP 649 behavior by mocking __annotate__ to raise NameError when evaluated as a value. This ensures the fix works correctly and won't regress.

Backwards Compatibility

There are no backwards compatibility concerns. The changes only affect Python 3.14+ behavior and gracefully fall back to existing behavior on older Python versions.

Security & Risk

No security concerns or regression risks identified. The changes are confined to metadata inspection and do not affect runtime execution logic, routing, or data handling.

External test recommendation

No. The changes are purely related to Python 3.14 compatibility with inspect.signature(). They do not touch routing, auth, websocket, embedding, assets, cross-origin, SiS, storage, or security headers.

Accessibility

N/A - No frontend changes.

Recommendations

No changes needed. The implementation is clean and well-tested.

Verdict

APPROVED: The PR successfully fixes Python 3.14 compatibility issues with PEP 649 annotations in a clean and well-tested manner.


This is an automated AI review by gemini-3.1-pro.

📋 Review by `gpt-5.3-codex-high`

Summary

This PR addresses Python 3.14 PEP 649 compatibility issues at multiple inspect.signature() call sites where deferred annotation evaluation can raise NameError (especially with TYPE_CHECKING-only imports). It adds defensive handling in st.help, @st.fragment, and internal metrics decorators, and introduces a dedicated helper in caching code to request string-form annotations on 3.14+.

Code Quality

The implementation is clean, targeted, and consistent with existing patterns:

  • lib/streamlit/runtime/caching/cache_utils.py uses a focused helper (_get_func_parameters) to centralize version-specific signature handling, avoiding duplicate logic and keeping _get_positional_arg_name simple.
  • lib/streamlit/elements/help.py, lib/streamlit/runtime/fragment.py, and lib/streamlit/runtime/metrics_util.py apply minimal, low-risk exception-scope changes that preserve existing behavior while handling the new 3.14 failure mode.
  • Comments clearly document the PEP 649 rationale at each modified call site.

No code quality issues were identified that should block merge.

Test Coverage

Coverage is directionally good for the caching path:

  • lib/tests/streamlit/runtime/caching/cache_utils_test.py adds parameterized coverage for positional-argument name resolution.
  • It also adds a Python 3.14-specific regression test that simulates deferred annotation evaluation and verifies the NameError-safe behavior.

Potential gap (non-blocking): there is no new direct regression test for the new NameError handling paths in:

  • lib/streamlit/elements/help.py (_get_signature)
  • lib/streamlit/runtime/fragment.py (decorator signature preservation block)
  • lib/streamlit/runtime/metrics_util.py (decorator signature preservation block)

Given the small and defensive nature of those changes, this is acceptable for merge, but adding targeted tests later would strengthen long-term regression protection.

Backwards Compatibility

No breaking changes found.

  • Existing behavior is preserved on Python <3.14.
  • On Python 3.14+, behavior improves by avoiding unexpected NameError crashes during signature introspection.
  • No public API shape changes, config changes, protocol changes, or dependency additions were introduced.

Security & Risk

No security concerns were identified.

  • Changes are limited to Python introspection and decorator metadata handling.
  • No auth/session, routing, websocket transport, file serving, external fetch, token/cookie, or code execution surfaces were modified.
  • Regression risk is low and localized.

External test recommendation

  • Recommend external_test: No
  • Triggered categories: None
  • Evidence:
    • lib/streamlit/runtime/caching/cache_utils.py: Signature-parameter introspection now uses annotation_format=Format.STRING on Python 3.14+; no networking, routing, auth, embedding, or storage changes.
    • lib/streamlit/elements/help.py: Adds NameError handling around inspect.signature; local introspection-only behavior.
    • lib/streamlit/runtime/fragment.py: Expands suppressed exceptions in decorator metadata copy/signature assignment; no iframe, cross-origin, or transport boundary changes.
    • lib/streamlit/runtime/metrics_util.py: Same defensive decorator metadata adjustment; telemetry wrapper logic unchanged in external-surface areas.
    • lib/tests/streamlit/runtime/caching/cache_utils_test.py: Unit-test-only updates.
  • Suggested external_test focus areas:
    • None required by risk checklist hits.
    • Optional sanity-only check (not required): Python 3.14 deployment smoke test for @st.cache_data, st.help, and @st.fragment usage with TYPE_CHECKING-guarded annotations.
  • Confidence: High
  • Assumptions and gaps: Assessment is based on diff/static review only; no runtime execution was performed in this review.

Accessibility

No frontend/UI accessibility-impacting code was changed in this PR.

Recommendations

  1. Consider adding focused regression tests for the new NameError paths in help, fragment, and metrics_util to mirror the strong coverage added for caching.

Verdict

APPROVED: The patch is well-scoped, backward compatible, and addresses the Python 3.14 PEP 649 regression without introducing material security or compatibility risk.


This is an automated AI review by gpt-5.3-codex-high. Please verify the feedback and use your judgment.

📋 Review by `opus-4.6-thinking`

Summary

This PR fixes Python 3.14 compatibility across multiple inspect.signature() call sites in the Streamlit library. Python 3.14 introduces PEP 649, which defers annotation evaluation until access time. When a function uses type annotations referencing types imported under if TYPE_CHECKING:, calling inspect.signature() on that function at runtime now raises NameError because those types aren't actually imported.

The four affected locations are:

  • @st.cache_data / @st.cache_resource — New _get_func_parameters() helper using annotation_format=Format.STRING to avoid annotation evaluation.
  • st.help() — Added NameError to the except clause in _get_signature().
  • @st.fragment — Added NameError to contextlib.suppress().
  • Internal @gather_metrics decorator — Added NameError to contextlib.suppress().

Code Quality

The code is clean, well-structured, and follows existing patterns in the codebase.

_get_func_parameters() helper (lib/streamlit/runtime/caching/cache_utils.py, lines 605–620): This is the most substantial change. The approach is correct — on Python 3.14+, it imports annotationlib.Format and passes annotation_format=Format.STRING to inspect.signature(), which returns string representations of annotations instead of evaluating them. Since only parameter names and kinds are needed (not actual annotation types), this is the ideal solution. The version check sys.version_info >= (3, 14) is appropriate, and the conditional import of annotationlib is correctly placed inside the version guard to avoid ImportError on older Python.

Exception handling additions: The NameError additions to the except clauses in help.py (line 194) and contextlib.suppress() calls in fragment.py (line 270) and metrics_util.py (line 552) are minimal and appropriate. In each case, the scope of the suppressed block is narrow (only inspect.signature() and __dict__.update()), making it very unlikely that a legitimate NameError from another source would be accidentally suppressed.

Naming: _get_func_parameters is properly prefixed with _ as a private function, and the name clearly conveys its purpose.

Comments: The inline comments explaining the NameError are useful in this case — they explain non-obvious intent (why a particular exception type is caught).

Minor observation: inspect.getfullargspec() at lib/streamlit/runtime/metrics_util.py line 373 internally also calls inspect.signature() and could similarly raise NameError on Python 3.14 for user-defined functions with deferred annotations. However, this call is already protected by a broad try/except Exception at line 527–530, so it would result in a silent telemetry failure rather than a user-visible error. This is acceptable behavior, but the team may want to address it in a follow-up if telemetry accuracy for Python 3.14 users is important.

Test Coverage

New tests in lib/tests/streamlit/runtime/caching/cache_utils_test.py:

  1. test_get_positional_arg_name (lines 72–101): Well-structured parametrized test covering positional args, negative index, out-of-range, keyword-only, and varargs cases. Uses proper ids for readable test output. This tests the existing _get_positional_arg_name() function which previously had no dedicated unit tests.

  2. test_get_func_parameters_handles_pep649_annotations (lines 104–155): Excellent test that correctly simulates PEP 649 behavior by creating a custom __annotate__ function that raises NameError for Format.VALUE but returns strings for Format.STRING. Appropriately skipped on Python < 3.14. The test first verifies that inspect.signature() does raise NameError (confirming the test setup is valid), then verifies that _get_func_parameters() handles it gracefully. The imports inside the test function (annotationlib) are justified since the module only exists in Python 3.14+.

Test gaps: There are no dedicated unit tests for the NameError handling in help.py, fragment.py, or metrics_util.py. These would be difficult to write for Python < 3.14 without mocking, and the changes are simple exception handling additions. The risk is low, and the PEP 649 test in cache_utils_test.py validates the core fix pattern. Existing E2E tests would catch any regressions in the decorator behavior.

The test file correctly follows the AGENTS.md guidelines: uses from __future__ import annotations, TYPE_CHECKING imports, @pytest.mark.parametrize, standalone pytest functions (not class-based), and proper docstrings.

Backwards Compatibility

No breaking changes. The code paths are fully backwards compatible:

  • Python < 3.14: All code paths remain identical to the previous behavior. The version check in _get_func_parameters() falls through to the original inspect.signature(func) call. The added NameError exception types in the other files are inert since NameError wouldn't be raised by inspect.signature() on these Python versions.
  • Python 3.14+: The new behavior correctly handles deferred annotation evaluation while preserving the same external behavior (parameter names, kinds, and signature strings).

Security & Risk

No security concerns. The changes are purely defensive error handling for a Python version compatibility issue. No network, auth, embedding, or external resource changes are involved. The annotationlib import is a standard library module in Python 3.14.

Regression risk: Very low. The changes are narrow in scope and well-guarded by version checks and exception handling.

External test recommendation

  • Recommend external_test: No
  • Triggered categories: None
  • Evidence:
    • All changes are Python-side error handling for inspect.signature() annotation evaluation
    • No changes to routing, auth, WebSocket, embedding, assets, cross-origin, SiS, storage, or security headers
  • Suggested external_test focus areas: N/A
  • Confidence: High
  • Assumptions and gaps: None — this is purely internal Python compatibility work with no external-facing behavior changes

Accessibility

Not applicable — no frontend changes in this PR.

Recommendations

  1. Consider a follow-up for inspect.getfullargspec() in metrics_util.py line 373: While already protected by a broad try/except Exception, the getfullargspec() call internally invokes inspect.signature() and could fail with NameError on Python 3.14. This would cause silent telemetry loss for affected commands. A similar annotation_format=Format.STRING approach (or switching to the _get_func_parameters helper) could preserve telemetry accuracy.

Verdict

APPROVED: Clean, well-tested fix for Python 3.14 PEP 649 compatibility that addresses all inspect.signature() call sites in the library with appropriate, minimal changes and no backwards compatibility concerns.


This is an automated AI review by opus-4.6-thinking.

lukasmasuch and others added 2 commits March 11, 2026 19:49
- Move get_func_parameters to type_util.py to avoid duplication
- Add _get_arg_keywords in metrics_util.py for PEP 649-safe arg parsing
- Add comprehensive tests for st.help, st.fragment, and metrics_util

Co-Authored-By: Claude Opus 4.6 <[email protected]>
On Python 3.14, inspect.getfullargspec() catches NameError from
annotation evaluation and re-raises as TypeError("unsupported callable").
Update the test to accept both exception types.

Also consolidates the _get_func_parameters wrapper into type_util.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@lukasmasuch lukasmasuch added the ai-review If applied to PR or issue will run AI review workflow label Mar 11, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Mar 11, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Summary

This PR fixes Python 3.14 compatibility across multiple inspect.signature() and inspect.getfullargspec() call sites affected by PEP 649 (deferred evaluation of annotations). When annotations reference types imported only under if TYPE_CHECKING: blocks, Python 3.14's deferred evaluation raises NameError at introspection time. The fix introduces a centralized get_func_parameters() helper in type_util.py that uses annotation_format=Format.STRING on Python 3.14+ to avoid triggering annotation evaluation, along with targeted NameError suppression at decorator sites that copy __signature__.

Changed files:

  • lib/streamlit/type_util.py — New get_func_parameters() helper
  • lib/streamlit/runtime/caching/cache_utils.py — Uses the new helper
  • lib/streamlit/runtime/metrics_util.py — New _get_arg_keywords() helper; NameError suppression in gather_metrics
  • lib/streamlit/runtime/fragment.pyNameError suppression in _fragment
  • lib/streamlit/elements/help.pyNameError catch in _get_signature()
  • 4 test files with comprehensive PEP 649 unit tests

Code Quality

The code is clean, well-structured, and follows Streamlit's established patterns. All three reviewers agreed on this.

  • Centralized fix: The get_func_parameters() helper in type_util.py is the right approach — it provides a single point of defense that callers reuse rather than scattering version checks everywhere. (All reviewers agreed)
  • Version gating is correct: The sys.version_info >= (3, 14) check properly gates the annotationlib import. (All reviewers agreed)
  • Docstrings and comments are clear and include links to the relevant GitHub issue. (All reviewers agreed)

Point of disagreement — POSITIONAL_ONLY in _get_arg_keywords():

  • gemini-3.1-pro flagged that _get_arg_keywords() only filters for POSITIONAL_OR_KEYWORD parameters, but getfullargspec().args also includes POSITIONAL_ONLY parameters.
  • opus-4.6-thinking stated the function "correctly replicates getfullargspec().args behavior."
  • gpt-5.3-codex-high did not flag this.

Resolution: Gemini is technically correct. CPython's getfullargspec() returns posonlyargs + args as its args field (verified from CPython source: return FullArgSpec(posonlyargs + args, ...)), so it includes both POSITIONAL_ONLY and POSITIONAL_OR_KEYWORD parameters. The _get_arg_keywords() function's docstring claims to match getfullargspec().args but omits POSITIONAL_ONLY. However, this is a non-blocking issue in practice: no Streamlit function decorated with @gather_metrics currently uses the positional-only (/) syntax, and adding POSITIONAL_ONLY to the filter is a trivial one-line fix. The docstring inaccuracy and minor behavioral gap are worth fixing but are not blocking.

Test placement (gemini-3.1-pro only): test_get_func_parameters_handles_pep649_annotations tests get_func_parameters from streamlit.type_util but lives in cache_utils_test.py. This is a minor organizational nit — it could reasonably live in type_util_test.py. However, the test also validates the cache argument-name resolution flow, so its current placement is defensible. Non-blocking.

Test Coverage

All reviewers agreed that test coverage is strong. Each changed call site has a corresponding test that:

  1. Verifies the precondition (that inspect.signature() or inspect.getfullargspec() actually raises NameError/TypeError)
  2. Verifies the postcondition (that the fix handles it gracefully)

Tests are appropriately gated with @pytest.mark.skipif(sys.version_info < (3, 14)). The simulation technique (creating a types.FunctionType with a custom __annotate__) is sound for triggering PEP 649 behavior.

Minor observation (non-blocking): The annotate_raises helper is duplicated across all four test files. A shared test utility could reduce duplication if more PEP 649 tests are added in the future.

Backwards Compatibility

All reviewers agreed: no breaking changes. On Python < 3.14, code takes the existing inspect.signature() path unchanged. The NameError additions to exception handling are additive and won't trigger on older versions.

Security & Risk

All reviewers agreed: no security concerns. Changes are purely internal Python introspection fixes with no networking, auth, WebSocket, CORS, session, or user-data handling impact. Regression risk is low.

External test recommendation

  • Recommend external_test: No
  • Triggered categories: None
  • Evidence: All changes are internal Python introspection fixes. No frontend changes. No networking, routing, auth, embedding, or cross-origin behavior impact.
  • Confidence: High
  • All three reviewers unanimously agreed on this assessment.

Accessibility

No frontend changes. Accessibility is not impacted. (All reviewers agreed)

Recommendations

  1. (Minor, non-blocking) In _get_arg_keywords(), include POSITIONAL_ONLY parameters to fully match the documented getfullargspec().args behavior:
    names = [
        p.name for p in params if p.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.POSITIONAL_ONLY)
    ]
  2. (Minor, non-blocking) Consider moving test_get_func_parameters_handles_pep649_annotations to type_util_test.py for better organizational alignment.
  3. (Minor, non-blocking) Consider noting in the get_func_parameters docstring that returned Parameter.annotation values will be strings (not types) on Python 3.14+ when using Format.STRING, in case future callers need annotations.

Reviewer Consensus

Reviewer Verdict Key Findings
gemini-3.1-pro CHANGES REQUESTED POSITIONAL_ONLY bug in _get_arg_keywords; test placement
gpt-5.3-codex-high APPROVED No blocking issues
opus-4.6-thinking APPROVED No blocking issues; minor observations

All expected models completed their reviews successfully.

Areas of agreement (all 3 reviewers):

  • Centralized helper is the right design approach
  • Version gating is correct
  • Test coverage is strong
  • No security, backwards compatibility, or accessibility concerns
  • No external test needed

Areas of disagreement (resolved above):

  • Whether _get_arg_keywords() has a bug with POSITIONAL_ONLY params (gemini correct, but non-blocking in practice)

Verdict

APPROVED: The majority of reviewers (2/3) approved, and the issue raised by gemini-3.1-pro — while technically correct — is non-blocking: it affects no current Streamlit code (no functions use positional-only syntax), is a one-line fix, and is better addressed as a follow-up nit. The PR is a well-structured, well-tested compatibility fix with low risk and no security or backwards-compatibility concerns.


This is a consolidated AI review by opus-4.6-thinking.


📋 Review by `gemini-3.1-pro`

Summary

This PR fixes compatibility issues with Python 3.14's PEP 649 deferred annotation evaluation. It introduces a centralized get_func_parameters helper to safely extract function parameters without evaluating annotations (which can raise NameError for TYPE_CHECKING-only imports) and updates several inspect.signature() call sites to handle NameError or use the new helper.

Code Quality

The code changes are well-structured and the centralized helper in type_util.py is a good approach. However, there is a bug in the implementation of _get_arg_keywords in lib/streamlit/runtime/metrics_util.py and a minor test placement issue.

  • lib/streamlit/runtime/metrics_util.py (line 388): _get_arg_keywords filters parameters by POSITIONAL_OR_KEYWORD to match getfullargspec().args. However, getfullargspec().args also includes POSITIONAL_ONLY arguments. By omitting POSITIONAL_ONLY, any positional-only arguments will be skipped in telemetry tracking, causing a mismatch with the args tuple.
  • lib/tests/streamlit/runtime/caching/cache_utils_test.py: The test test_get_func_parameters_handles_pep649_annotations directly tests get_func_parameters from streamlit.type_util. It would be more appropriate to place this test in lib/tests/streamlit/type_util_test.py.

Test Coverage

The changes are well-tested. The author added specific tests using mocked __annotate__ methods to simulate Python 3.14+ behavior and verify that the NameError is handled gracefully.

Backwards Compatibility

The changes are backwards compatible and only affect internal reflection and telemetry mechanisms. The use of annotation_format=Format.STRING is correctly gated behind a sys.version_info >= (3, 14) check.

Security & Risk

No security concerns or regression risks identified. The changes are confined to internal reflection and do not affect security-sensitive areas.

External test recommendation

  • Recommend external_test: No
  • Triggered categories: None
  • Evidence:
    • lib/streamlit/type_util.py: Internal reflection changes to handle Python 3.14+ deferred annotations.
    • lib/streamlit/runtime/metrics_util.py: Internal telemetry reflection changes.
  • Suggested external_test focus areas: N/A
  • Confidence: High
  • Assumptions and gaps: None

Accessibility

N/A

Recommendations

  1. In lib/streamlit/runtime/metrics_util.py, update _get_arg_keywords to include POSITIONAL_ONLY parameters to fully match getfullargspec().args:
    names = [
        p.name for p in params if p.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.POSITIONAL_ONLY)
    ]
  2. Move test_get_func_parameters_handles_pep649_annotations from lib/tests/streamlit/runtime/caching/cache_utils_test.py to lib/tests/streamlit/type_util_test.py since it directly tests streamlit.type_util.get_func_parameters.

Verdict

CHANGES REQUESTED: Please fix the missing POSITIONAL_ONLY parameter handling in _get_arg_keywords and consider moving the test to the appropriate file.


This is an automated AI review by gemini-3.1-pro.

📋 Review by `gpt-5.3-codex-high`

Summary

This PR hardens Python 3.14 compatibility for PEP 649 deferred annotations at all current inspect.signature()-related call sites in lib/streamlit. It introduces a centralized get_func_parameters() helper and updates st.help, caching argument-name extraction, @st.fragment, and telemetry decorators to avoid NameError failures from TYPE_CHECKING-only annotations.

Code Quality

The changes are focused, minimal, and consistent with existing patterns:

  • Shared logic is centralized in lib/streamlit/type_util.py (get_func_parameters()), reducing repeated version-specific handling.
  • Call-site fixes in lib/streamlit/elements/help.py, lib/streamlit/runtime/fragment.py, lib/streamlit/runtime/metrics_util.py, and lib/streamlit/runtime/caching/cache_utils.py are narrowly scoped and maintain current behavior/fallback semantics.
  • Comments clearly describe the Python 3.14 failure mode and why fallback behavior is safe.

No code-quality blockers were identified.

Test Coverage

Coverage is strong for the changed behavior:

  • lib/tests/streamlit/elements/help_test.py adds a targeted regression test for _get_signature() with simulated PEP 649 deferred annotation failures.
  • lib/tests/streamlit/runtime/caching/cache_utils_test.py validates get_func_parameters() behavior via cache arg-name resolution flows.
  • lib/tests/streamlit/runtime/fragment_test.py verifies decorator signature-preservation logic tolerates NameError.
  • lib/tests/streamlit/runtime/metrics_util_test.py covers both _get_arg_keywords() and gather_metrics() decorator behavior under deferred-annotation failures.

Tests are appropriately gated with pytest.mark.skipif(sys.version_info < (3, 14)) for the PEP 649-specific scenarios. No e2e gaps appear critical for this backend-introspection fix.

Backwards Compatibility

No breaking API changes were detected:

  • Public APIs (st.help, @st.fragment, caching decorators, telemetry wrapper behavior) preserve existing outputs and control flow while avoiding Python 3.14 annotation-evaluation crashes.
  • Python <3.14 behavior remains on the existing inspect.signature(func) path.

This should be backwards compatible for existing users.

Security & Risk

No security concerns were identified in this diff:

  • No changes to auth/session handling, websocket transport, routing, file serving, CORS/CSP, cookies, OAuth, or external network access.
  • No new dependency additions, dynamic code execution additions, or user-input parsing surfaces.

Primary risk is low and limited to metadata/signature introspection behavior in Python runtime utilities.

External test recommendation

  • Recommend external_test: No
  • Triggered categories: None
  • Key evidence from changed files:
    • lib/streamlit/type_util.py: internal signature/parameter extraction compatibility helper only.
    • lib/streamlit/runtime/caching/cache_utils.py: swaps positional-arg name extraction to helper, no network/runtime boundary changes.
    • lib/streamlit/runtime/fragment.py: suppresses NameError during decorator metadata assignment only.
    • lib/streamlit/runtime/metrics_util.py: telemetry arg extraction and decorator metadata preservation updates only.
    • lib/streamlit/elements/help.py: graceful fallback in signature rendering only.
  • Suggested external test focus areas:
    • None required for this PR; if desired, optional smoke test on Python 3.14 for st.help, cached functions, and fragment-decorated functions in a hosted deployment.
  • Confidence: High
  • Assumptions/gaps: Assessment is diff-based only; no runtime execution was performed in this review.

Accessibility

No frontend/UI code was changed, so no new accessibility risks were introduced by this PR.

Recommendations

  1. Merge as-is; no blocking issues found.
  2. Ensure Python 3.14 remains in CI coverage so these regression tests continue guarding deferred-annotation behavior.

Verdict

APPROVED: The PR addresses the Python 3.14 PEP 649 failure mode comprehensively across current inspect.signature()-related call sites with strong targeted unit-test coverage and low regression/security risk.


This is an automated AI review by gpt-5.3-codex-high. Please verify the feedback and use your judgment.

📋 Review by `opus-4.6-thinking`

Summary

This PR fixes Python 3.14 compatibility across multiple inspect.signature() and inspect.getfullargspec() call sites. PEP 649 (deferred evaluation of annotations), landing in Python 3.14, causes NameError when annotations reference types imported only under if TYPE_CHECKING: blocks. The fix introduces a centralized get_func_parameters() helper in type_util.py that uses annotation_format=Format.STRING on Python 3.14+ to avoid triggering annotation evaluation, along with targeted NameError suppression at decorator sites that copy __signature__.

Changed files:

  • lib/streamlit/type_util.py — New get_func_parameters() helper
  • lib/streamlit/runtime/caching/cache_utils.py — Uses the new helper
  • lib/streamlit/runtime/metrics_util.py — New _get_arg_keywords() helper; NameError suppression in gather_metrics
  • lib/streamlit/runtime/fragment.pyNameError suppression in _fragment
  • lib/streamlit/elements/help.pyNameError catch in _get_signature()
  • 4 test files with comprehensive PEP 649 unit tests

Code Quality

The code is clean, well-structured, and follows Streamlit's established patterns:

  • Centralized fix: The get_func_parameters() helper in type_util.py is the right approach — it provides a single point of defense that callers (cache_utils.py, metrics_util.py) can reuse rather than scattering version checks everywhere.

  • Version gating is correct: The sys.version_info >= (3, 14) check properly gates the annotationlib import which only exists in Python 3.14+. The conditional import inside the function body avoids import errors on older Python versions.

  • Type annotations are sound: The inspect module is imported under TYPE_CHECKING for the return type annotation list[inspect.Parameter], while the runtime import happens inside the function body. This works correctly with from __future__ import annotations.

  • _get_arg_keywords() correctly replicates getfullargspec().args behavior: It filters to POSITIONAL_OR_KEYWORD params and re-inserts self for bound methods (since inspect.signature() strips self for bound methods while getfullargspec() does not). This preserves the telemetry argument-mapping behavior.

  • _get_arg_keywords naming: The function is properly prefixed with _ since it's only used within metrics_util.py, following the module-private convention documented in lib/AGENTS.md.

  • Docstrings: All new functions have clear, informative docstrings with links to the relevant GitHub issue. Comments are properly capitalized and use good grammar per the style guide.

No issues identified.

Test Coverage

Test coverage is thorough and well-designed:

  1. cache_utils_test.py — Tests get_func_parameters() via _get_positional_arg_name() with a synthetic PEP 649 function. Also adds a good parametrized test for _get_positional_arg_name covering happy path, boundary conditions (negative index, out-of-range), keyword-only, and varargs parameters.

  2. help_test.py — Tests that _get_signature() returns "(...)" as a fallback when NameError is raised.

  3. fragment_test.py — Tests that the @fragment decorator completes without error despite NameError from inspect.signature(), properly setting up and tearing down the required context_dg_stack.

  4. metrics_util_test.py — Tests both _get_arg_keywords() (verifying correct parameter name extraction) and gather_metrics() decorator (verifying it completes without error and the decorated function works).

All tests use @pytest.mark.skipif(sys.version_info < (3, 14)) appropriately, since the annotationlib module doesn't exist on older Python. The test simulation technique (creating a types.FunctionType with a custom __annotate__) is a sound way to trigger PEP 649 behavior.

Each test verifies both the precondition (that inspect.signature() or inspect.getfullargspec() actually raises NameError/TypeError) and the postcondition (that the fix handles it gracefully), which is good testing practice.

Minor note: Local imports of inspect, types, and annotationlib inside test functions are justified here since these tests are conditionally skipped and annotationlib doesn't exist on Python < 3.14.

Backwards Compatibility

No breaking changes. The PR is fully backwards compatible:

  • On Python < 3.14, get_func_parameters() takes the existing inspect.signature() path with no behavioral change.
  • The NameError additions to contextlib.suppress() and except clauses are additive — they won't trigger on older Python versions since PEP 649 deferred evaluation doesn't exist there.
  • _get_arg_keywords() is a new private function that replaces inspect.getfullargspec() (which was already deprecated) with equivalent behavior.
  • No public API changes.

Security & Risk

No security concerns. This PR:

  • Does not touch any security-sensitive areas (no changes to auth, WebSocket handling, file serving, CORS, CSP, session management, or user data handling).
  • Only modifies how function parameter introspection is performed — a purely internal operation.
  • The annotationlib.Format.STRING mode is safer than the default VALUE mode since it avoids executing annotation code entirely.
  • No new dependencies are introduced; annotationlib is a Python 3.14 standard library module.

Regression risk is low. The changes are defensive in nature (catching a new exception type) and version-gated. The fallback behavior on pre-3.14 Python is unchanged.

External test recommendation

  • Recommend external_test: No
  • Triggered categories: None
  • Evidence:
    • All changes are internal Python introspection fixes with no networking, routing, auth, embedding, asset serving, CORS, storage, or security header impact.
    • No frontend changes at all.
    • No changes to WebSocket, session management, or cross-origin behavior.
  • Suggested external_test focus areas: N/A
  • Confidence: High
  • Assumptions and gaps: None. The changes are purely defensive Python 3.14 runtime compatibility fixes with no external integration surface.

Accessibility

No frontend changes are included in this PR. Accessibility is not impacted.

Recommendations

No blocking issues found. The implementation is clean, well-tested, and follows established patterns. A few minor observations (non-blocking):

  1. The annotate_raises helper function is duplicated across all four test files with slight variations. If future PEP 649 tests are added, consider extracting a shared test utility. For now, the duplication is acceptable given the tests are self-contained and skipped on most CI Python versions.

  2. Consider adding a brief comment or note in the get_func_parameters docstring mentioning that the returned Parameter.annotation values will be strings (not actual types) on Python 3.14+ when using Format.STRING. This is currently irrelevant since callers only use .name and .kind, but would be helpful if future callers need annotations.

Verdict

APPROVED: Well-structured bug fix for Python 3.14 PEP 649 compatibility with a centralized helper function, correct version gating, thorough test coverage, and no backwards compatibility or security concerns.


This is an automated AI review by opus-4.6-thinking.

@lukasmasuch lukasmasuch changed the title [fix] Python 3.14 PEP 649 annotation compatibility across inspect.signature() sites [fix] Python 3.14 PEP 649 annotation compatibility across inspect.signature() calls Mar 11, 2026
…docs

Address PR review recommendations:
- Include POSITIONAL_ONLY parameters in _get_arg_keywords() to fully match
  getfullargspec().args behavior (fixes potential telemetry arg mapping bug)
- Update get_func_parameters() docstring to note that annotations are strings
  on Python 3.14+
- Move get_func_parameters PEP 649 test to type_util_test.py for better
  organization
- Add test for positional-only parameter handling

Co-Authored-By: Claude Opus 4.6 <[email protected]>

See: https://github.com/streamlit/streamlit/issues/14324
"""
from streamlit import type_util
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: Remove this redundant import statement.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Removed 👍


On Python 3.14+, inspect.signature() raises NameError for annotations
referencing types imported under TYPE_CHECKING. Our fix uses
annotation_format=STRING to avoid evaluation.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: This should be annotation_format=Format.STRING

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Changed

st.help(st, width=width)


@pytest.mark.skipif(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion (non-blocking): All of these PEP 649 tests seem to be similar in structure, consider a shared test helper to reduce duplicative test code.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added a shared helper method 👍

- Move type_util import to top-level in metrics_util.py
- Fix docstring to use `annotation_format=Format.STRING`
- Create shared `create_pep649_function()` helper in testutil.py
- Refactor all PEP 649 tests to use the shared helper

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@lukasmasuch lukasmasuch force-pushed the lukasmasuch/issue-14324 branch from 6ed9102 to a9904e6 Compare March 12, 2026 23:47
@lukasmasuch lukasmasuch enabled auto-merge (squash) March 12, 2026 23:55
@lukasmasuch lukasmasuch merged commit 563e6eb into develop Mar 13, 2026
42 checks passed
@lukasmasuch lukasmasuch deleted the lukasmasuch/issue-14324 branch March 13, 2026 00:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:bugfix PR contains bug fix implementation impact:users PR changes affect end users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@st.cache_data crashes with NameError on Python 3.14 when annotations use TYPE_CHECKING imports (PEP 649)

3 participants