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

This commit is contained in:
Martin Xu 2024-05-10 12:12:42 -07:00 committed by GitHub
parent 4e959694a1
commit 2789f32134
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 214 additions and 112 deletions

View File

@ -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]"
)

View File

@ -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

View File

@ -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)}.",

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -1,21 +1,77 @@
"""Custom Exceptions."""
class InvalidStylePropError(TypeError):
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."""
class VarNameError(ReflexError, NameError):
"""Custom NameError for when a state var has been shadowed by a substate var."""
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."""
pass
class ImmutableStateError(AttributeError):
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

View File

@ -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
if reload:
from reflex.state import reload_state_module
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,))
# Reset rx.State subclasses to avoid conflict when reloading.
reload_state_module(module=module)
if reload:
from reflex.state import reload_state_module
# Reload the app module.
importlib.reload(app)
# Reset rx.State subclasses to avoid conflict when reloading.
reload_state_module(module=module)
return app
# Reload the app module.
importlib.reload(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:

View File

@ -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):