ソースを参照

[REF-2774] Add ReflexError and subclasses, send in telemetry (#3271)

Martin Xu 1 年間 前
コミット
2789f32134
9 ファイル変更214 行追加112 行削除
  1. 18 9
      reflex/app.py
  2. 5 2
      reflex/base.py
  3. 2 1
      reflex/components/component.py
  4. 5 3
      reflex/config.py
  5. 18 9
      reflex/event.py
  6. 22 12
      reflex/state.py
  7. 64 8
      reflex/utils/exceptions.py
  8. 24 17
      reflex/utils/prerequisites.py
  9. 56 51
      reflex/vars.py

+ 18 - 9
reflex/app.py

@@ -403,9 +403,12 @@ class App(Base):
             The generated component.
             The generated component.
 
 
         Raises:
         Raises:
+            VarOperationTypeError: When an invalid component var related function is passed.
             TypeError: When an invalid component function is passed.
             TypeError: When an invalid component function is passed.
             exceptions.MatchTypeError: If the return types of match cases in rx.match are different.
             exceptions.MatchTypeError: If the return types of match cases in rx.match are different.
         """
         """
+        from reflex.utils.exceptions import VarOperationTypeError
+
         try:
         try:
             return component if isinstance(component, Component) else component()
             return component if isinstance(component, Component) else component()
         except exceptions.MatchTypeError:
         except exceptions.MatchTypeError:
@@ -413,7 +416,7 @@ class App(Base):
         except TypeError as e:
         except TypeError as e:
             message = str(e)
             message = str(e)
             if "BaseVar" in message or "ComputedVar" in message:
             if "BaseVar" in message or "ComputedVar" in message:
-                raise TypeError(
+                raise VarOperationTypeError(
                     "You may be trying to use an invalid Python function on a state var. "
                     "You may be trying to use an invalid Python function on a state var. "
                     "When referencing a var inside your render code, only limited var operations are supported. "
                     "When referencing a var inside your render code, only limited var operations are supported. "
                     "See the var operation docs here: https://reflex.dev/docs/vars/var-operations/"
                     "See the var operation docs here: https://reflex.dev/docs/vars/var-operations/"
@@ -555,11 +558,13 @@ class App(Base):
         Based on conflicts that NextJS would throw if not intercepted.
         Based on conflicts that NextJS would throw if not intercepted.
 
 
         Raises:
         Raises:
-            ValueError: exception showing which conflict exist with the route to be added
+            RouteValueError: exception showing which conflict exist with the route to be added
 
 
         Args:
         Args:
             new_route: the route being newly added.
             new_route: the route being newly added.
         """
         """
+        from reflex.utils.exceptions import RouteValueError
+
         if "[" not in new_route:
         if "[" not in new_route:
             return
             return
 
 
@@ -576,7 +581,7 @@ class App(Base):
             ):
             ):
                 if rw in segments and r != nr:
                 if rw in segments and r != nr:
                     # If the slugs in the segments of both routes are not the same, then the route is invalid
                     # If the slugs in the segments of both routes are not the same, then the route is invalid
-                    raise ValueError(
+                    raise RouteValueError(
                         f"You cannot use different slug names for the same dynamic path in  {route} and {new_route} ('{r}' != '{nr}')"
                         f"You cannot use different slug names for the same dynamic path in  {route} and {new_route} ('{r}' != '{nr}')"
                     )
                     )
                 elif rw not in segments and r != nr:
                 elif rw not in segments and r != nr:
@@ -755,8 +760,10 @@ class App(Base):
             export: Whether to compile the app for export.
             export: Whether to compile the app for export.
 
 
         Raises:
         Raises:
-            RuntimeError: When any page uses state, but no rx.State subclass is defined.
+            ReflexRuntimeError: When any page uses state, but no rx.State subclass is defined.
         """
         """
+        from reflex.utils.exceptions import ReflexRuntimeError
+
         # Render a default 404 page if the user didn't supply one
         # Render a default 404 page if the user didn't supply one
         if constants.Page404.SLUG not in self.pages:
         if constants.Page404.SLUG not in self.pages:
             self.add_custom_404_page()
             self.add_custom_404_page()
@@ -837,7 +844,7 @@ class App(Base):
 
 
         # Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
         # Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
         if code_uses_state_contexts(stateful_components_code) and self.state is None:
         if code_uses_state_contexts(stateful_components_code) and self.state is None:
-            raise RuntimeError(
+            raise ReflexRuntimeError(
                 "To access rx.State in frontend components, at least one "
                 "To access rx.State in frontend components, at least one "
                 "subclass of rx.State must be defined in the app."
                 "subclass of rx.State must be defined in the app."
             )
             )
@@ -1137,10 +1144,12 @@ def upload(app: App):
             emitted by the upload handler.
             emitted by the upload handler.
 
 
         Raises:
         Raises:
-            ValueError: if there are no args with supported annotation.
-            TypeError: if a background task is used as the handler.
+            UploadValueError: if there are no args with supported annotation.
+            UploadTypeError: if a background task is used as the handler.
             HTTPException: when the request does not include token / handler headers.
             HTTPException: when the request does not include token / handler headers.
         """
         """
+        from reflex.utils.exceptions import UploadTypeError, UploadValueError
+
         token = request.headers.get("reflex-client-token")
         token = request.headers.get("reflex-client-token")
         handler = request.headers.get("reflex-event-handler")
         handler = request.headers.get("reflex-event-handler")
 
 
@@ -1166,7 +1175,7 @@ def upload(app: App):
         # check if there exists any handler args with annotation, List[UploadFile]
         # check if there exists any handler args with annotation, List[UploadFile]
         if isinstance(func, EventHandler):
         if isinstance(func, EventHandler):
             if func.is_background:
             if func.is_background:
-                raise TypeError(
+                raise UploadTypeError(
                     f"@rx.background is not supported for upload handler `{handler}`.",
                     f"@rx.background is not supported for upload handler `{handler}`.",
                 )
                 )
             func = func.fn
             func = func.fn
@@ -1181,7 +1190,7 @@ def upload(app: App):
                 break
                 break
 
 
         if not handler_upload_param:
         if not handler_upload_param:
-            raise ValueError(
+            raise UploadValueError(
                 f"`{handler}` handler should have a parameter annotated as "
                 f"`{handler}` handler should have a parameter annotated as "
                 "List[rx.UploadFile]"
                 "List[rx.UploadFile]"
             )
             )

+ 5 - 2
reflex/base.py

@@ -1,4 +1,5 @@
 """Define the base Reflex class."""
 """Define the base Reflex class."""
+
 from __future__ import annotations
 from __future__ import annotations
 
 
 import os
 import os
@@ -26,15 +27,17 @@ def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None
         field_name: name of attribute
         field_name: name of attribute
 
 
     Raises:
     Raises:
-        NameError: If state var field shadows another in its parent state
+        VarNameError: If state var field shadows another in its parent state
     """
     """
+    from reflex.utils.exceptions import VarNameError
+
     reload = os.getenv(constants.RELOAD_CONFIG) == "True"
     reload = os.getenv(constants.RELOAD_CONFIG) == "True"
     for base in bases:
     for base in bases:
         try:
         try:
             if not reload and getattr(base, field_name, None):
             if not reload and getattr(base, field_name, None):
                 pass
                 pass
         except TypeError as te:
         except TypeError as te:
-            raise NameError(
+            raise VarNameError(
                 f'State var "{field_name}" in {base} has been shadowed by a substate var; '
                 f'State var "{field_name}" in {base} has been shadowed by a substate var; '
                 f'use a different field name instead".'
                 f'use a different field name instead".'
             ) from te
             ) from te

+ 2 - 1
reflex/components/component.py

@@ -731,6 +731,7 @@ class Component(BaseComponent, ABC):
         # Import here to avoid circular imports.
         # Import here to avoid circular imports.
         from reflex.components.base.bare import Bare
         from reflex.components.base.bare import Bare
         from reflex.components.base.fragment import Fragment
         from reflex.components.base.fragment import Fragment
+        from reflex.utils.exceptions import ComponentTypeError
 
 
         # Translate deprecated props to new names.
         # Translate deprecated props to new names.
         new_prop_names = [
         new_prop_names = [
@@ -757,7 +758,7 @@ class Component(BaseComponent, ABC):
                     validate_children(child)
                     validate_children(child)
                 # Make sure the child is a valid type.
                 # Make sure the child is a valid type.
                 if not types._isinstance(child, ComponentChild):
                 if not types._isinstance(child, ComponentChild):
-                    raise TypeError(
+                    raise ComponentTypeError(
                         "Children of Reflex components must be other components, "
                         "Children of Reflex components must be other components, "
                         "state vars, or primitive Python types. "
                         "state vars, or primitive Python types. "
                         f"Got child {child} of type {type(child)}.",
                         f"Got child {child} of type {type(child)}.",

+ 5 - 3
reflex/config.py

@@ -251,8 +251,10 @@ class Config(Base):
             The updated config values.
             The updated config values.
 
 
         Raises:
         Raises:
-            ValueError: If an environment variable is set to an invalid type.
+            EnvVarValueError: If an environment variable is set to an invalid type.
         """
         """
+        from reflex.utils.exceptions import EnvVarValueError
+
         updated_values = {}
         updated_values = {}
         # Iterate over the fields.
         # Iterate over the fields.
         for key, field in self.__fields__.items():
         for key, field in self.__fields__.items():
@@ -273,11 +275,11 @@ class Config(Base):
                         env_var = env_var.lower() in ["true", "1", "yes"]
                         env_var = env_var.lower() in ["true", "1", "yes"]
                     else:
                     else:
                         env_var = field.type_(env_var)
                         env_var = field.type_(env_var)
-                except ValueError:
+                except ValueError as ve:
                     console.error(
                     console.error(
                         f"Could not convert {key.upper()}={env_var} to type {field.type_}"
                         f"Could not convert {key.upper()}={env_var} to type {field.type_}"
                     )
                     )
-                    raise
+                    raise EnvVarValueError from ve
 
 
                 # Set the value.
                 # Set the value.
                 updated_values[key] = env_var
                 updated_values[key] = env_var

+ 18 - 9
reflex/event.py

@@ -180,8 +180,10 @@ class EventHandler(EventActionsMixin):
             The event spec, containing both the function and args.
             The event spec, containing both the function and args.
 
 
         Raises:
         Raises:
-            TypeError: If the arguments are invalid.
+            EventHandlerTypeError: If the arguments are invalid.
         """
         """
+        from reflex.utils.exceptions import EventHandlerTypeError
+
         # Get the function args.
         # Get the function args.
         fn_args = inspect.getfullargspec(self.fn).args[1:]
         fn_args = inspect.getfullargspec(self.fn).args[1:]
         fn_args = (Var.create_safe(arg) for arg in fn_args)
         fn_args = (Var.create_safe(arg) for arg in fn_args)
@@ -197,7 +199,7 @@ class EventHandler(EventActionsMixin):
             try:
             try:
                 values.append(Var.create(arg, _var_is_string=isinstance(arg, str)))
                 values.append(Var.create(arg, _var_is_string=isinstance(arg, str)))
             except TypeError as e:
             except TypeError as e:
-                raise TypeError(
+                raise EventHandlerTypeError(
                     f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
                     f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
                 ) from e
                 ) from e
         payload = tuple(zip(fn_args, values))
         payload = tuple(zip(fn_args, values))
@@ -256,8 +258,10 @@ class EventSpec(EventActionsMixin):
             The event spec with the new arguments.
             The event spec with the new arguments.
 
 
         Raises:
         Raises:
-            TypeError: If the arguments are invalid.
+            EventHandlerTypeError: If the arguments are invalid.
         """
         """
+        from reflex.utils.exceptions import EventHandlerTypeError
+
         # Get the remaining unfilled function args.
         # Get the remaining unfilled function args.
         fn_args = inspect.getfullargspec(self.handler.fn).args[1 + len(self.args) :]
         fn_args = inspect.getfullargspec(self.handler.fn).args[1 + len(self.args) :]
         fn_args = (Var.create_safe(arg) for arg in fn_args)
         fn_args = (Var.create_safe(arg) for arg in fn_args)
@@ -268,7 +272,7 @@ class EventSpec(EventActionsMixin):
             try:
             try:
                 values.append(Var.create(arg, _var_is_string=isinstance(arg, str)))
                 values.append(Var.create(arg, _var_is_string=isinstance(arg, str)))
             except TypeError as e:
             except TypeError as e:
-                raise TypeError(
+                raise EventHandlerTypeError(
                     f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
                     f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
                 ) from e
                 ) from e
         new_payload = tuple(zip(fn_args, values))
         new_payload = tuple(zip(fn_args, values))
@@ -312,10 +316,12 @@ class CallableEventSpec(EventSpec):
             The EventSpec returned from calling the function.
             The EventSpec returned from calling the function.
 
 
         Raises:
         Raises:
-            TypeError: If the CallableEventSpec has no associated function.
+            EventHandlerTypeError: If the CallableEventSpec has no associated function.
         """
         """
+        from reflex.utils.exceptions import EventHandlerTypeError
+
         if self.fn is None:
         if self.fn is None:
-            raise TypeError("CallableEventSpec has no associated function.")
+            raise EventHandlerTypeError("CallableEventSpec has no associated function.")
         return self.fn(*args, **kwargs)
         return self.fn(*args, **kwargs)
 
 
 
 
@@ -834,10 +840,11 @@ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]:
         The event specs from calling the function.
         The event specs from calling the function.
 
 
     Raises:
     Raises:
-        ValueError: If the lambda has an invalid signature.
+        EventHandlerValueError: If the lambda has an invalid signature.
     """
     """
     # Import here to avoid circular imports.
     # Import here to avoid circular imports.
     from reflex.event import EventHandler, EventSpec
     from reflex.event import EventHandler, EventSpec
+    from reflex.utils.exceptions import EventHandlerValueError
 
 
     # Get the args of the lambda.
     # Get the args of the lambda.
     args = inspect.getfullargspec(fn).args
     args = inspect.getfullargspec(fn).args
@@ -851,7 +858,7 @@ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]:
         elif len(args) == 1:
         elif len(args) == 1:
             out = fn(arg)
             out = fn(arg)
         else:
         else:
-            raise ValueError(f"Lambda {fn} must have 0 or 1 arguments.")
+            raise EventHandlerValueError(f"Lambda {fn} must have 0 or 1 arguments.")
 
 
     # Convert the output to a list.
     # Convert the output to a list.
     if not isinstance(out, List):
     if not isinstance(out, List):
@@ -869,7 +876,9 @@ def call_event_fn(fn: Callable, arg: Union[Var, ArgsSpec]) -> list[EventSpec]:
 
 
         # Make sure the event spec is valid.
         # Make sure the event spec is valid.
         if not isinstance(e, EventSpec):
         if not isinstance(e, EventSpec):
-            raise ValueError(f"Lambda {fn} returned an invalid event spec: {e}.")
+            raise EventHandlerValueError(
+                f"Lambda {fn} returned an invalid event spec: {e}."
+            )
 
 
         # Add the event spec to the chain.
         # Add the event spec to the chain.
         events.append(e)
         events.append(e)

+ 22 - 12
reflex/state.py

@@ -279,11 +279,13 @@ class EventHandlerSetVar(EventHandler):
 
 
         Raises:
         Raises:
             AttributeError: If the given Var name does not exist on the state.
             AttributeError: If the given Var name does not exist on the state.
-            ValueError: If the given Var name is not a str
+            EventHandlerValueError: If the given Var name is not a str
         """
         """
+        from reflex.utils.exceptions import EventHandlerValueError
+
         if args:
         if args:
             if not isinstance(args[0], str):
             if not isinstance(args[0], str):
-                raise ValueError(
+                raise EventHandlerValueError(
                     f"Var name must be passed as a string, got {args[0]!r}"
                     f"Var name must be passed as a string, got {args[0]!r}"
                 )
                 )
             # Check that the requested Var setter exists on the State at compile time.
             # Check that the requested Var setter exists on the State at compile time.
@@ -380,10 +382,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             **kwargs: The kwargs to pass to the Pydantic init method.
             **kwargs: The kwargs to pass to the Pydantic init method.
 
 
         Raises:
         Raises:
-            RuntimeError: If the state is instantiated directly by end user.
+            ReflexRuntimeError: If the state is instantiated directly by end user.
         """
         """
+        from reflex.utils.exceptions import ReflexRuntimeError
+
         if not _reflex_internal_init and not is_testing_env():
         if not _reflex_internal_init and not is_testing_env():
-            raise RuntimeError(
+            raise ReflexRuntimeError(
                 "State classes should not be instantiated directly in a Reflex app. "
                 "State classes should not be instantiated directly in a Reflex app. "
                 "See https://reflex.dev/docs/state/ for further information."
                 "See https://reflex.dev/docs/state/ for further information."
             )
             )
@@ -438,8 +442,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             **kwargs: The kwargs to pass to the pydantic init_subclass method.
             **kwargs: The kwargs to pass to the pydantic init_subclass method.
 
 
         Raises:
         Raises:
-            ValueError: If a substate class shadows another.
+            StateValueError: If a substate class shadows another.
         """
         """
+        from reflex.utils.exceptions import StateValueError
+
         super().__init_subclass__(**kwargs)
         super().__init_subclass__(**kwargs)
         # Event handlers should not shadow builtin state methods.
         # Event handlers should not shadow builtin state methods.
         cls._check_overridden_methods()
         cls._check_overridden_methods()
@@ -471,7 +477,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
                 else:
                 else:
                     # During normal operation, subclasses cannot have the same name, even if they are
                     # During normal operation, subclasses cannot have the same name, even if they are
                     # defined in different modules.
                     # defined in different modules.
-                    raise ValueError(
+                    raise StateValueError(
                         f"The substate class '{cls.__name__}' has been defined multiple times. "
                         f"The substate class '{cls.__name__}' has been defined multiple times. "
                         "Shadowing substate classes is not allowed."
                         "Shadowing substate classes is not allowed."
                     )
                     )
@@ -829,10 +835,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             prop: The variable to initialize
             prop: The variable to initialize
 
 
         Raises:
         Raises:
-            TypeError: if the variable has an incorrect type
+            VarTypeError: if the variable has an incorrect type
         """
         """
+        from reflex.utils.exceptions import VarTypeError
+
         if not types.is_valid_var_type(prop._var_type):
         if not types.is_valid_var_type(prop._var_type):
-            raise TypeError(
+            raise VarTypeError(
                 "State vars must be primitive Python types, "
                 "State vars must be primitive Python types, "
                 "Plotly figures, Pandas dataframes, "
                 "Plotly figures, Pandas dataframes, "
                 "or subclasses of rx.Base. "
                 "or subclasses of rx.Base. "
@@ -1688,10 +1696,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         if initial:
         if initial:
             computed_vars = {
             computed_vars = {
                 # Include initial computed vars.
                 # Include initial computed vars.
-                prop_name: cv._initial_value
-                if isinstance(cv, ComputedVar)
-                and not isinstance(cv._initial_value, types.Unset)
-                else self.get_value(getattr(self, prop_name))
+                prop_name: (
+                    cv._initial_value
+                    if isinstance(cv, ComputedVar)
+                    and not isinstance(cv._initial_value, types.Unset)
+                    else self.get_value(getattr(self, prop_name))
+                )
                 for prop_name, cv in self.computed_vars.items()
                 for prop_name, cv in self.computed_vars.items()
             }
             }
         elif include_computed:
         elif include_computed:

+ 64 - 8
reflex/utils/exceptions.py

@@ -1,21 +1,77 @@
 """Custom Exceptions."""
 """Custom Exceptions."""
 
 
 
 
-class InvalidStylePropError(TypeError):
-    """Custom Type Error when style props have invalid values."""
+class ReflexError(Exception):
+    """Base exception for all Reflex exceptions."""
+
+
+class ReflexRuntimeError(ReflexError, RuntimeError):
+    """Custom RuntimeError for Reflex."""
+
+
+class UploadTypeError(ReflexError, TypeError):
+    """Custom TypeError for upload related errors."""
+
+
+class EnvVarValueError(ReflexError, ValueError):
+    """Custom ValueError raised when unable to convert env var to expected type."""
+
+
+class ComponentTypeError(ReflexError, TypeError):
+    """Custom TypeError for component related errors."""
+
+
+class EventHandlerTypeError(ReflexError, TypeError):
+    """Custom TypeError for event handler related errors."""
+
+
+class EventHandlerValueError(ReflexError, ValueError):
+    """Custom ValueError for event handler related errors."""
+
+
+class StateValueError(ReflexError, ValueError):
+    """Custom ValueError for state related errors."""
+
 
 
-    pass
+class VarNameError(ReflexError, NameError):
+    """Custom NameError for when a state var has been shadowed by a substate var."""
 
 
 
 
-class ImmutableStateError(AttributeError):
+class VarTypeError(ReflexError, TypeError):
+    """Custom TypeError for var related errors."""
+
+
+class VarValueError(ReflexError, ValueError):
+    """Custom ValueError for var related errors."""
+
+
+class VarAttributeError(ReflexError, AttributeError):
+    """Custom AttributeError for var related errors."""
+
+
+class UploadValueError(ReflexError, ValueError):
+    """Custom ValueError for upload related errors."""
+
+
+class RouteValueError(ReflexError, ValueError):
+    """Custom ValueError for route related errors."""
+
+
+class VarOperationTypeError(ReflexError, TypeError):
+    """Custom TypeError for when unsupported operations are performed on vars."""
+
+
+class InvalidStylePropError(ReflexError, TypeError):
+    """Custom Type Error when style props have invalid values."""
+
+
+class ImmutableStateError(ReflexError):
     """Raised when a background task attempts to modify state outside of context."""
     """Raised when a background task attempts to modify state outside of context."""
 
 
 
 
-class LockExpiredError(Exception):
+class LockExpiredError(ReflexError):
     """Raised when the state lock expires while an event is being processed."""
     """Raised when the state lock expires while an event is being processed."""
 
 
 
 
-class MatchTypeError(TypeError):
+class MatchTypeError(ReflexError, TypeError):
     """Raised when the return types of match cases are different."""
     """Raised when the return types of match cases are different."""
-
-    pass

+ 24 - 17
reflex/utils/prerequisites.py

@@ -210,28 +210,35 @@ def get_app(reload: bool = False) -> ModuleType:
 
 
     Raises:
     Raises:
         RuntimeError: If the app name is not set in the config.
         RuntimeError: If the app name is not set in the config.
+        exceptions.ReflexError: Reflex specific errors.
     """
     """
-    os.environ[constants.RELOAD_CONFIG] = str(reload)
-    config = get_config()
-    if not config.app_name:
-        raise RuntimeError(
-            "Cannot get the app module because `app_name` is not set in rxconfig! "
-            "If this error occurs in a reflex test case, ensure that `get_app` is mocked."
-        )
-    module = config.module
-    sys.path.insert(0, os.getcwd())
-    app = __import__(module, fromlist=(constants.CompileVars.APP,))
+    from reflex.utils import exceptions, telemetry
+
+    try:
+        os.environ[constants.RELOAD_CONFIG] = str(reload)
+        config = get_config()
+        if not config.app_name:
+            raise RuntimeError(
+                "Cannot get the app module because `app_name` is not set in rxconfig! "
+                "If this error occurs in a reflex test case, ensure that `get_app` is mocked."
+            )
+        module = config.module
+        sys.path.insert(0, os.getcwd())
+        app = __import__(module, fromlist=(constants.CompileVars.APP,))
 
 
-    if reload:
-        from reflex.state import reload_state_module
+        if reload:
+            from reflex.state import reload_state_module
 
 
-        # Reset rx.State subclasses to avoid conflict when reloading.
-        reload_state_module(module=module)
+            # Reset rx.State subclasses to avoid conflict when reloading.
+            reload_state_module(module=module)
 
 
-        # Reload the app module.
-        importlib.reload(app)
+            # Reload the app module.
+            importlib.reload(app)
 
 
-    return app
+        return app
+    except exceptions.ReflexError as ex:
+        telemetry.send("error", context="frontend", detail=str(ex))
+        raise
 
 
 
 
 def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
 def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:

+ 56 - 51
reflex/vars.py

@@ -35,6 +35,7 @@ from typing import (
 from reflex import constants
 from reflex import constants
 from reflex.base import Base
 from reflex.base import Base
 from reflex.utils import console, format, imports, serializers, types
 from reflex.utils import console, format, imports, serializers, types
+from reflex.utils.exceptions import VarAttributeError, VarTypeError, VarValueError
 
 
 # This module used to export ImportVar itself, so we still import it for export here
 # This module used to export ImportVar itself, so we still import it for export here
 from reflex.utils.imports import ImportDict, ImportVar
 from reflex.utils.imports import ImportDict, ImportVar
@@ -353,7 +354,7 @@ class Var:
             The var.
             The var.
 
 
         Raises:
         Raises:
-            TypeError: If the value is JSON-unserializable.
+            VarTypeError: If the value is JSON-unserializable.
         """
         """
         # Check for none values.
         # Check for none values.
         if value is None:
         if value is None:
@@ -372,7 +373,7 @@ class Var:
         type_ = type(value)
         type_ = type(value)
         name = value if type_ in types.JSONType else serializers.serialize(value)
         name = value if type_ in types.JSONType else serializers.serialize(value)
         if name is None:
         if name is None:
-            raise TypeError(
+            raise VarTypeError(
                 f"No JSON serializer found for var {value} of type {type_}."
                 f"No JSON serializer found for var {value} of type {type_}."
             )
             )
         name = name if isinstance(name, str) else format.json_dumps(name)
         name = name if isinstance(name, str) else format.json_dumps(name)
@@ -542,9 +543,9 @@ class Var:
         """Raise exception if using Var in a boolean context.
         """Raise exception if using Var in a boolean context.
 
 
         Raises:
         Raises:
-            TypeError: when attempting to bool-ify the Var.
+            VarTypeError: when attempting to bool-ify the Var.
         """
         """
-        raise TypeError(
+        raise VarTypeError(
             f"Cannot convert Var {self._var_full_name!r} to bool for use with `if`, `and`, `or`, and `not`. "
             f"Cannot convert Var {self._var_full_name!r} to bool for use with `if`, `and`, `or`, and `not`. "
             "Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)."
             "Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)."
         )
         )
@@ -553,9 +554,9 @@ class Var:
         """Raise exception if using Var in an iterable context.
         """Raise exception if using Var in an iterable context.
 
 
         Raises:
         Raises:
-            TypeError: when attempting to iterate over the Var.
+            VarTypeError: when attempting to iterate over the Var.
         """
         """
-        raise TypeError(
+        raise VarTypeError(
             f"Cannot iterate over Var {self._var_full_name!r}. Instead use `rx.foreach`."
             f"Cannot iterate over Var {self._var_full_name!r}. Instead use `rx.foreach`."
         )
         )
 
 
@@ -584,7 +585,7 @@ class Var:
             The indexed var.
             The indexed var.
 
 
         Raises:
         Raises:
-            TypeError: If the var is not indexable.
+            VarTypeError: If the var is not indexable.
         """
         """
         # Indexing is only supported for strings, lists, tuples, dicts, and dataframes.
         # Indexing is only supported for strings, lists, tuples, dicts, and dataframes.
         if not (
         if not (
@@ -592,11 +593,11 @@ class Var:
             or types.is_dataframe(self._var_type)
             or types.is_dataframe(self._var_type)
         ):
         ):
             if self._var_type == Any:
             if self._var_type == Any:
-                raise TypeError(
+                raise VarTypeError(
                     "Could not index into var of type Any. (If you are trying to index into a state var, "
                     "Could not index into var of type Any. (If you are trying to index into a state var, "
                     "add the correct type annotation to the var.)"
                     "add the correct type annotation to the var.)"
                 )
                 )
-            raise TypeError(
+            raise VarTypeError(
                 f"Var {self._var_name} of type {self._var_type} does not support indexing."
                 f"Var {self._var_name} of type {self._var_type} does not support indexing."
             )
             )
 
 
@@ -615,7 +616,7 @@ class Var:
                 or isinstance(i, Var)
                 or isinstance(i, Var)
                 and not i._var_type == int
                 and not i._var_type == int
             ):
             ):
-                raise TypeError("Index must be an integer or an integer var.")
+                raise VarTypeError("Index must be an integer or an integer var.")
 
 
             # Handle slices first.
             # Handle slices first.
             if isinstance(i, slice):
             if isinstance(i, slice):
@@ -658,7 +659,7 @@ class Var:
                 i._var_type, types.get_args(Union[int, str, float])
                 i._var_type, types.get_args(Union[int, str, float])
             )
             )
         ):
         ):
