-
Notifications
You must be signed in to change notification settings - Fork 1.7k
ENG-9425: fixes for extensible BaseState #6418
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4646,3 +4646,56 @@ async def test_rebind_mutable_proxy( | |||||||||||
| assert state.data["a"] == [2, 3] | ||||||||||||
| # Object identity persists across serialization, so data["b"] is also mutated. | ||||||||||||
| assert state.data["b"] == [2, 3] | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| def test_override_base_method_skips_event_handler_wrapping(): | ||||||||||||
| """A method marked with __override_base_method__ should not be wrapped as an EventHandler.""" | ||||||||||||
| from reflex.state import _override_base_method | ||||||||||||
|
|
||||||||||||
| class OverrideState(rx.State): | ||||||||||||
| @_override_base_method | ||||||||||||
| def custom_override(self) -> int: | ||||||||||||
| return 42 | ||||||||||||
|
|
||||||||||||
| # The marked method must remain a plain function, not an EventHandler. | ||||||||||||
| assert not isinstance(OverrideState.__dict__["custom_override"], EventHandler) | ||||||||||||
| assert "custom_override" not in OverrideState.event_handlers | ||||||||||||
| assert OverrideState().custom_override() == 42 | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| def test_descriptor_attribute_not_in_backend_vars(): | ||||||||||||
| """A custom descriptor on a state should appear in vars/base_vars but not backend_vars.""" | ||||||||||||
|
|
||||||||||||
| class _IntDescriptor: | ||||||||||||
| def __init__(self): | ||||||||||||
| self._values: dict[int, int] = {} | ||||||||||||
|
|
||||||||||||
| def __set_name__(self, owner, name): | ||||||||||||
| self._name = name | ||||||||||||
|
|
||||||||||||
| def __get__(self, instance, owner): | ||||||||||||
| if instance is None: | ||||||||||||
| return self | ||||||||||||
| return self._values.get(id(instance), 0) | ||||||||||||
|
|
||||||||||||
| def __set__(self, instance, value): | ||||||||||||
| self._values[id(instance)] = value | ||||||||||||
|
|
||||||||||||
| class DescriptorState(rx.State): | ||||||||||||
| _desc_value: int = _IntDescriptor() # pyright: ignore[reportAssignmentType] | ||||||||||||
|
|
||||||||||||
| @rx.var | ||||||||||||
| def doubled(self) -> int: | ||||||||||||
| return self._desc_value * 2 | ||||||||||||
|
|
||||||||||||
| # The descriptor should not be tracked as a backend var. | ||||||||||||
| assert "_desc_value" not in DescriptorState.backend_vars | ||||||||||||
| # But it should be visible in base_vars/vars for dependency tracking. | ||||||||||||
| assert "_desc_value" in DescriptorState.base_vars | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The comment on line 4693 says "visible in base_vars/vars", but only the
Suggested change
|
||||||||||||
| assert "_desc_value" in DescriptorState.vars | ||||||||||||
| # Descriptor remains the class-level attribute (not overwritten by a Var). | ||||||||||||
| assert isinstance(DescriptorState.__dict__["_desc_value"], _IntDescriptor) | ||||||||||||
|
|
||||||||||||
| # A computed var depending on the descriptor must register the dependency. | ||||||||||||
| deps = DescriptorState._var_dependencies.get("_desc_value", set()) | ||||||||||||
| assert (DescriptorState.get_full_name(), "doubled") in deps | ||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a subclass overrides a parent's descriptor with a different implementation,
dname in cls.inherited_varsisTrue(because the parent's placeholder Var was merged intoparent_state.vars, which becomesinherited_vars). The child's descriptor is therefore skipped and the dependency-tracking Var still refers to the parent-state field name in itsVarData. Any computed var in the child that depends on the overridden descriptor would be attached to the parent's tracking dict rather than the child's, potentially missing invalidation.This may be an accepted limitation, but it would be worth a comment or a
# TODOto document the behaviour for future readers.