Skip to content

[DynamicContainers] Callback support for popovers#14075

Merged
sfc-gh-lwilby merged 1 commit intodevelopfrom
02-23-_dynamiccontainers_callback_support_for_popovers
Feb 25, 2026
Merged

[DynamicContainers] Callback support for popovers#14075
sfc-gh-lwilby merged 1 commit intodevelopfrom
02-23-_dynamiccontainers_callback_support_for_popovers

Conversation

@sfc-gh-lwilby
Copy link
Copy Markdown
Collaborator

@sfc-gh-lwilby sfc-gh-lwilby commented Feb 23, 2026

Describe your changes

Adds on_change callback support to st.popover, consistent with the existing callback pattern in other widgets (st.checkbox, st.toggle, etc.).

  • on_change accepts a callable in addition to the existing "ignore" / "rerun" string literals
  • Optional args and kwargs parameters pass through to the callback
  • The callback fires on any state change (both open and close). Users can check st.session_state[key] inside the callback to distinguish between open (True) and close (False) events.

GitHub Issue Link (if applicable)

Testing Plan

  • Unit Tests (Python) — lib/tests/streamlit/elements/layouts_test.py
  • E2E Tests — e2e_playwright/st_popover_test.py
  • Any manual testing needed?
    • Did some manual testing of using the callbacks and targeting to open/close.

Contribution License Agreement

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

@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Feb 23, 2026

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

Status Scanner 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.

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 23, 2026

✅ PR preview is ready!

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

@sfc-gh-lwilby sfc-gh-lwilby added security-assessment-completed change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users labels Feb 23, 2026
@sfc-gh-lwilby sfc-gh-lwilby force-pushed the 02-23-_dynamiccontainers_callback_support_for_popovers branch 2 times, most recently from 2869e33 to 8b76087 Compare February 23, 2026 15:20
@sfc-gh-lwilby sfc-gh-lwilby marked this pull request as ready for review February 23, 2026 15:45
Copilot AI review requested due to automatic review settings February 23, 2026 15:45
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

This pull request adds callback support to st.popover, extending its existing state management functionality to support user-defined callback functions in addition to the string literals "ignore" and "rerun". This brings popover in line with other Streamlit widgets that support callbacks (like st.checkbox, st.button, etc.).

Changes:

  • Extended st.popover to accept callable functions as the on_change parameter
  • Added args and kwargs parameters to pass arguments to callbacks
  • Updated validation logic to check for callable in addition to string literals

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
lib/streamlit/elements/layouts.py Core implementation: Added WidgetCallback, WidgetArgs, WidgetKwargs imports; extended on_change parameter type to include callables; added args and kwargs parameters; updated validation, state tracking, and widget registration logic; enhanced documentation
lib/tests/streamlit/elements/layouts_test.py Comprehensive unit tests covering callback behavior: state tracking, proto ID setting, metadata registration, callback firing on state changes, args/kwargs handling, session state accessibility, and error handling
lib/tests/streamlit/typing/popover_container_types.py Type tests to verify correct type annotations for callable callbacks and parameter passing
e2e_playwright/st_popover.py E2E test app with three callback scenarios: basic callback, callback with args/kwargs, and callback inside a fragment
e2e_playwright/st_popover_test.py E2E tests verifying callback behavior in real app scenarios: callback firing on open/close, args/kwargs passing, and fragment integration; updated popover count from 18 to 21

@sfc-gh-lwilby sfc-gh-lwilby added the ai-review If applied to PR or issue will run AI review workflow label Feb 23, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Feb 23, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Consolidated Code Review

Summary

This PR adds callable on_change callback support to st.popover, extending the existing "ignore" / "rerun" string literal API with the ability to pass a callback function. It also introduces optional args and kwargs parameters forwarded to the callback, consistent with the callback pattern used by other Streamlit widgets (st.checkbox, st.toggle, etc.). The callback fires on any popover state change (open or close).

Files changed:

  • lib/streamlit/elements/layouts.py — Core implementation: widened on_change type, added args/kwargs params, wired callback through register_widget.
  • lib/tests/streamlit/elements/layouts_test.py — 12 new unit tests covering callback registration, firing, args/kwargs, session state, and error handling.
  • lib/tests/streamlit/typing/popover_container_types.py — Typing tests for the new parameter signatures.
  • e2e_playwright/st_popover.py — E2E app script with callback scenarios.
  • e2e_playwright/st_popover_test.py — 3 new E2E tests.

