Skip to content

Bind widgets to query params - st.datetime_input & st.time_input#14103

Merged
mayagbarnes merged 7 commits intodevelopfrom
query-time
Feb 25, 2026
Merged

Bind widgets to query params - st.datetime_input & st.time_input#14103
mayagbarnes merged 7 commits intodevelopfrom
query-time

Conversation

@mayagbarnes
Copy link
Copy Markdown
Collaborator

Describe your changes

Adds bind="query-params" to st.datetime_input and st.time_input for two-way sync between widget values and URL query parameters.

Key changes:

  • st.datetime_input wire format modernized to ISO 8601: The internal protobuf wire format is changed from YYYY/MM/DD, HH:mm to YYYY-MM-DDThh:mm (e.g., 2025-01-15T16:45). This is an internal-only change — the user-facing display format (controlled by the format parameter) is unaffected. Both frontend and backend parsers accept both formats, so backward compatibility is maintained via fallback parsing.
  • st.datetime_input binding: Uses string_array_value for the proto value type. URL format: ?my_dt=2025-01-15T16:45. Clearable when value=None (empty URL param ?my_dt= clears to None), non-clearable otherwise.
  • st.time_input binding: Uses string_value for the proto value type. URL format: ?my_time=14:30. Clearable when value=None, non-clearable otherwise.
  • Invalid URL values (e.g. ?my_dt=not-a-datetime) silently revert to the widget's default value, matching the pattern used by st.date_input, st.radio, and st.selectbox.
  • Out-of-bounds URL values (e.g. a datetime outside min_value/max_value) are reset to the widget default, and the stale URL param is explicitly cleared.
  • Follows the same patterns established in Bind widgets to query params - st.date_input #14034 for st.date_input.

Testing Plan

  • JS & Python Unit Tests: ✅ Added
  • E2E Tests: ✅ Added
  • Manual Testing: ✅

@mayagbarnes mayagbarnes added security-assessment-completed change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users labels Feb 24, 2026
@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Feb 24, 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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 24, 2026

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-14103/streamlit-1.54.0-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-14103.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

This PR adds bind="query-params" support to st.datetime_input and st.time_input, enabling two-way synchronization between widget values and URL query parameters. It follows the same patterns established in #14034 for st.date_input.

Changes:

  • Wire format modernization for st.datetime_input: Internal protobuf format changed from YYYY/MM/DD, HH:mm to ISO 8601 (YYYY-MM-DDThh:mm) with backward-compatible fallback parsing in both frontend and backend
  • Query param binding implementation: Both widgets support bind="query-params" with proper clearability, error handling for invalid/out-of-bounds values, and seamless URL synchronization
  • Comprehensive test coverage: Python unit tests, frontend tests, and E2E tests verify binding functionality, invalid value handling, clearability, and out-of-bounds behavior

Reviewed changes

