Browse Source

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

Martin Xu 1 year ago
parent
commit
2789f32134
9 changed files with 214 additions and 112 deletions
  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.
 
         Raises:
+            VarOperationTypeError: When an invalid component var related 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.
         """
+        from reflex.utils.exceptions import VarOperationTypeError
+
         try:
             return component if isinstance(component, Component) else component()
         except exceptions.MatchTypeError:
@@ -413,7 +416,7 @@ class App(Base):
         except TypeError as e:
             message = str(e)
             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. "
                     "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/"
@@ -555,11 +558,13 @@ class App(Base):
         Based on conflicts that NextJS would throw if not intercepted.
 
         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:
             new_route: the route being newly added.
         """
+        from reflex.utils.exceptions import RouteValueError
+
         if "[" not in new_route:
             return
 
@@ -576,7 +581,7 @@ class App(Base):
             ):
                 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
-                    raise ValueError(
+                    raise RouteValueError(
                         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:
@@ -755,8 +760,10 @@ class App(Base):
             export: Whether to compile the app for export.
 
         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
         if constants.Page404.SLUG not in self.pages:
             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.
         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 "
                 "subclass of rx.State must be defined in the app."
             )
@@ -1137,10 +1144,12 @@ def upload(app: App):
             emitted by the upload handler.
 
         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.
         """
+        from reflex.utils.exceptions import UploadTypeError, UploadValueError
+
         token = request.headers.get("reflex-client-token")
         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]
         if isinstance(func, EventHandler):
             if func.is_background:
-                raise TypeError(
+                raise UploadTypeError(
                     f"@rx.background is not supported for upload handler `{handler}`.",
                 )
             func = func.fn
@@ -1181,7 +1190,7 @@ def upload(app: App):
                 break
 
         if not handler_upload_param:
-            raise ValueError(
+            raise UploadValueError(
                 f"`{handler}` handler should have a parameter annotated as "
                 "List[rx.UploadFile]"
             )

+ 5 - 2
reflex/base.py

@@ -1,4 +1,5 @@
 """Define the base Reflex class."""
+
 from __future__ import annotations
 
 import os
@@ -26,15 +27,17 @@ def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None
         field_name: name of attribute
 
     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"
     for base in bases:
         try:
             if not reload and getattr(base, field_name, None):
                 pass
         except TypeError as te:
-            raise NameError(
+            raise VarNameError(
                 f'State var "{field_name}" in {base} has been shadowed by a substate var; '
                 f'use a different field name instead".'
             ) from te

+ 2 - 1
reflex/components/component.py

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

+ 5 - 3
reflex/config.py

@@ -251,8 +251,10 @@ class Config(Base):
             The updated config values.
 
         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 = {}
         # Iterate over the fields.
         for key, field in self.__fields__.items():
@@ -273,11 +275,11 @@ class Config(Base):
                         env_var = env_var.lower() in ["true", "1", "yes"]
                     else:
                         env_var = field.type_(env_var)
-                except ValueError:
+                except ValueError as ve:
                     console.error(
                         f"Could not convert {key.upper()}={env_var} to type {field.type_}"
                     )
-                    raise
+                    raise EnvVarValueError from ve
 
                 # Set the value.
                 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.
 
         Raises:
-            TypeError: If the arguments are invalid.
+            EventHandlerTypeError: If the arguments are invalid.
         """
+        from reflex.utils.exceptions import EventHandlerTypeError
+
         # Get the function args.
         fn_args = inspect.getfullargspec(self.fn).args[1:]
         fn_args = (Var.create_safe(arg) for arg in fn_args)
@@ -197,7 +199,7 @@ class EventHandler(EventActionsMixin):
             try:
                 values.append(Var.create(arg, _var_is_string=isinstance(arg, str)))
             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)}."
                 ) from e
         payload = tuple(zip(fn_args, values))
@@ -256,8 +258,10 @@ class EventSpec(EventActionsMixin):
             The event spec with the new arguments.
 
         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.
         fn_args = inspect.getfullargspec(self.handler.fn).args[1 + len(self.args) :]
         fn_args = (Var.create_safe(arg) for arg in fn_args)
@@ -268,7 +272,7 @@ class EventSpec(EventActionsMixin):
             try:
                 values.append(Var.create(arg, _var_is_string=isinstance(arg, str)))
             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)}."
                 ) from e
         new_payload = tuple(zip(fn_args, values))
