Skip to content

Support hybrid_property on ObjectVar for nested dataclasses#6619

Open
masenf wants to merge 4 commits into
mainfrom
claude/relaxed-cerf-Z110q
Open

Support hybrid_property on ObjectVar for nested dataclasses#6619
masenf wants to merge 4 commits into
mainfrom
claude/relaxed-cerf-Z110q

Conversation

@masenf
Copy link
Copy Markdown
Collaborator

@masenf masenf commented Jun 5, 2026

Type of change

  • New feature (non-breaking change which adds functionality)

Description

This PR extends hybrid_property support to work seamlessly with ObjectVar attribute access on nested dataclasses, Pydantic models, and SQLAlchemy models—not just State classes.

Key changes:

  1. Moved HybridProperty to reflex_base: Relocated hybrid_property.py from reflex/experimental/ to reflex_base/vars/ to make it available at the var level where it's needed for ObjectVar resolution.

  2. ObjectVar descriptor resolution: Modified ObjectVar.__getattr__ to detect and resolve HybridProperty descriptors 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 as self, enabling consistent var-level semantics.

  3. HybridProperty._get_var extraction: Refactored the var-resolution logic into a dedicated _get_var method that accepts either a class or an ObjectVar as the owner, allowing the property to work uniformly across both direct class access and nested object var access.

  4. 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:

@dataclass
class Info:
    a: str
    b: str
    
    @hybrid_property
    def a_b(self) -> str:
        return f"{self.a} - {self.b}"

class State(rx.State):
    info: Info = Info(a="a", b="b")

# Frontend renders as: "a - b"
rx.text(State.info.a_b)

Closes #6617

Testing

  • Added unit tests in tests/units/vars/test_object.py covering hybrid property resolution on all object types
  • Added integration tests in tests/integration/test_hybrid_properties.py verifying frontend rendering and re-rendering on state updates
  • Existing tests pass with new functionality

https://claude.ai/code/session_01DKFiYGnWRQG8wMNKFW7obm

claude added 2 commits June 5, 2026 20:25
`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
@masenf masenf requested a review from a team as a code owner June 5, 2026 21:08
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Jun 5, 2026

Merging this PR will not alter performance

✅ 26 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing claude/relaxed-cerf-Z110q (ae2b3b8) with main (31f785e)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 5, 2026

Greptile Summary

This PR extends hybrid_property support to nested dataclasses, Pydantic models, and SQLAlchemy models by wiring ObjectVar.__getattr__ to detect and resolve HybridProperty descriptors on the underlying type. The HybridProperty class is relocated from reflex/experimental/ to reflex_base/vars/ and refactored to expose a _get_var(owner) helper that accepts either a class or an ObjectVar as the receiver.

  • ObjectVar.__getattr__ now calls getattr(fixed_type, name, None) before the normal attribute-type lookup; if the result is a HybridProperty, _get_var(self) is called with the object var substituted as self, so the getter body runs as frontend var logic.
  • HybridProperty.__get__ is updated to return the descriptor itself (self) when accessed at the class level on a non-state class, which is what allows the plain getattr in ObjectVar to surface the descriptor safely; BaseState subclasses still resolve to a frontend Var as before (via a lazy import to avoid a circular dependency at module load time).
  • New unit tests cover all four object-var variants (bare, Pydantic, SQLAlchemy, dataclass) and a new integration test verifies re-rendering when a nested dataclass field is mutated through state.

Confidence Score: 5/5

Safe to merge. The change is additive: ObjectVar attribute lookups gain a new hybrid-property fast-path that is well-guarded and falls through cleanly to existing logic when not applicable.

The mechanics are sound: the get/_get_var refactoring is consistent, the ObjectVar hook is minimal and guarded, and all four object-var variants plus state-level access are covered by unit and integration tests. No existing behaviour is removed or silently changed for state classes.

No files require special attention.

Important Files Changed

Filename Overview
packages/reflex-base/src/reflex_base/vars/hybrid_property.py Moved from reflex/experimental and refactored: adds _get_var() helper, changes get to return the descriptor itself on non-state classes, with a lazy BaseState import guarding the state-level var path
packages/reflex-base/src/reflex_base/vars/object.py Added hybrid-property lookup block in getattr: guarded by isinstance(fixed_type, type), delegates to _get_var(self) when descriptor is a HybridProperty
reflex/experimental/init.py Import for hybrid_property redirected to reflex_base.vars.hybrid_property; public surface unchanged
tests/units/vars/test_object.py Added HybridQuantity mixin and four new test functions covering hybrid property resolution on all object-var variants
tests/integration/test_hybrid_properties.py Adds nested Info dataclass with a_b hybrid property and integration assertions verifying initial render and re-render after state update

Reviews (2): Last reviewed commit: "perf: detect hybrid properties via getat..." | Re-trigger Greptile

Comment thread packages/reflex-base/src/reflex_base/vars/hybrid_property.py Outdated
Comment thread reflex/experimental/__init__.py
claude added 2 commits June 5, 2026 23:00
…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
@masenf
Copy link
Copy Markdown
Collaborator Author

masenf commented Jun 5, 2026

@greptile

@benedikt-bartscher
Copy link
Copy Markdown
Contributor

This is great, thanks @masenf !

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.

@rx._x.hybrid_property should work on a model/dataclass

3 participants