Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/6812.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
State classes no longer resolve descriptors while being constructed, so a hybrid property's frontend var is no longer built against a half-built class.
1 change: 1 addition & 0 deletions packages/reflex-base/news/6812.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`hybrid_property` is no longer a `property` subclass, so class-level access now resolves to the frontend var's type instead of the descriptor: without a var function it follows the var equivalent of the getter's return type, and a var function may declare a type of its own. The var function may be a `classmethod`, may return `None` to declare that the property has no frontend value on a class, and — like `getter`, `setter` and `deleter` — may be defined under a name of its own instead of shadowing the property's declaration.
5 changes: 4 additions & 1 deletion packages/reflex-base/src/reflex_base/utils/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,10 @@ def get_property_hint(attr: Any | None) -> GenericType | None:
Returns:
The type hint of the property, if it is a property, else None.
"""
if not isinstance(attr, PROPERTY_CLASSES):
# imported lazily: reflex_base.vars imports this module
from reflex_base.vars.hybrid_property import HybridProperty

if not isinstance(attr, (*PROPERTY_CLASSES, HybridProperty)):
return None
hints = get_type_hints(attr.fget)
return hints.get("return", None)
Expand Down
19 changes: 18 additions & 1 deletion packages/reflex-base/src/reflex_base/vars/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3725,6 +3725,20 @@ def field(
)


@once
def _hybrid_property_cls() -> type:
"""Get the HybridProperty class.

Imported lazily because `hybrid_property` imports this module.

Returns:
The HybridProperty class.
"""
from .hybrid_property import HybridProperty

return HybridProperty


@dataclass_transform(kw_only_default=True, field_specifiers=(field,))
class BaseStateMeta(ABCMeta):
"""Meta class for BaseState."""
Expand Down Expand Up @@ -3800,7 +3814,10 @@ def __new__(
elif (
not key.startswith("__")
and not callable(value)
and not isinstance(value, (staticmethod, classmethod, property, Var))
and not isinstance(
value,
(staticmethod, classmethod, property, _hybrid_property_cls(), Var),
)
):
if types.is_immutable(value):
new_value = Field(
Expand Down
5 changes: 4 additions & 1 deletion packages/reflex-base/src/reflex_base/vars/dep_tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ def load_attr_or_method(self, instruction: dis.Instruction) -> None:
does not reference a BaseState.
"""
from .base import ComputedVar
from .hybrid_property import HybridProperty

if instruction.argval in self.INVALID_NAMES:
msg = f"Cached var {self!s} cannot access arbitrary state via `{instruction.argval}`."
Expand Down Expand Up @@ -193,7 +194,9 @@ def load_attr_or_method(self, instruction: dis.Instruction) -> None:
except AttributeError:
static_obj = None

if isinstance(static_obj, property) and not isinstance(static_obj, ComputedVar):
if isinstance(static_obj, (property, HybridProperty)) and not isinstance(
static_obj, ComputedVar
):
# recurse into property fget functions
ref_obj = static_obj.fget
else:
Expand Down
Loading
Loading