@@ -312,10 +316,12 @@ class CallableEventSpec(EventSpec):
             The EventSpec returned from calling the function.
 
         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:
-            raise TypeError("CallableEventSpec has no associated function.")
+            raise EventHandlerTypeError("CallableEventSpec has no associated function.")
         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.
 
     Raises:
-        ValueError: If the lambda has an invalid signature.
+        EventHandlerValueError: If the lambda has an invalid signature.
     """
     # Import here to avoid circular imports.
     from reflex.event import EventHandler, EventSpec
+    from reflex.utils.exceptions import EventHandlerValueError
 
     # Get the args of the lambda.
     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:
             out = fn(arg)
         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.
     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.
         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.
         events.append(e)

+ 22 - 12
reflex/state.py

@@ -279,11 +279,13 @@ class EventHandlerSetVar(EventHandler):
 
         Raises:
             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 not isinstance(args[0], str):
-                raise ValueError(
+                raise EventHandlerValueError(
                     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.
@@ -380,10 +382,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             **kwargs: The kwargs to pass to the Pydantic init method.
 
         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():
-            raise RuntimeError(
+            raise ReflexRuntimeError(
                 "State classes should not be instantiated directly in a Reflex app. "
                 "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.
 
         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)
         # Event handlers should not shadow builtin state methods.
         cls._check_overridden_methods()
@@ -471,7 +477,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
                 else:
                     # During normal operation, subclasses cannot have the same name, even if they are
                     # defined in different modules.
-                    raise ValueError(
+                    raise StateValueError(
                         f"The substate class '{cls.__name__}' has been defined multiple times. "
                         "Shadowing substate classes is not allowed."
                     )
@@ -829,10 +835,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             prop: The variable to initialize
 
         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):
-            raise TypeError(
+            raise VarTypeError(
                 "State vars must be primitive Python types, "
                 "Plotly figures, Pandas dataframes, "
                 "or subclasses of rx.Base. "
@@ -1688,10 +1696,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         if 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()
             }
         elif include_computed:

+ 64 - 8
reflex/utils/exceptions.py

@@ -1,21 +1,77 @@
 """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."""
 
 
-class LockExpiredError(Exception):
+class LockExpiredError(ReflexError):
     """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."""
-
-    pass

+ 24 - 17
reflex/utils/prerequisites.py

@@ -210,28 +210,35 @@ def get_app(reload: bool = False) -> ModuleType:
 
     Raises:
         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:

+ 56 - 51
reflex/vars.py

@@ -35,6 +35,7 @@ from typing import (
 from reflex import constants
 from reflex.base import Base
 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
 from reflex.utils.imports import ImportDict, ImportVar
@@ -353,7 +354,7 @@ class Var:
             The var.
 
         Raises:
-            TypeError: If the value is JSON-unserializable.
+            VarTypeError: If the value is JSON-unserializable.
         """
         # Check for none values.
         if value is None:
@@ -372,7 +373,7 @@ class Var:
         type_ = type(value)
         name = value if type_ in types.JSONType else serializers.serialize(value)
         if name is None:
-            raise TypeError(
+            raise VarTypeError(
                 f"No JSON serializer found for var {value} of type {type_}."
             )
         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.
 
         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`. "
             "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.
 
         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`."
         )
 
@@ -584,7 +585,7 @@ class Var:
             The indexed var.
 
         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.
         if not (
@@ -592,11 +593,11 @@ class Var:
             or types.is_dataframe(self._var_type)
         ):
             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, "
                     "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."
             )
 
@@ -615,7 +616,7 @@ class Var:
                 or isinstance(i, Var)
                 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.
             if isinstance(i, slice):
@@ -658,7 +659,7 @@ class Var:
                 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"
             )
         # Get the type of the indexed var.
@@ -687,7 +688,7 @@ class Var:
             The var attribute.
 
         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:
             var_attribute = super().__getattribute__(name)
@@ -698,10 +699,12 @@ class Var:
                     super().__getattribute__("_var_type"), name
                 )
                 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 var_attribute
-        except AttributeError:
+        except VarAttributeError:
             raise  # fall back to __getattr__ anyway
 
     def __getattr__(self, name: str) -> Var:
@@ -714,13 +717,13 @@ class Var:
             The var attribute.
 
         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.
         if not name.startswith("_"):
             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}`"
                 ) from None
             is_optional = types.is_optional(self._var_type)
@@ -734,16 +737,16 @@ class Var:
                 )
 
             if name in REPLACED_NAMES:
-                raise AttributeError(
+                raise VarAttributeError(
                     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"wrongly."
             )
 
-        raise AttributeError(
+        raise VarAttributeError(
             f"The State var has no attribute '{name}' or may have been annotated wrongly.",
         )
 
@@ -770,8 +773,8 @@ class Var:
             The operation result.
 
         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):
             other = Var.create(json.dumps(other))
@@ -781,7 +784,7 @@ class Var:
         type_ = type_ or self._var_type
 
         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"
             )
 
@@ -804,7 +807,7 @@ class Var:
                 types.get_base_class(right_operand._var_type),  # type: ignore
                 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
                 )
 
@@ -925,10 +928,10 @@ class Var:
             A var with the absolute value.
 
         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):
-            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(
             _var_name=f"{self._var_name}.length",
             _var_type=int,
@@ -1328,9 +1331,9 @@ class Var:
         """Override the 'in' operator to alert the user that it is not supported.
 
         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."
         )
 
@@ -1341,13 +1344,13 @@ class Var:
             other: The object to check.
 
         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:
             A var representing the contain check.
         """
         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."
             )
         method = (
@@ -1371,7 +1374,7 @@ class Var:
             if types._issubclass(self._var_type, str) and not types._issubclass(
                 other._var_type, str
             ):
-                raise TypeError(
+                raise VarTypeError(
                     f"'in <string>' requires string as left operand, not {other._var_type}"
                 )
             return self._replace(
@@ -1385,13 +1388,13 @@ class Var:
         """Reverse a list var.
 
         Raises:
-            TypeError: If the var is not a list.
+            VarTypeError: If the var is not a list.
 
         Returns:
             A var with the reversed 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(
             _var_name=f"[...{self._var_full_name}].reverse()",
@@ -1406,10 +1409,10 @@ class Var:
             A var with the lowercase string.
 
         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):
-            raise TypeError(
+            raise VarTypeError(
                 f"Cannot convert non-string var {self._var_full_name} to lowercase."
             )
 
@@ -1426,10 +1429,10 @@ class Var:
             A var with the uppercase string.
 
         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):
-            raise TypeError(
+            raise VarTypeError(
                 f"Cannot convert non-string var {self._var_full_name} to uppercase."
             )
 
@@ -1449,10 +1452,10 @@ class Var:
             A var with the stripped string.
 
         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):
-            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
 
@@ -1472,10 +1475,10 @@ class Var:
             A var with the list.
 
         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):
-            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
 
@@ -1496,10 +1499,10 @@ class Var:
             A var with the string.
 
         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):
-            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:
             other = Var.create_safe('""')
@@ -1525,11 +1528,11 @@ class Var:
             A var representing foreach operation.
 
         Raises:
-            TypeError: If the var is not a list.
+            VarTypeError: If the var is not a list.
         """
         inner_types = get_args(self._var_type)
         if not inner_types:
-            raise TypeError(
+            raise VarTypeError(
                 f"Cannot foreach over non-sequence var {self._var_full_name} of type {self._var_type}."
             )
         arg = BaseVar(
@@ -1566,25 +1569,27 @@ class Var:
             A var representing range operation.
 
         Raises:
-            TypeError: If the var is not an int.
+            VarTypeError: If the var is not an int.
         """
         if not isinstance(v1, Var):
             v1 = Var.create_safe(v1)
         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):
             v2 = Var.create(v2)
         if v2 is None:
             v2 = Var.create_safe("undefined")
         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):
             step = Var.create(step)
         if step is None:
             step = Var.create_safe(1)
         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(
             _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.
 
         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).
         """
         d = set()
@@ -1966,7 +1971,7 @@ class ComputedVar(Var, property):
                 except Exception:
                     ref_obj = None
                 if instruction.argval in invalid_names:
-                    raise ValueError(
+                    raise VarValueError(
                         f"Cached var {self._var_full_name} cannot access arbitrary state via `{instruction.argval}`."
                     )
                 if callable(ref_obj):