瀏覽代碼

improve error messages for missing var operations (#4869)

* improve error messages for missing var operations

* maintain same traceback

* dang it darglint

* simon wanted this

* fix unit tests
Khaleel Al-Adhami 2 月之前
父節點
當前提交
31cb2f0e0c
共有 6 個文件被更改,包括 62 次插入9 次删除
  1. 40 5
      reflex/compiler/compiler.py
  2. 5 0
      reflex/components/tags/iter_tag.py
  3. 14 1
      reflex/state.py
  4. 1 1
      reflex/vars/base.py
  5. 1 1
      tests/units/test_state.py
  6. 1 1
      tests/units/test_var.py

+ 40 - 5
reflex/compiler/compiler.py

@@ -577,14 +577,49 @@ def into_component(component: Component | ComponentCallable) -> Component:
 
     Raises:
         TypeError: If the component is not a Component.
+
+    # noqa: DAR401
     """
     if (converted := _into_component_once(component)) is not None:
         return converted
-    if (
-        callable(component)
-        and (converted := _into_component_once(component())) is not None
-    ):
-        return converted
+    try:
+        if (
+            callable(component)
+            and (converted := _into_component_once(component())) is not None
+        ):
+            return converted
+    except KeyError as e:
+        key = e.args[0] if e.args else None
+        if key is not None and isinstance(key, Var):
+            raise TypeError(
+                "Cannot access a primitive map with a Var. Consider calling rx.Var.create() on the map."
+            ).with_traceback(e.__traceback__) from None
+        raise
+    except TypeError as e:
+        message = e.args[0] if e.args else None
+        if message and isinstance(message, str):
+            if message.endswith("has no len()") and (
+                "ArrayCastedVar" in message
+                or "ObjectCastedVar" in message
+                or "StringCastedVar" in message
+            ):
+                raise TypeError(
+                    "Cannot pass a Var to a built-in function. Consider using .length() for accessing the length of an iterable Var."
+                ).with_traceback(e.__traceback__) from None
+            if message.endswith(
+                "indices must be integers or slices, not NumberCastedVar"
+            ) or message.endswith(
+                "indices must be integers or slices, not BooleanCastedVar"
+            ):
+                raise TypeError(
+                    "Cannot index into a primitive sequence with a Var. Consider calling rx.Var.create() on the sequence."
+                ).with_traceback(e.__traceback__) from None
+        if "CastedVar" in str(e):
+            raise TypeError(
+                "Cannot pass a Var to a built-in function. Consider moving the operation to the backend, using existing Var operations, or defining a custom Var operation."
+            ).with_traceback(e.__traceback__) from None
+        raise
+
     raise TypeError(f"Expected a Component, got {type(component)}")
 
 

+ 5 - 0
reflex/components/tags/iter_tag.py

@@ -107,12 +107,14 @@ class IterTag(Tag):
 
         Raises:
             ValueError: If the render function takes more than 2 arguments.
+            ValueError: If the render function doesn't return a component.
 
         Returns:
             The rendered component.
         """
         # Import here to avoid circular imports.
         from reflex.components.base.fragment import Fragment
+        from reflex.components.component import Component
         from reflex.components.core.cond import Cond
         from reflex.components.core.foreach import Foreach
 
@@ -138,6 +140,9 @@ class IterTag(Tag):
         if isinstance(component, tuple):
             component = Fragment.create(*component)
 
+        if not isinstance(component, Component):
+            raise ValueError("The render function must return a component.")
+
         # Set the component key.
         if component.key is None:
             component.key = index

+ 14 - 1
reflex/state.py

@@ -14,6 +14,7 @@ import sys
 import time
 import typing
 import uuid
+import warnings
 from abc import ABC, abstractmethod
 from hashlib import md5
 from pathlib import Path
@@ -1641,14 +1642,26 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
 
         if events is None or _is_valid_type(events):
             return events
+
+        if not isinstance(events, Sequence):
+            events = [events]
+
         try:
             if all(_is_valid_type(e) for e in events):
                 return events
         except TypeError:
             pass
 
+        coroutines = [e for e in events if asyncio.iscoroutine(e)]
+
+        for coroutine in coroutines:
+            coroutine_name = coroutine.__qualname__
+            warnings.filterwarnings(
+                "ignore", message=f"coroutine '{coroutine_name}' was never awaited"
+            )
+
         raise TypeError(
-            f"Your handler {handler.fn.__qualname__} must only return/yield: None, Events or other EventHandlers referenced by their class (not using `self`)"
+            f"Your handler {handler.fn.__qualname__} must only return/yield: None, Events or other EventHandlers referenced by their class (i.e. using `type(self)` or other class references)."
         )
 
     async def _as_state_update(

+ 1 - 1
reflex/vars/base.py

@@ -475,7 +475,7 @@ class Var(Generic[VAR_TYPE]):
 
                 _default_var_type: ClassVar[GenericType] = default_type
 
-            new_to_var_operation_name = f"To{cls.__name__.removesuffix('Var')}Operation"
+            new_to_var_operation_name = f"{cls.__name__.removesuffix('Var')}CastedVar"
             ToVarOperation.__qualname__ = (
                 ToVarOperation.__qualname__.removesuffix(ToVarOperation.__name__)
                 + new_to_var_operation_name

+ 1 - 1
tests/units/test_state.py

@@ -1607,7 +1607,7 @@ async def test_state_with_invalid_yield(capsys, mock_app):
                     "An error occurred.",
                     level="error",
                     fallback_to_alert=True,
-                    description="TypeError: Your handler test_state_with_invalid_yield.<locals>.StateWithInvalidYield.invalid_handler must only return/yield: None, Events or other EventHandlers referenced by their class (not using `self`).<br/>See logs for details.",
+                    description="TypeError: Your handler test_state_with_invalid_yield.<locals>.StateWithInvalidYield.invalid_handler must only return/yield: None, Events or other EventHandlers referenced by their class (i.e. using `type(self)` or other class references)..<br/>See logs for details.",
                     id="backend_error",
                     position="top-center",
                     style={"width": "500px"},

+ 1 - 1
tests/units/test_var.py

@@ -1376,7 +1376,7 @@ def test_unsupported_types_for_string_contains(other):
         assert Var(_js_expr="var").to(str).contains(other)
     assert (
         err.value.args[0]
-        == f"Unsupported Operand type(s) for contains: ToStringOperation, {type(other).__name__}"
+        == f"Unsupported Operand type(s) for contains: StringCastedVar, {type(other).__name__}"
     )