Reviewer Agreement

Both reviewers (gpt-5.3-codex-high and opus-4.6-thinking) agreed on all major points:

  • APPROVED the PR
  • Implementation is clean and follows existing widget callback patterns
  • Test coverage (unit, E2E, typing) is strong and adequate for merge
  • No breaking changes — purely additive, backwards compatible
  • No security concerns or new attack surface
  • No accessibility implications (no frontend changes in this PR)
  • The is_stateful = on_change != "ignore" simplification is correct
  • Docstring updates are well-written and clearly explain the three modes

Code Quality

The implementation is well-structured and localized:

  • Validation (layouts.py:1318): if not callable(on_change) and on_change not in {"ignore", "rerun"} correctly gates both string literals and callables.
  • Stateful check (layouts.py:1324): is_stateful = on_change != "ignore" cleanly unifies both "rerun" and callable behavior.
  • Conditional callback forwarding (layouts.py:1333, 1362): The callable(on_change) guard ensures the handler is only passed when it's actually a function.
  • Docstring (layouts.py:1240–1269): Well-written Numpydoc-style documentation with clear explanation of the three modes and new parameters.

No blocking code quality issues were identified by either reviewer.

Test Coverage

Coverage is strong across all test layers:

  • Unit tests (12 new): Callback registration, proto ID setting, widget metadata, state change firing (open and close), initial render guard, args/kwargs forwarding, session state accessibility, keyless callback support, and invalid input error handling.
  • E2E tests (3 new): Open/close cycle with counter verification, args/kwargs forwarding, and fragment usage.
  • Typing tests: All three on_change modes, callback with args/kwargs, and callback with/without key.

Backwards Compatibility

No breaking changes. on_change retains its "ignore" default. args and kwargs are new optional keyword-only parameters. The type widening from Literal["ignore", "rerun"] to Literal["ignore", "rerun"] | WidgetCallback is non-breaking.

Security & Risk

No security concerns. Regression risk is low and well-mitigated by the comprehensive test additions.

Accessibility

No frontend changes — no accessibility implications.

Recommendations (Non-blocking)

Both reviewers offered minor, non-blocking suggestions. Consolidated:

  1. Move repeated imports to top-level in layouts_test.py (raised by opus-4.6-thinking): WidgetState, WidgetStates, and get_script_run_ctx are imported inside 6+ individual test functions. Per lib/tests/AGENTS.md, imports should be at the top-level unless there's a specific reason. Moving them would reduce duplication. Verified: these imports don't have circular-import constraints.

  2. Consider testing callable on_change inside st.form (raised by gpt-5.3-codex-high): This would lock in the intended callback policy behavior around the check_widget_policies gate. A worthwhile addition but not blocking.

  3. Consider validating args/kwargs when on_change is not callable (raised by opus-4.6-thinking): Currently st.popover("label", on_change="rerun", args=("x",)) silently ignores args. This is consistent with how register_widget works elsewhere but could confuse users. Non-blocking since it matches existing patterns.

  4. Consider consolidating small E2E tests (raised by opus-4.6-thinking): test_popover_callback_with_args_kwargs could be merged into the main open/close test to reduce browser loads. Minor optimization.

Verdict

APPROVED: Both reviewers approved. The change is well-scoped, backwards compatible, follows established widget callback patterns, and is thoroughly tested across unit, E2E, and typing test layers. All recommendations are non-blocking.


Consolidated review by opus-4.6-thinking.


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

Summary

This PR adds callable on_change callback support to st.popover, including optional args/kwargs, while preserving the existing "ignore" and "rerun" behavior. The backend wiring in lib/streamlit/elements/layouts.py is consistent with existing widget callback patterns, and coverage was expanded with Python unit tests, typing tests, and e2e scenarios.

Code Quality

The implementation is clean and localized:

  • st.popover now accepts on_change as "ignore" | "rerun" | callable and registers callback metadata via register_widget when callable.
  • Stateful behavior is correctly derived from on_change != "ignore" and callback policy checks are enabled only for callable callbacks.
  • Docstring updates are aligned with the new API contract and expected session state behavior.

