Browse Source

[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
Masen Furer 1 year ago
parent
commit
b23859a45d
2 changed files with 32 additions and 4 deletions
  1. 1 1
      reflex/utils/types.py
  2. 31 3
      reflex/vars.py

+ 1 - 1
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:

+ 31 - 3
reflex/vars.py

@@ -677,6 +677,33 @@ class 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 @@ class ComputedVar(Var, property):
         """
         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 @@ class ComputedVar(Var, property):
         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