Copilot reviewed 17 out of 20 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
proto/streamlit/proto/TimeInput.proto Added optional query_param_key field for query param binding
proto/streamlit/proto/DateTimeInput.proto Added optional query_param_key field for query param binding
lib/streamlit/elements/widgets/time_widgets.py Wire format updated to ISO 8601, bind parameter added to both widgets, serdes updated with fallback parsing, clearability logic
lib/tests/streamlit/typing/time_input_types.py Type tests for bind parameter with time_input
lib/tests/streamlit/typing/datetime_input_types.py Type tests for bind parameter with datetime_input
lib/tests/streamlit/elements/time_input_test.py Unit tests for query param binding, serde format parsing, invalid value handling
lib/tests/streamlit/elements/datetime_input_test.py Unit tests for query param binding, ISO format parsing with legacy fallback, out-of-bounds handling
lib/streamlit/testing/v1/element_tree.py AppTest support for new ISO format with legacy fallback parsing
frontend/lib/src/components/widgets/TimeInput/TimeInput.tsx Query param binding setup with queryParamBinding config
frontend/lib/src/components/widgets/TimeInput/TimeInput.test.tsx Frontend tests for query param registration, clearability, unregistration
frontend/lib/src/components/widgets/DateTimeInput/dateTimeInputUtils.ts Wire format updated to ISO 8601, fallback parsing for legacy format
frontend/lib/src/components/widgets/DateTimeInput/DateTimeInput.tsx Query param binding setup with queryParamBinding config
frontend/lib/src/components/widgets/DateTimeInput/DateTimeInput.test.tsx Frontend tests updated for new wire format, query param binding tests added
e2e_playwright/st_time_input.py Three bound time input widgets added for E2E testing
e2e_playwright/st_time_input_test.py E2E tests for seeding, clearable, invalid values, step non-snapping
e2e_playwright/st_datetime_input.py Three bound datetime input widgets added for E2E testing
e2e_playwright/st_datetime_input_test.py E2E tests for seeding, clearable, invalid values, out-of-range handling
e2e_playwright/__snapshots__/linux/st_time_input_test/*.png Updated snapshots reflecting new widget count (13→16)

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

Summary

This PR adds bind="query-params" support to st.datetime_input and st.time_input, enabling two-way sync between widget values and URL query parameters. Key changes include:

  • Proto changes: Added optional string query_param_key to both DateTimeInput.proto (field 15) and TimeInput.proto (field 11).
  • Wire format modernization: st.datetime_input wire format changed from YYYY/MM/DD, HH:mm to ISO 8601 YYYY-MM-DDThh:mm, with backward-compatible fallback parsing on both frontend and backend.
  • Backend: DateTimeInputSerde and TimeInputSerde updated to handle invalid/out-of-bounds URL values by reverting to defaults. bind parameter wired through to register_widget.
  • Frontend: DateTimeInput.tsx and TimeInput.tsx construct queryParamBinding objects from the new queryParamKey proto field.
  • AppTest: element_tree.py updated to parse both ISO and legacy datetime formats.

The implementation follows the patterns established in PR #14034 for st.date_input.

Code Quality

The code is well-structured, clean, and follows existing Streamlit patterns closely. Specific observations:

  • Backend (time_widgets.py): The DateTimeInputSerde and TimeInputSerde classes are well-designed. The ISO format constants and fallback parsing logic are clear.
  • Frontend: Both DateTimeInput.tsx and TimeInput.tsx correctly construct queryParamBinding objects and follow React best practices (memoization, useCallback, derived state via useMemo).
  • dateTimeInputUtils.ts: Good separation of concerns with the dual-format parsing via DATE_TIME_PARSE_FORMATS.
  • Docstrings: The bind parameter documentation for both widgets is thorough and matches the pattern from st.date_input.

One inconsistency found — In _datetime_input (line 1331-1335), when value_needs_reset is true due to dynamic bounds changes with bind="query-params", the stale URL param is NOT cleared:

        if value_needs_reset and key is not None:
            # Update session_state so subsequent accesses in this run
            # return the corrected value. Use reset_state_value to avoid
            # the "cannot be modified after widget instantiated" error.
            get_session_state().reset_state_value(key, current_value)

Compare with _date_input (line 1790-1799) which does clear it:

        if value_needs_reset and key is not None:
            # Update session_state so subsequent accesses in this run
            # return the corrected value. Use reset_state_value to avoid
            # the "cannot be modified after widget instantiated" error.
            get_session_state().reset_state_value(key, current_value)

            # Clear stale URL param when an out-of-bounds URL value was reset.
            if bind == "query-params":
                with get_session_state().query_params() as qp:
                    qp.remove_param(str(key))

This means that if a user has bind="query-params" on a datetime_input AND dynamically changes min_value/max_value such that the current (URL-seeded) value becomes out of bounds, the widget value will be reset but the stale URL param will remain. For the initial URL seeding case (tested in E2E), this is handled correctly by _seed_widget_from_url detecting default equality. But the dynamic bounds + query-params combination would leave stale URL params.

Test Coverage

Test coverage is comprehensive across all layers:

  • Python unit tests (datetime_input_test.py, time_input_test.py): Tests cover bind with key, bind without key (error), invalid bind value, bind with explicit value, bind with None value, and serde deserialization for ISO/legacy/invalid/empty/out-of-bounds formats. The TestDateTimeInputSerdeISO and TestTimeInputSerdeStepSnapping classes thoroughly test the deserialization logic.
  • Frontend unit tests (DateTimeInput.test.tsx, TimeInput.test.tsx): Tests cover query param binding registration/unregistration, clearable behavior, and the absence of binding when queryParamKey is not set.
  • E2E tests (st_datetime_input_test.py, st_time_input_test.py): Tests cover URL seeding, clearable empty params, invalid URL values reverting to defaults, out-of-range values, and step-unaligned values for time input.
  • Typing tests: Both datetime_input_types.py and time_input_types.py verify the bind parameter types with assert_type.

The NUM_DATETIME_INPUTS and NUM_TIME_INPUTS counters are correctly updated to account for the new bound widgets in the E2E app scripts.

Backwards Compatibility

This change is fully backward-compatible:

  • Proto changes: Adding optional fields (15 and 11) is additive and safe. Existing clients ignore unknown fields.
  • Wire format: Both frontend (DATE_TIME_PARSE_FORMATS) and backend (DateTimeInputSerde.deserialize) accept both the new ISO format and the legacy YYYY/MM/DD, HH:mm format. The element_tree.py _parse_dt helper also tries both formats.
  • API surface: The bind parameter defaults to None, so existing code is unaffected. The parameter follows the same pattern already established by st.date_input, st.radio, st.selectbox, etc.
  • User-facing display: The format parameter (controlling display) is completely unaffected by the wire format change.

Security & Risk

No security concerns identified:

  • Invalid URL values are silently reverted to widget defaults (no error messages that could leak information).
  • Out-of-bounds URL values are rejected and reset, preventing injection of unexpected values.
  • The implementation follows the same safe patterns used by all other bound widgets.

Low risk of regression: The wire format change is internal and protected by dual-format parsing. The only regression risk is in the edge case noted in Code Quality (stale URL params with dynamic bounds), which is a minor inconsistency rather than a correctness bug.

Accessibility

No accessibility concerns. The widgets already have proper aria-label attributes set from the element label. The query param binding feature is a backend/URL concern that doesn't alter the widget's DOM structure or interactive behavior.

Recommendations

  1. Add URL param clearing for dynamic bounds in _datetime_input: Add the remove_param logic to _datetime_input when value_needs_reset occurs with bind="query-params", matching the pattern in _date_input. This ensures consistent behavior across all bound time widgets. The fix would be adding lines after get_session_state().reset_state_value(key, current_value) in _datetime_input:
if bind == "query-params":
    with get_session_state().query_params() as qp:
        qp.remove_param(str(key))

Verdict

APPROVED: Well-implemented feature that follows established patterns with comprehensive test coverage. The missing URL param clearing for the dynamic bounds edge case (recommendation #1) is a minor inconsistency that could be addressed as a follow-up.


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

@mayagbarnes mayagbarnes marked this pull request as ready for review February 24, 2026 10:28
@mayagbarnes mayagbarnes merged commit 04e8abe into develop Feb 25, 2026
43 checks passed
@mayagbarnes mayagbarnes deleted the query-time branch February 25, 2026 04:06
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