[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. 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. UploadValueError: if there are no args with supported annotation.
TypeError: if a background task is used as the handler. 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]"
) )

View File

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

View File

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

View File

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

View File

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

View File

@ -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 prop_name: (
if isinstance(cv, ComputedVar) cv._initial_value
and not isinstance(cv._initial_value, types.Unset) if isinstance(cv, ComputedVar)
else self.get_value(getattr(self, prop_name)) 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:

View File

@ -1,21 +1,77 @@
"""Custom Exceptions.""" """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.""" """Custom Type Error when style props have invalid values."""
pass
class ImmutableStateError(ReflexError):
class ImmutableStateError(AttributeError):
"""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

View File

@ -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) from reflex.utils import exceptions, telemetry
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: try:
from reflex.state import reload_state_module 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. if reload:
reload_state_module(module=module) from reflex.state import reload_state_module
# Reload the app module. # Reset rx.State subclasses to avoid conflict when reloading.
importlib.reload(app) 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: 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 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. VarAttributeError: If the var is wrongly annotated or can't find attribute.
TypeError: If an annotation to the var isn't provided. 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. VarTypeError: If the operation between two operands is invalid.
ValueError: If flip is set to true and value of operand is not provided 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):