No blocking code quality issues were identified.

Test Coverage

Coverage is strong for the changed behavior:

  • Unit tests in lib/tests/streamlit/elements/layouts_test.py cover callback registration, open/close triggering, initial render behavior, args/kwargs plumbing, key/session-state interaction, and invalid on_change input.
  • Typing tests in lib/tests/streamlit/typing/popover_container_types.py validate the widened API typing for callable callbacks and callback args/kwargs.
  • E2E tests in e2e_playwright/st_popover_test.py verify callback firing on open/close, args/kwargs behavior, and fragment usage.

Overall, tests are adequate for merge.

Backwards Compatibility

No breaking change detected:

  • Existing on_change="ignore" and on_change="rerun" paths remain intact.
  • New parameters (args, kwargs) are additive and optional.
  • Existing behavior for keyless popovers and state tracking semantics appears preserved.

Security & Risk

No new security concerns identified.
Regression risk is low and mainly limited to popover state/callback wiring; this is mitigated by the added unit and e2e coverage.

Accessibility

No frontend component logic was changed. Accessibility impact is minimal and neutral for this PR.

Recommendations

  1. Consider adding one explicit test for callable on_change inside st.form to lock in the intended callback policy behavior in lib/streamlit/elements/layouts.py (around the check_widget_policies callback-rule gate).

Verdict

APPROVED: The change is well-scoped, backwards compatible, and sufficiently tested for the new popover callback functionality.


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 adds on_change callback support to st.popover, allowing users to pass a callable (in addition to the existing "ignore" / "rerun" string literals). It also introduces optional args and kwargs parameters that are forwarded to the callback, consistent with the callback pattern used by other Streamlit widgets (st.checkbox, st.toggle, etc.). The callback fires on any popover state change (open or close), and users can inspect st.session_state[key] inside the callback to distinguish between open (True) and close (False).

Files changed:

  • lib/streamlit/elements/layouts.py — Core implementation: widened on_change type, added args/kwargs params, wired callback through register_widget.
  • lib/tests/streamlit/elements/layouts_test.py — 12 new unit tests covering callback registration, firing, args/kwargs, session state, and error handling.
  • lib/tests/streamlit/typing/popover_container_types.py — Typing tests for the new parameter signatures.
  • e2e_playwright/st_popover.py — E2E app script with callback scenarios.
  • e2e_playwright/st_popover_test.py — 3 new E2E tests.

Code Quality

The implementation is clean and follows existing patterns well:

  • Validation logic (layouts.py:1318): if not callable(on_change) and on_change not in {"ignore", "rerun"} correctly gates both string literals and callables before falling through to error.
  • Stateful check (layouts.py:1324): is_stateful = on_change != "ignore" is a clean simplification — both "rerun" and callable values enable state tracking.
  • Conditional callback forwarding (layouts.py:1333, 1336, 1362): The repeated callable(on_change) guard ensures the handler is only passed when it's actually a function. This is correct.
  • Docstring (layouts.py:1240–1269): Well-written Numpydoc-style documentation that clearly explains the three modes and the new args/kwargs parameters.

Minor style items:

  • Imports inside test functions (layouts_test.py): WidgetState, WidgetStates, and get_script_run_ctx are imported inside individual test functions (lines 913, 926, 948, 988, 1009). Per lib/tests/AGENTS.md, "Imports should be at the top-level of the test file. Only use imports inside test functions when there is a specific reason." These proto/state imports don't appear to have circular-import constraints and are used by 6+ tests — moving them to the top level would be cleaner.
  • StreamlitValueError message (layouts.py:1320): The value "a callback function" is unquoted while the others ('rerun', 'ignore') are quoted. The resulting message reads fine ("Supported values: 'rerun', 'ignore', a callback function."), so this is cosmetic.

Test Coverage

Test coverage is strong:

Unit tests (12 new tests):

  • Callback enables state tracking, sets proto ID, registers metadata
  • Callback fires on state change (open), fires on both open and close
  • Callback does not fire on initial render (important anti-regression)
  • Args and kwargs forwarded correctly
  • Callback with key accessible via session state
  • Callback without key still works
  • Invalid on_change value raises StreamlitAPIException