-            raise TypeError(
+            raise VarTypeError(
                 "Index must be one of the following types: int, str, int or str Var"
                 "Index must be one of the following types: int, str, int or str Var"
             )
             )
         # Get the type of the indexed var.
         # Get the type of the indexed var.
@@ -687,7 +688,7 @@ class Var:
             The var attribute.
             The var attribute.
 
 
         Raises:
         Raises:
-            AttributeError: If the attribute cannot be found, or if __getattr__ fallback should be used.
+            VarAttributeError: If the attribute cannot be found, or if __getattr__ fallback should be used.
         """
         """
         try:
         try:
             var_attribute = super().__getattribute__(name)
             var_attribute = super().__getattribute__(name)
@@ -698,10 +699,12 @@ class Var:
                     super().__getattribute__("_var_type"), name
                     super().__getattribute__("_var_type"), name
                 )
                 )
                 if type_ is not None:
                 if type_ is not None:
-                    raise AttributeError(f"{name} is being accessed through the Var.")
+                    raise VarAttributeError(
+                        f"{name} is being accessed through the Var."
+                    )
             # Return the attribute as-is.
             # Return the attribute as-is.
             return var_attribute
             return var_attribute
-        except AttributeError:
+        except VarAttributeError:
             raise  # fall back to __getattr__ anyway
             raise  # fall back to __getattr__ anyway
 
 
     def __getattr__(self, name: str) -> Var:
     def __getattr__(self, name: str) -> Var:
@@ -714,13 +717,13 @@ class Var:
             The var attribute.
             The var attribute.
 
 
         Raises:
         Raises:
-            AttributeError: If the var is wrongly annotated or can't find attribute.
-            TypeError: If an annotation to the var isn't provided.
+            VarAttributeError: If the var is wrongly annotated or can't find attribute.
+            VarTypeError: If an annotation to the var isn't provided.
         """
         """
         # Check if the attribute is one of the class fields.
         # Check if the attribute is one of the class fields.
         if not name.startswith("_"):
         if not name.startswith("_"):
             if self._var_type == Any:
             if self._var_type == Any:
-                raise TypeError(
+                raise VarTypeError(
                     f"You must provide an annotation for the state var `{self._var_full_name}`. Annotation cannot be `{self._var_type}`"
                     f"You must provide an annotation for the state var `{self._var_full_name}`. Annotation cannot be `{self._var_type}`"
                 ) from None
                 ) from None
             is_optional = types.is_optional(self._var_type)
             is_optional = types.is_optional(self._var_type)
@@ -734,16 +737,16 @@ class Var:
                 )
                 )
 
 
             if name in REPLACED_NAMES:
             if name in REPLACED_NAMES:
-                raise AttributeError(
+                raise VarAttributeError(
                     f"Field {name!r} was renamed to {REPLACED_NAMES[name]!r}"
                     f"Field {name!r} was renamed to {REPLACED_NAMES[name]!r}"
                 )
                 )
 
 
-            raise AttributeError(
+            raise VarAttributeError(
                 f"The State var `{self._var_full_name}` has no attribute '{name}' or may have been annotated "
                 f"The State var `{self._var_full_name}` has no attribute '{name}' or may have been annotated "
                 f"wrongly."
                 f"wrongly."
             )
             )
 
 
-        raise AttributeError(
+        raise VarAttributeError(
             f"The State var has no attribute '{name}' or may have been annotated wrongly.",
             f"The State var has no attribute '{name}' or may have been annotated wrongly.",
         )
         )
 
 
@@ -770,8 +773,8 @@ class Var:
             The operation result.
             The operation result.
 
 
         Raises:
         Raises:
-            TypeError: If the operation between two operands is invalid.
-            ValueError: If flip is set to true and value of operand is not provided
+            VarTypeError: If the operation between two operands is invalid.
+            VarValueError: If flip is set to true and value of operand is not provided
         """
         """
         if isinstance(other, str):
         if isinstance(other, str):
             other = Var.create(json.dumps(other))
             other = Var.create(json.dumps(other))
@@ -781,7 +784,7 @@ class Var:
         type_ = type_ or self._var_type
         type_ = type_ or self._var_type
 
 
         if other is None and flip:
         if other is None and flip:
-            raise ValueError(
+            raise VarValueError(
                 "flip_operands cannot be set to True if the value of 'other' operand is not provided"
                 "flip_operands cannot be set to True if the value of 'other' operand is not provided"
             )
             )
 
 
