Support TypedDict form data in on_submit#6301
Support TypedDict form data in on_submit#6301GautamBytes wants to merge 9 commits intoreflex-dev:mainfrom
Conversation
Signed-off-by: Gautam Manchandani <gautammanch@Gautams-MacBook-Air.local>
Greptile SummaryThis PR adds
Key changes:
Confidence Score: 5/5Safe to merge — all remaining findings are non-blocking style and defensive-coding suggestions with no impact on correctness or existing behaviour The implementation is well-structured and thoroughly tested. The four findings are all P2: a redundant dead-code import block, a narrow exception catch that could be broadened for defensive safety, a deliberate (though undocumented) global relaxation of Mapping→TypedDict compatibility, and a minor double traversal of form refs. None of these affect runtime correctness or existing users. The runtime payload shape is explicitly unchanged, backward compatibility is preserved, and the new validation only fires at construction time with a clear error message. packages/reflex-base/src/reflex_base/event/init.py — redundant TYPE_CHECKING block at the bottom and the global scope of the Mapping relaxation are worth a second look before merge
|
| Filename | Overview |
|---|---|
| packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py | Core change: adds TypedDict field validation at form construction time, new mapping submit event overload, and several helper functions for static field discovery — well-structured but has a subtle gap where get_type_hints only catches NameError |
| packages/reflex-base/src/reflex_base/event/init.py | Adds _is_mapping_style_event_arg_compatible_with_typed_dict to relax type checks globally for Mapping→TypedDict compatibility; moves BASE_STATE TypeVar to top but leaves a now-redundant if TYPE_CHECKING import block at the bottom |
| tests/units/components/forms/test_form.py | Comprehensive new test suite covering acceptance, optional fields, extra fields, bound-arg resolution, missing-field errors, and dynamic-name bypass — good coverage of the happy and error paths |
| pyi_hashes.json | Hash update for the changed forms.py stub — routine maintenance change |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["Form.create"] --> B{on_submit in props?}
B -- No --> C[Assign prevent_default]
B -- Yes --> D[Build form component]
C --> D
D --> E[_validate_on_submit_typed_dict_fields]
E --> F{on_submit is EventChain?}
F -- No --> Z[Skip validation]
F -- Yes --> G{Any non-EventSpec events?}
G -- Yes --> Z
G -- No --> H[_resolve_on_submit_typed_dict_contract]
H --> I{form_data arg found?}
I -- No --> Z
I -- Yes --> J[get_type_hints on handler func]
J --> K{annotation is TypedDict?}
K -- No --> Z
K -- Yes --> L[_get_static_form_field_keys]
L --> M[Collect id-backed refs]
M --> N[Scan children for static name props]
N --> O{Any dynamic Var identifiers?}
O -- Yes --> Z
O -- No --> P{Missing required keys?}
P -- No --> Z[Validation passes]
P -- Yes --> Q[Raise EventHandlerValueError]
Reviews (1): Last reviewed commit: "Support TypedDict form submit data" | Re-trigger Greptile
|
Can you please add these tests as well and fix the issue? def test_on_submit_accepts_typed_dict_with_inherited_optional_fields():
"""Inherited optional TypedDict keys should remain optional."""
class BaseSignupData(TypedDict, total=False):
nickname: str
class SignupData(BaseSignupData):
email: str
class SignupState(rx.State):
@rx.event
def on_submit(self, form_data: SignupData):
pass
form = HTMLForm.create(
Input.create(name="email"),
on_submit=SignupState.on_submit,
)
assert isinstance(form.event_triggers["on_submit"], EventChain)def test_on_submit_accepts_controls_associated_via_form_attribute():
"""Controls associated via the HTML form attribute should not fail validation."""
class SignupData(TypedDict):
email: str
class SignupState(rx.State):
@rx.event
def on_submit(self, form_data: SignupData):
pass
form = HTMLForm.create(
id="signup",
on_submit=SignupState.on_submit,
)
Input.create(name="email", form="signup")
assert isinstance(form.event_triggers["on_submit"], EventChain) |
Use __required_keys__ for inherited TypedDict optional fields, skip validation for forms with id (HTML form attribute), replace stringly-typed control detection with _is_form_control marker, use concrete Mapping type in event spec, narrow exception handling, and add integration tests.
On 3.10, typing.TypedDict ignores typing_extensions.NotRequired when populating __required_keys__. Fall back to annotation inspection to subtract NotRequired fields on older Python versions.
…tamBytes/reflex into feat/support-typeddict-forms
Unit tests now pair happy paths with failing counterparts to prove validation is active. Integration test carries input/expected data per variant instead of fragile runtime app_source detection.
| return _get_handler_name(event_spec.handler), annotation, required_fields | ||
|
|
||
|
|
||
| def _get_required_typed_dict_fields(typed_dict_type: type[Any]) -> frozenset[str]: |
There was a problem hiding this comment.
I think we should check this here
required_keys = getattr(typed_dict_type, "__required_keys__", None)
if required_keys is not None:
return frozenset(required_keys)| return isinstance(value, Var) and value._js_expr == FORM_DATA._js_expr | ||
|
|
||
|
|
||
| def _get_handler_name(handler: EventHandler) -> str: |
There was a problem hiding this comment.
I think this can be inlined. As I understand it is called once.
All Submissions:
Type of change
New Feature Submission:
Changes To Core Features:
Description
This PR adds
TypedDictsupport foron_submitform data handlers while keeping existingdict[str, Any]anddict[str, str]handlers working as before.What changed:
on_submithandlers annotated with a concreteTypedDictTypedDictkeys against statically knowable form fields at form construction timenamefields and existing id-backed form refs in the validation setThis improves:
closes #6264