E2E tests (3 new tests):

  • test_popover_callback_fires_on_open_and_close — open/close cycle with counter verification and "must NOT happen" check (content not visible when closed)
  • test_popover_callback_with_args_kwargs — verifies args/kwargs reach the callback
  • test_popover_callback_in_fragment — callback inside @st.fragment

Typing tests:

  • Covers all three on_change modes, callback with args/kwargs, and callback with/without key.

One observation per E2E AGENTS.md guidance ("Prefer a single aggregated scenario test over many micro-tests when they share the same setup and page state"): test_popover_callback_with_args_kwargs is small and could be merged into test_popover_callback_fires_on_open_and_close to reduce browser loads, though this is a minor optimization concern.

Missing coverage (non-blocking): There is no test verifying that passing args or kwargs when on_change="rerun" is either rejected or documented as a no-op. Currently these values would be silently stored in widget metadata without being used. This is consistent with how register_widget works elsewhere, but could be confusing for users.

Backwards Compatibility

No breaking changes. This is purely additive:

  • on_change retains its default "ignore", so all existing code continues to work.
  • args and kwargs are new optional keyword-only parameters with None defaults.
  • The type of on_change is widened from Literal["ignore", "rerun"] to Literal["ignore", "rerun"] | WidgetCallback, which is a non-breaking type expansion.
  • The popover count in the snapshot test is correctly updated from 18 to 21 to reflect the 3 new popover elements.

Security & Risk

No security concerns. Callbacks are user-provided functions that execute in the same Python process, consistent with how all other widget callbacks work. No new attack surface is introduced.

The risk of regression is low:

  • The is_stateful logic change from on_change == "rerun" to on_change != "ignore" is correct and well-tested.
  • The previously-TODO'd check_widget_policies and enable_check_callback_rules are now properly set, which is an improvement over the previous placeholder state.

Accessibility

No frontend changes are included in this PR. The feature is entirely backend (Python callback plumbing), so there are no accessibility implications.

Recommendations

  1. Move repeated imports to top-level in layouts_test.py: WidgetState, WidgetStates (from streamlit.proto.WidgetStates_pb2), and get_script_run_ctx (from streamlit.runtime.state.session_state) are imported inside 6+ test functions. Moving them to the top of the file would follow the AGENTS.md guidance and reduce duplication.

  2. Consider validating args/kwargs when on_change is not callable: Currently, st.popover("label", on_change="rerun", args=("x",)) silently ignores the args. A warning or error would help prevent user confusion. This is not a blocker since the behavior is consistent with how register_widget handles these internally.

  3. Consider consolidating E2E tests: test_popover_callback_with_args_kwargs is small enough to merge into test_popover_callback_fires_on_open_and_close, saving a browser load per E2E AGENTS.md guidance.

Verdict

APPROVED: Clean, well-tested feature addition that extends st.popover with callback support following established widget patterns. Minor import and test consolidation suggestions are non-blocking.


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

@sfc-gh-lwilby sfc-gh-lwilby force-pushed the 02-23-_dynamiccontainers_callback_support_for_popovers branch 3 times, most recently from 58da0c8 to 3f9da78 Compare February 23, 2026 20:24
Comment on lines +1324 to +1327
if not callable(on_change) and (args is not None or kwargs is not None):
raise StreamlitAPIException(
"`args` and `kwargs` can only be used when `on_change` is a callable."
)
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.

Same as in the other PR, I think it's better not to throw this error (see other PR)

Copy link
Copy Markdown
Collaborator

@lukasmasuch lukasmasuch left a comment

Choose a reason for hiding this comment

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

LGTM 👍

@sfc-gh-lwilby sfc-gh-lwilby force-pushed the 02-23-_dynamiccontainers_callback_support_for_popovers branch from 3f9da78 to 05417bb Compare February 25, 2026 07:29
@sfc-gh-lwilby sfc-gh-lwilby force-pushed the 02-23-_dynamiccontainers_callback_support_for_popovers branch from 05417bb to 7b662ec Compare February 25, 2026 07:31
@sfc-gh-lwilby sfc-gh-lwilby merged commit a8a99e8 into develop Feb 25, 2026
45 checks passed
@sfc-gh-lwilby sfc-gh-lwilby deleted the 02-23-_dynamiccontainers_callback_support_for_popovers branch February 25, 2026 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants