Bind widgets to query params - st.date_input#14034
Conversation
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
✅ PR preview is ready!
|
There was a problem hiding this comment.
Pull request overview
Adds URL query-parameter binding support (bind="query-params") for st.date_input, enabling two-way sync between widget state and URL while modernizing the internal wire date format to ISO 8601.
Changes:
- Add
query_param_keyto theDateInputproto and wire up backend + frontend query-param binding forst.date_input. - Modernize the
date_inputprotobuf wire format fromYYYY/MM/DDto ISOYYYY-MM-DD, with backend fallback parsing for legacy values. - Add/adjust Python, TypeScript, and Playwright E2E tests to cover query-param seeding, clearing, invalid values, and bounds behavior.
Reviewed changes
Copilot reviewed 11 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| proto/streamlit/proto/DateInput.proto | Adds query_param_key to support URL binding from backend to frontend. |
| lib/streamlit/elements/widgets/time_widgets.py | Implements bind for st.date_input, adds ISO serialization, URL seeding behavior, and URL cleanup on bounds reset. |
| lib/streamlit/testing/v1/element_tree.py | Updates date parsing for min/max to match ISO wire format in tests. |
| frontend/lib/src/components/widgets/DateInput/DateInput.tsx | Switches date parsing/serialization to ISO and registers query param binding when queryParamKey is present. |
| frontend/lib/src/components/widgets/DateInput/DateInput.test.tsx | Updates tests for ISO wire format and adds query-param binding unit tests. |
| frontend/lib/src/WidgetStateManager.ts | Adds Date handling for default comparisons when deciding whether to clear URL params. |
| frontend/lib/src/WidgetStateManager.test.ts | Adds unit tests verifying Date[] defaults compare correctly against URL string-array values. |
| lib/tests/streamlit/elements/date_input_test.py | Updates expected wire-format strings and adds backend bind + serde parsing unit tests. |
| lib/tests/streamlit/typing/date_input_types.py | Extends typing checks to cover the new bind parameter. |
| e2e_playwright/st_date_input.py | Adds bound st.date_input widgets used by the new E2E scenarios. |
| e2e_playwright/st_date_input_test.py | Adds E2E coverage for URL seeding/clearing/invalid/out-of-range behavior for date_input. |
| e2e_playwright/snapshots/linux/st_date_input_test/*.png | Updates snapshots impacted by the additional widgets / rendering changes. |
Consolidated Code Review: PR #14034 — Bind widgets to query params -
|
| Area | gpt-5.3-codex-high | opus-4.6-thinking | Consensus |
|---|---|---|---|
| Code Quality | Clean, no blocking issues | Clean, follows patterns | Agree |
| Test Coverage | Strong | Thorough | Agree |
| Backwards Compat | Good | Well-handled | Agree |
| Security | No concerns | No concerns | Agree |
| Accessibility | No concerns | No concerns | Agree |
| Verdict | APPROVED | APPROVED | APPROVED |
No disagreements between reviewers. Both independently identified the same strengths and reached the same conclusion.
Verdict
APPROVED: Both reviewers unanimously approve. The feature is well-implemented across all layers, follows established patterns, has thorough test coverage, maintains backward compatibility through dual-format parsing, and correctly handles edge cases (invalid input, out-of-bounds values, clearable widgets). No blocking issues were identified.
Consolidated review by opus-4.6-thinking. Individual reviews below.
📋 Review by `gpt-5.3-codex-high`
Summary
This PR adds bind="query-params" support to st.date_input, including backend/frontend wiring, protobuf support, and tests. It also updates the internal date wire format from YYYY/MM/DD to ISO YYYY-MM-DD, with backend fallback parsing for legacy values and URL reset behavior for invalid or out-of-bounds query params.
Code Quality
The implementation is cohesive across layers (proto, Python widget serde/registration, frontend widget state binding, and e2e coverage), and follows established query-param binding patterns used by other widgets.
No blocking code quality issues found.
Test Coverage
Coverage is strong and appropriately distributed:
- Python unit tests cover bind behavior, serde parsing/serialization, and bounds reset behavior in
lib/tests/streamlit/elements/date_input_test.py. - Frontend unit tests cover ISO wire values, query-param registration/unregistration, seeded values, and default-comparison behavior in
frontend/lib/src/components/widgets/DateInput/DateInput.test.tsxandfrontend/lib/src/WidgetStateManager.test.ts. - E2E tests cover URL seeding, invalid values, clearable behavior, range seeding, and out-of-range resets in
e2e_playwright/st_date_input_test.py. - Typing coverage was updated in
lib/tests/streamlit/typing/date_input_types.py.
Backwards Compatibility
Backwards compatibility looks good:
- Backend deserialization accepts both ISO and legacy slash formats via
DateInputSerde._parse_dateinlib/streamlit/elements/widgets/time_widgets.py. - Frontend and backend now consistently serialize date-input wire values as ISO, reducing ambiguity and improving URL interoperability.
- Query params are cleared when values are default or invalid/out-of-bounds, matching current widget binding behavior patterns.
Security & Risk
No direct security concerns identified.
Regression risk is low-to-moderate due to protocol/value-format touchpoints, but mitigated by cross-layer tests and explicit fallback parsing.
Accessibility
No new accessibility concerns identified.
The frontend changes do not introduce new interaction primitives; they reuse existing date input behavior.
Recommendations
- Add one explicit regression test for legacy URL seeding with
YYYY/MM/DD(slash format) to directly validate the compatibility path claimed byDateInputSerde._parse_date.
Verdict
APPROVED: The feature is implemented cleanly across backend/frontend/proto with solid test coverage and no blocking defects found.
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 bind="query-params" support to st.date_input, enabling two-way sync between widget values and URL query parameters. Key changes include:
- Added a
bindparameter to thest.date_inputpublic API (all overloads). - Added
optional string query_param_key = 14to theDateInputprotobuf message. - Modernized the internal wire format from
YYYY/MM/DDto ISO 8601YYYY-MM-DD, with backward-compatible fallback parsing on both backend (_parse_datetries both formats) and frontend (moment.js is lenient with separators by default). - Added bounds validation in
DateInputSerde.deserializeso out-of-range URL values revert to the widget default. - Frontend
DateInput.tsxconstructs thequeryParamBindingobject usingstring_array_value, withurlFormat: "repeated"for range mode andclearablederived from empty defaults. WidgetStateManager.toStringPrimitivenow supportsDateobjects (formatted asYYYY-MM-DD) to enable correct default-comparison logic for URL parameter clearing.
Code Quality
The implementation is clean, well-structured, and follows existing patterns established by other bound widgets (slider, selectbox, text_input, etc.):
- The
queryParamBindingconstruction inDateInput.tsx(lines 102–109) mirrors the pattern used bySlider.tsxand other widgets. - The
DateInputSerde._parse_datestatic method (lines 583–591 oftime_widgets.py) provides clean backward-compatible parsing. - The out-of-bounds URL clearing via
qp.remove_param(lines 1744–1746 oftime_widgets.py) is a thoughtful touch that prevents stale URL params from persisting after reset. - The
toStringPrimitiveDate formatting (lines 1414–1418 ofWidgetStateManager.ts) is straightforward and correct for the expected date range.
Minor note: In DateInputSerde.serialize (line 625 of time_widgets.py), the list comprehension variable v shadows the function parameter v. This is pre-existing and not introduced by this PR, but could be improved for clarity in a follow-up.
Test Coverage
Testing is thorough across all layers:
- Python unit tests (
date_input_test.py): 7 bind-specific tests covering proto field setting, missing key error, invalid bind value, explicit value,Nonevalue, and range mode. 11 serde tests covering ISO parsing, legacy fallback, invalid input,None, empty range, out-of-bounds (single and range), boundary values, and serialization. - Frontend unit tests (
DateInput.test.tsx): 7 query-param binding tests covering registration, unregistration, no-binding case, clearable flag, rangeurlFormat, and URL-seeded values for both single and range modes. - WidgetStateManager tests: 3 tests specifically for
Dateobject default comparison ensuring URL clearing works correctly when string array values matchDate[]defaults. - E2E tests (
st_date_input_test.py): 6 E2E tests covering query param seeding, clearable empty state, invalid value revert, range seeding, out-of-bounds reset, and default-clearing behavior. - Type tests (
date_input_types.py):assert_typechecks forbindparameter with all return type variants.
The existing tests were properly updated to reflect the wire format change (YYYY/MM/DD → YYYY-MM-DD).
Backwards Compatibility
Wire format change: The internal protobuf format for date strings changed from YYYY/MM/DD to YYYY-MM-DD. This is the most significant compatibility consideration:
- Backend → Frontend:
DateInputSerde._parse_dateaccepts both%Y-%m-%dand%Y/%m/%dformats. Frontendmoment(val, "YYYY-MM-DD")is lenient with separators by default. - Frontend → Backend: The frontend sends ISO format; the backend parser accepts both.
- Rolling deployments: Both sides can handle either format, so mixed-version scenarios are safe.
Behavioral change in deserialize: The addition of bounds validation in DateInputSerde.deserialize means that out-of-range values (from any source, not just query params) now revert to the default. This is a tightening of behavior that could theoretically affect AppTest users who set dates outside computed bounds. The test change from date(2012, 1, 3) to date(2025, 6, 15) demonstrates this. This is a reasonable change since setting out-of-range dates was already invalid, and the separate _validate_date_value function already handled this at the widget level.
API compatibility: The bind parameter defaults to None, so existing code is unaffected. The parameter is keyword-only in all overloads.
Security & Risk
No security concerns identified:
- URL query parameter values are validated and sanitized (invalid formats revert to defaults, out-of-bounds values are rejected).
- No user input is used in string formatting or code execution paths.
- The
bindvalue is validated against a known set ("query-params"orNone), withStreamlitInvalidBindValueErrorraised for invalid values.
Low regression risk due to backward-compatible parsing and comprehensive test coverage.
Accessibility
No accessibility changes are introduced by this PR. The DateInput component continues to use the BaseWeb Datepicker with the same ARIA labels and keyboard interactions. The bind parameter is a backend/URL sync feature that doesn't alter the widget's DOM structure or interaction model.
Recommendations
No blocking issues found. Minor suggestions for potential follow-ups:
-
Variable shadowing in
serialize: InDateInputSerde.serialize(line 625 oftime_widgets.py), the list comprehension variablevshadows the function parameterv. Consider renaming todfor clarity in a future cleanup pass:return [date.strftime(d, "%Y-%m-%d") for d in to_serialize]
-
Year padding in
toStringPrimitive: ThegetFullYear()value is not zero-padded to 4 digits. While dates before year 1000 are extremely unlikely fordate_input, adding.toString().padStart(4, "0")would make the function strictly ISO 8601 compliant. This is a very minor edge case.
Verdict
APPROVED: Well-implemented feature that follows established patterns, has thorough test coverage across all layers, maintains backward compatibility through dual-format parsing, and handles edge cases (invalid input, out-of-bounds values, clearable widgets) correctly.
This is an automated AI review by opus-4.6-thinking.
| if (value instanceof Date) { | ||
| const y = value.getFullYear() | ||
| const m = String(value.getMonth() + 1).padStart(2, "0") | ||
| const d = String(value.getDate()).padStart(2, "0") | ||
| return `${y}-${m}-${d}` | ||
| } |
There was a problem hiding this comment.
nitpick: maybe a slightly nicer way might be to use toISOString on date and take the first 10 chars.
There was a problem hiding this comment.
Understandable - toISOString().slice(0, 10) would definitely be cleaner but opted for this pattern because toISOString() converts to UTC, while moment creates these Date objects in local time.
…14103) 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 #14034 for `st.date_input`.
Describe your changes
Adds bind="query-params" to
st.date_inputfor two-way sync between widget values and URL query parameters.Key changes:
date_inputis changed fromYYYY/MM/DDtoYYYY-MM-DD. This is an internal-only change — the user-facing display format (controlled by theformatparameter) is unaffected. Both frontend and backend parsers already accepted ISO dates, so backward compatibility is maintained via fallback parsing.string_array_valuefor the proto value type. Single date:?my_date=2025-01-15. Date range:?my_date=2025-01-15&my_date=2025-01-31.value=None(empty URL param?my_date=clears toNone), non-clearable otherwise.?my_date=not-a-date) silently revert to the widget's default value, matching the pattern used byst.radioandst.selectbox.min_value/max_value) are reset to the widget default, and the stale URL param is explicitly cleared.Testing Plan