@@ -804,7 +807,7 @@ class Var:
                 types.get_base_class(right_operand._var_type),  # type: ignore
                 types.get_base_class(right_operand._var_type),  # type: ignore
                 op,
                 op,
             ):
             ):
-                raise TypeError(
+                raise VarTypeError(
                     f"Unsupported Operand type(s) for {op}: `{left_operand._var_full_name}` of type {left_operand._var_type.__name__} and `{right_operand._var_full_name}` of type {right_operand._var_type.__name__}"  # type: ignore
                     f"Unsupported Operand type(s) for {op}: `{left_operand._var_full_name}` of type {left_operand._var_type.__name__} and `{right_operand._var_full_name}` of type {right_operand._var_type.__name__}"  # type: ignore
                 )
                 )
 
 
@@ -925,10 +928,10 @@ class Var:
             A var with the absolute value.
             A var with the absolute value.
 
 
         Raises:
         Raises:
-            TypeError: If the var is not a list.
+            VarTypeError: If the var is not a list.
         """
         """
         if not types._issubclass(self._var_type, List):
         if not types._issubclass(self._var_type, List):
-            raise TypeError(f"Cannot get length of non-list var {self}.")
+            raise VarTypeError(f"Cannot get length of non-list var {self}.")
         return self._replace(
         return self._replace(
             _var_name=f"{self._var_name}.length",
             _var_name=f"{self._var_name}.length",
             _var_type=int,
             _var_type=int,
@@ -1328,9 +1331,9 @@ class Var:
         """Override the 'in' operator to alert the user that it is not supported.
         """Override the 'in' operator to alert the user that it is not supported.
 
 
         Raises:
         Raises:
-            TypeError: the operation is not supported
+            VarTypeError: the operation is not supported
         """
         """
-        raise TypeError(
+        raise VarTypeError(
             "'in' operator not supported for Var types, use Var.contains() instead."
             "'in' operator not supported for Var types, use Var.contains() instead."
         )
         )
 
 
@@ -1341,13 +1344,13 @@ class Var:
             other: The object to check.
             other: The object to check.
 
 
         Raises:
         Raises:
-            TypeError: If the var is not a valid type: dict, list, tuple or str.
+            VarTypeError: If the var is not a valid type: dict, list, tuple or str.
 
 
         Returns:
         Returns:
             A var representing the contain check.
             A var representing the contain check.
         """
         """
         if not (types._issubclass(self._var_type, Union[dict, list, tuple, str, set])):
         if not (types._issubclass(self._var_type, Union[dict, list, tuple, str, set])):
-            raise TypeError(
+            raise VarTypeError(
                 f"Var {self._var_full_name} of type {self._var_type} does not support contains check."
                 f"Var {self._var_full_name} of type {self._var_type} does not support contains check."
             )
             )
         method = (
         method = (
@@ -1371,7 +1374,7 @@ class Var:
             if types._issubclass(self._var_type, str) and not types._issubclass(
             if types._issubclass(self._var_type, str) and not types._issubclass(
                 other._var_type, str
                 other._var_type, str
             ):
             ):
-                raise TypeError(
+                raise VarTypeError(
                     f"'in <string>' requires string as left operand, not {other._var_type}"
                     f"'in <string>' requires string as left operand, not {other._var_type}"
                 )
                 )
             return self._replace(
             return self._replace(
@@ -1385,13 +1388,13 @@ class Var:
         """Reverse a list var.
         """Reverse a list var.
 
 
         Raises:
         Raises:
-            TypeError: If the var is not a list.
+            VarTypeError: If the var is not a list.
 
 
         Returns:
         Returns:
             A var with the reversed list.
             A var with the reversed list.
         """
         """
         if not types._issubclass(self._var_type, list):
         if not types._issubclass(self._var_type, list):
-            raise TypeError(f"Cannot reverse non-list var {self._var_full_name}.")
+            raise VarTypeError(f"Cannot reverse non-list var {self._var_full_name}.")
 
 
         return self._replace(
         return self._replace(
             _var_name=f"[...{self._var_full_name}].reverse()",
             _var_name=f"[...{self._var_full_name}].reverse()",
@@ -1406,10 +1409,10 @@ class Var:
             A var with the lowercase string.
             A var with the lowercase string.
 
 
         Raises:
         Raises:
-            TypeError: If the var is not a string.
+            VarTypeError: If the var is not a string.
         """
         """
         if not types._issubclass(self._var_type, str):
         if not types._issubclass(self._var_type, str):
-            raise TypeError(
+            raise VarTypeError(
                 f"Cannot convert non-string var {self._var_full_name} to lowercase."
                 f"Cannot convert non-string var {self._var_full_name} to lowercase."
             )
             )
 
 
@@ -1426,10 +1429,10 @@ class Var:
             A var with the uppercase string.
             A var with the uppercase string.
 
 
         Raises:
         Raises:
-            TypeError: If the var is not a string.
+            VarTypeError: If the var is not a string.
         """
         """
         if not types._issubclass(self._var_type, str):
         if not types._issubclass(self._var_type, str):
-            raise TypeError(
+            raise VarTypeError(
                 f"Cannot convert non-string var {self._var_full_name} to uppercase."
                 f"Cannot convert non-string var {self._var_full_name} to uppercase."
             )
             )
 
 
@@ -1449,10 +1452,10 @@ class Var:
             A var with the stripped string.
             A var with the stripped string.
 
 
         Raises:
         Raises:
-            TypeError: If the var is not a string.
+            VarTypeError: If the var is not a string.
         """
         """
         if not types._issubclass(self._var_type, str):
         if not types._issubclass(self._var_type, str):
-            raise TypeError(f"Cannot strip non-string var {self._var_full_name}.")
+            raise VarTypeError(f"Cannot strip non-string var {self._var_full_name}.")
 
 
         other = Var.create_safe(json.dumps(other)) if isinstance(other, str) else other
         other = Var.create_safe(json.dumps(other)) if isinstance(other, str) else other
 
 
@@ -1472,10 +1475,10 @@ class Var:
             A var with the list.
             A var with the list.
 
 
         Raises:
         Raises:
-            TypeError: If the var is not a string.
+            VarTypeError: If the var is not a string.
         """
         """
         if not types._issubclass(self._var_type, str):
         if not types._issubclass(self._var_type, str):
-            raise TypeError(f"Cannot split non-string var {self._var_full_name}.")
+            raise VarTypeError(f"Cannot split non-string var {self._var_full_name}.")
 
 
         other = Var.create_safe(json.dumps(other)) if isinstance(other, str) else other
         other = Var.create_safe(json.dumps(other)) if isinstance(other, str) else other
 
 
@@ -1496,10 +1499,10 @@ class Var:
             A var with the string.
             A var with the string.
 
 
         Raises:
         Raises:
-            TypeError: If the var is not a list.
+            VarTypeError: If the var is not a list.
         """
         """
         if not types._issubclass(self._var_type, list):
         if not types._issubclass(self._var_type, list):
-            raise TypeError(f"Cannot join non-list var {self._var_full_name}.")
+            raise VarTypeError(f"Cannot join non-list var {self._var_full_name}.")
 
 
         if other is None:
         if other is None:
             other = Var.create_safe('""')
             other = Var.create_safe('""')
@@ -1525,11 +1528,11 @@ class Var:
             A var representing foreach operation.
             A var representing foreach operation.
 
 
         Raises:
         Raises:
-            TypeError: If the var is not a list.
+            VarTypeError: If the var is not a list.
         """
         """
         inner_types = get_args(self._var_type)
         inner_types = get_args(self._var_type)
         if not inner_types:
         if not inner_types:
-            raise TypeError(
+            raise VarTypeError(
                 f"Cannot foreach over non-sequence var {self._var_full_name} of type {self._var_type}."
                 f"Cannot foreach over non-sequence var {self._var_full_name} of type {self._var_type}."
             )
             )
         arg = BaseVar(
         arg = BaseVar(
@@ -1566,25 +1569,27 @@ class Var:
             A var representing range operation.
             A var representing range operation.
 
 
         Raises:
         Raises:
-            TypeError: If the var is not an int.
+            VarTypeError: If the var is not an int.
         """
         """
         if not isinstance(v1, Var):
         if not isinstance(v1, Var):
             v1 = Var.create_safe(v1)
             v1 = Var.create_safe(v1)
         if v1._var_type != int:
         if v1._var_type != int:
-            raise TypeError(f"Cannot get range on non-int var {v1._var_full_name}.")
+            raise VarTypeError(f"Cannot get range on non-int var {v1._var_full_name}.")
         if not isinstance(v2, Var):
         if not isinstance(v2, Var):
             v2 = Var.create(v2)
             v2 = Var.create(v2)
         if v2 is None:
         if v2 is None:
             v2 = Var.create_safe("undefined")
             v2 = Var.create_safe("undefined")
         elif v2._var_type != int:
         elif v2._var_type != int:
-            raise TypeError(f"Cannot get range on non-int var {v2._var_full_name}.")
+            raise VarTypeError(f"Cannot get range on non-int var {v2._var_full_name}.")
 
 
         if not isinstance(step, Var):
         if not isinstance(step, Var):
             step = Var.create(step)
             step = Var.create(step)
         if step is None:
         if step is None:
             step = Var.create_safe(1)
             step = Var.create_safe(1)
         elif step._var_type != int:
         elif step._var_type != int:
-            raise TypeError(f"Cannot get range on non-int var {step._var_full_name}.")
+            raise VarTypeError(
+                f"Cannot get range on non-int var {step._var_full_name}."
+            )
 
 
         return BaseVar(
         return BaseVar(
             _var_name=f"Array.from(range({v1._var_full_name}, {v2._var_full_name}, {step._var_name}))",
             _var_name=f"Array.from(range({v1._var_full_name}, {v2._var_full_name}, {step._var_name}))",
@@ -1919,7 +1924,7 @@ class ComputedVar(Var, property):
             A set of variable names accessed by the given obj.
             A set of variable names accessed by the given obj.
 
 
         Raises:
         Raises:
-            ValueError: if the function references the get_state, parent_state, or substates attributes
+            VarValueError: if the function references the get_state, parent_state, or substates attributes
                 (cannot track deps in a related state, only implicitly via parent state).
                 (cannot track deps in a related state, only implicitly via parent state).
         """
         """
         d = set()
         d = set()
@@ -1966,7 +1971,7 @@ class ComputedVar(Var, property):
                 except Exception:
                 except Exception:
                     ref_obj = None
                     ref_obj = None
                 if instruction.argval in invalid_names:
                 if instruction.argval in invalid_names:
-                    raise ValueError(
+                    raise VarValueError(
                         f"Cached var {self._var_full_name} cannot access arbitrary state via `{instruction.argval}`."
                         f"Cached var {self._var_full_name} cannot access arbitrary state via `{instruction.argval}`."
                     )
                     )
                 if callable(ref_obj):
                 if callable(ref_obj):