From b23859a45da9342bf975c56ef1781e80206e08f1 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Wed, 27 Mar 2024 16:22:46 -0700 Subject: [PATCH] [REF-2302] When a Var points to a model, prefer access to model fields. (#2893) * When a Var points to a model, prefer access to model fields. When a Var points to a model, and fields of the model share the same name as Var operations, access to the model fields is now preferred to avoid having Var operation names shadow model fields. Since most Var operations do not actually work against Models, this does not really block any functionality. * Special case for ComputedVar needing to internally access fget Since fget is a "slot" on property, normal __getattribute__ access cannot find it. * Workaround https://github.com/python/cpython/issues/88459 In python 3.9 and 3.10, the `isinstance(list[...], type)` returns True, but it's not a valid class for use in issubclass --- reflex/utils/types.py | 2 +- reflex/vars.py | 34 +++++++++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/reflex/utils/types.py b/reflex/utils/types.py index 4c3958bb07..f0da26a15c 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -202,7 +202,7 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None attr.remote_attr.key, # type: ignore[attr-defined] ) ] - elif isinstance(cls, type) and issubclass(cls, Model): + elif isinstance(cls, type) and not is_generic_alias(cls) and issubclass(cls, Model): # Check in the annotations directly (for sqlmodel.Relationship) hints = get_type_hints(cls) if name in hints: diff --git a/reflex/vars.py b/reflex/vars.py index c14e6cdf66..68964ed5c4 100644 --- a/reflex/vars.py +++ b/reflex/vars.py @@ -677,6 +677,33 @@ def __getitem__(self, i: Any) -> Var: _var_is_string=False, ) + def __getattribute__(self, name: str) -> Any: + """Get a var attribute. + + Args: + name: The name of the attribute. + + Returns: + The var attribute. + + Raises: + AttributeError: If the attribute cannot be found, or if __getattr__ fallback should be used. + """ + try: + var_attribute = super().__getattribute__(name) + if not name.startswith("_"): + # Check if the attribute should be accessed through the Var instead of + # accessing one of the Var operations + type_ = types.get_attribute_access_type( + super().__getattribute__("_var_type"), name + ) + if type_ is not None: + raise AttributeError(f"{name} is being accessed through the Var.") + # Return the attribute as-is. + return var_attribute + except AttributeError: + raise # fall back to __getattr__ anyway + def __getattr__(self, name: str) -> Var: """Get a var attribute. @@ -1891,8 +1918,9 @@ def _deps( """ d = set() if obj is None: - if self.fget is not None: - obj = cast(FunctionType, self.fget) + fget = property.__getattribute__(self, "fget") + if fget is not None: + obj = cast(FunctionType, fget) else: return set() with contextlib.suppress(AttributeError): @@ -1976,7 +2004,7 @@ def _determine_var_type(self) -> Type: Returns: The type of the var. """ - hints = get_type_hints(self.fget) + hints = get_type_hints(property.__getattribute__(self, "fget")) if "return" in hints: return hints["return"] return Any