Skip to content

ENG-9425: fixes for extensible BaseState#6418

Merged
masenf merged 2 commits intomainfrom
masenf/extensible-state
Apr 29, 2026
Merged

ENG-9425: fixes for extensible BaseState#6418
masenf merged 2 commits intomainfrom
masenf/extensible-state

Conversation

@masenf
Copy link
Copy Markdown
Collaborator

@masenf masenf commented Apr 29, 2026

  • For overridden methods, do not create EventHandler wrappers
  • For descriptor fields in a State, add a placeholder in vars, but do not intercept their access/set patterns.

* For overridden methods, do not create EventHandler wrappers
* For descriptor fields in a State, add a placeholder in `base_vars`, but do
  not intercept their access/set patterns.
@masenf masenf requested a review from a team as a code owner April 29, 2026 04:02
@linear
Copy link
Copy Markdown

linear Bot commented Apr 29, 2026

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 29, 2026

Merging this PR will not alter performance

✅ 9 untouched benchmarks


Comparing masenf/extensible-state (ccb74a2) with main (c36ac89)

Open in CodSpeed

adhami3310
adhami3310 previously approved these changes Apr 29, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 29, 2026

Greptile Summary

This PR introduces two fixes for extensible BaseState: (1) methods decorated with @_override_base_method are no longer wrapped as EventHandlers, and (2) custom descriptors defined on a state class are registered in cls.vars as lightweight placeholder Vars (via a new descriptor_vars dict) so computed vars can declare dependencies on them, while is_backend_base_variable is updated to exclude such descriptors from backend-var storage.

  • The new test at line 4694 asserts \"_desc_value\" in DescriptorState.base_vars, but _desc_value starts with _ and is explicitly excluded from base_vars by the not name.startswith(\"_\") filter; descriptors land only in descriptor_varscls.vars. This assertion will fail and block CI.

Confidence Score: 3/5

Not safe to merge until the incorrect base_vars assertion in the new test is fixed — it will fail CI.

A P1 test bug (base_vars assertion for an underscore-prefixed descriptor) will cause the new test to fail immediately. The production logic in state.py and types.py is otherwise sound.

tests/units/test_state.py — line 4694 has an incorrect assertion that will fail CI.

Important Files Changed

Filename Overview
tests/units/test_state.py Two new tests added; the descriptor test contains an incorrect base_vars assertion (line 4694) that will fail because _desc_value starts with _ and is excluded from base_vars.
reflex/state.py Adds _is_user_descriptor helper and descriptor_vars collection; skips EventHandler wrapping for __override_base_method__-marked functions. Logic is sound with the caveat that child descriptors silently reuse the parent's Var.
packages/reflex-base/src/reflex_base/utils/types.py Adds a __get__ check in is_backend_base_variable so custom descriptors are excluded from backend-var storage; straightforward and correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[BaseState.__init_subclass__] --> B[Compute base_vars\nfrom pydantic fields\nnot starting with _]
    A --> C[Iterate mixins + cls\nfor descriptor_vars]
    C --> D{_is_user_descriptor?}
    D -- No --> E[skip]
    D -- Yes --> F[Create placeholder Var\nwith FIELD_MARKER]
    F --> G[descriptor_vars dict]
    G --> H[cls.vars merge\ndescriptor_vars + inherited_vars\n+ base_vars + computed_vars]
    B --> H
    A --> I[_item_is_event_handler check]
    I --> J{__override_base_method__?}
    J -- Yes --> K[skip — not an EventHandler]
    J -- No --> L[wrap as EventHandler]
    A --> M[_init_var_dependency_dicts\nregisters computed var deps\nagainst descriptor_vars entries in cls.vars]
Loading

Reviews (1): Last reviewed commit: "ENG-9425: fixes for extensible BaseState" | Re-trigger Greptile

Comment thread tests/units/test_state.py Outdated
Comment on lines +4693 to +4694
# But it should be visible in base_vars/vars for dependency tracking.
assert "_desc_value" in DescriptorState.base_vars
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Incorrect base_vars assertion — test will fail

_desc_value starts with _, so it is explicitly excluded from base_vars by the not name.startswith("_") guard in __init_subclass__. Descriptor vars are added only to descriptor_vars, which is then merged into cls.vars. The assertion on line 4694 will therefore always be False and the test will fail.

The comment on line 4693 says "visible in base_vars/vars", but only the vars half is correct; the base_vars assertion should be inverted (or simply removed).

Suggested change
# But it should be visible in base_vars/vars for dependency tracking.
assert "_desc_value" in DescriptorState.base_vars
# But it should be visible in vars for dependency tracking.
assert "_desc_value" not in DescriptorState.base_vars
assert "_desc_value" in DescriptorState.vars

Comment thread reflex/state.py Outdated
Comment on lines +598 to +606
for source_cls in (*cls._mixins(), cls):
for dname, dvalue in source_cls.__dict__.items():
if (
dname in cls.base_vars
or dname in descriptor_vars
or dname in cls.inherited_vars
or dname in cls.inherited_backend_vars
or dname not in hints
or not _is_user_descriptor(dvalue)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Child override of parent descriptor silently uses parent Var

When a subclass overrides a parent's descriptor with a different implementation, dname in cls.inherited_vars is True (because the parent's placeholder Var was merged into parent_state.vars, which becomes inherited_vars). The child's descriptor is therefore skipped and the dependency-tracking Var still refers to the parent-state field name in its VarData. 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 # TODO to document the behaviour for future readers.

Allow substates to override descriptors in parent states.

Fix test assertions
@masenf masenf merged commit aeef6a2 into main Apr 29, 2026
68 of 69 checks passed
@masenf masenf deleted the masenf/extensible-state branch April 29, 2026 22:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants