Support hybrid_property on ObjectVar for nested dataclasses#6619
Support hybrid_property on ObjectVar for nested dataclasses#6619masenf wants to merge 6 commits into
Conversation
`rx._x.hybrid_property` previously only resolved as a frontend var when accessed directly on a `State` class. Accessing it through an object var (e.g. `State.info.a_b` where `info` is a dataclass, pydantic model or SQLAlchemy model) raised `VarAttributeError`. `ObjectVar.__getattr__` now detects a `HybridProperty` defined on the underlying type and evaluates its frontend logic with the object var substituted as `self`, so it renders with the same Var-access semantics as accessing the hybrid property directly on the state. This works uniformly across bare classes, pydantic models, SQLAlchemy models and dataclasses, since they are all treated as object vars. `HybridProperty` moved to `reflex_base.vars.hybrid_property` (so the var system can reference it without an inverted dependency) and is still re-exported from `reflex.experimental.hybrid_property`. Fixes #6617 https://claude.ai/code/session_01DKFiYGnWRQG8wMNKFW7obm
Import `hybrid_property` directly from `reflex_base.vars.hybrid_property` in `reflex.experimental.__init__` instead of going through a one-line re-export module. `from reflex.experimental import hybrid_property` and `rx._x.hybrid_property` are unchanged. https://claude.ai/code/session_01DKFiYGnWRQG8wMNKFW7obm
Merging this PR will not alter performance
Comparing Footnotes
|
Greptile SummaryThis PR extends
Confidence Score: 5/5Safe to merge; the logic change is well-tested and the refactoring preserves all existing dispatch paths in ObjectVar.getattr. The branch-reordering in ObjectVar.getattr is behavior-preserving (TypedDicts are still excluded from the first arm by their Mapping inheritance and route to the same elif handler as before). The _get_var extraction is straightforward and backed by unit tests across all four object types plus an integration test. No data-loss or incorrect-var-generation paths were found. packages/reflex-base/src/reflex_base/vars/hybrid_property.py — the lazy from reflex.state import BaseState import inside get creates an upward runtime dependency from reflex_base into reflex; packages/reflex-base/src/reflex_base/utils/types.py — the descriptor parameter is shadowed by a local in the SQLAlchemy block. Important Files Changed
Reviews (3): Last reviewed commit: "Merge branch 'main' into claude/relaxed-..." | Re-trigger Greptile |
…non-states HybridProperty.__get__ produced a frontend var for any class-level access, which only makes sense on a state (whose class attributes are vars). On a plain class accessed directly — e.g. `Info.a_b` on a dataclass, not through an object var — it ran the getter with the class as `self`, raising AttributeError (no field default) or returning a value built from class defaults. It now returns the descriptor itself, like a normal property. Var access through an object var (`State.info.a_b`) is unaffected: it is resolved by ObjectVar.__getattr__ via _get_var, not __get__. https://claude.ai/code/session_01DKFiYGnWRQG8wMNKFW7obm
ObjectVar.__getattr__ ran inspect.getattr_static on every attribute access to detect a HybridProperty on the underlying type — a pure-Python MRO walk on a hot path, ~15x slower than getattr for ordinary field access. Now that HybridProperty.__get__ returns the descriptor itself for non-state class access, a plain getattr surfaces it directly, so the static lookup is no longer needed. https://claude.ai/code/session_01DKFiYGnWRQG8wMNKFW7obm
|
@greptile |
|
This is great, thanks @masenf ! |
Extract the class-attribute resolution into get_attribute_descriptor so ObjectVar attribute access resolves the descriptor once and reuses it for both HybridProperty detection and get_attribute_access_type, instead of looking it up twice. Also hoist the HybridProperty import to module level and flatten the branching in __getattr__.
Type of change
Description
This PR extends
hybrid_propertysupport to work seamlessly withObjectVarattribute access on nested dataclasses, Pydantic models, and SQLAlchemy models—not justStateclasses.Key changes:
Moved
HybridPropertytoreflex_base: Relocatedhybrid_property.pyfromreflex/experimental/toreflex_base/vars/to make it available at the var level where it's needed forObjectVarresolution.ObjectVardescriptor resolution: ModifiedObjectVar.__getattr__to detect and resolveHybridPropertydescriptors on the underlying type. When a hybrid property is accessed (e.g.,State.info.a_b), the property's frontend logic is evaluated with the object var substituted asself, enabling consistent var-level semantics.HybridProperty._get_varextraction: Refactored the var-resolution logic into a dedicated_get_varmethod that accepts either a class or anObjectVaras the owner, allowing the property to work uniformly across both direct class access and nested object var access.Comprehensive test coverage: Added unit tests verifying hybrid property resolution on bare classes, Pydantic models, SQLAlchemy models, and dataclasses. Included integration tests demonstrating re-rendering when nested dataclass fields are updated.
Example usage:
Closes #6617
Testing
tests/units/vars/test_object.pycovering hybrid property resolution on all object typestests/integration/test_hybrid_properties.pyverifying frontend rendering and re-rendering on state updateshttps://claude.ai/code/session_01DKFiYGnWRQG8wMNKFW7obm