From f0355e7f39796318376b3a19c300a90fbae9a92e Mon Sep 17 00:00:00 2001 From: Nikhil Rao Date: Tue, 29 Nov 2022 19:22:48 -0800 Subject: [PATCH] Add better error messages (#18) --- pynecone/components/component.py | 13 +++++++++++++ pynecone/state.py | 12 +++++++++++- pynecone/utils.py | 26 ++++++++++++++++++++++++-- pyproject.toml | 2 +- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/pynecone/components/component.py b/pynecone/components/component.py index 2d44bc527..f670fac6a 100644 --- a/pynecone/components/component.py +++ b/pynecone/components/component.py @@ -253,10 +253,22 @@ class Component(Base, ABC): Returns: The component. + + Raises: + TypeError: If an invalid child is passed. """ # Import here to avoid circular imports. from pynecone.components.base.bare import Bare + # Validate all the children. + for child in children: + if not utils._isinstance(child, ComponentChild): + raise TypeError( + "Children of Pynecone components must be other components, " + "state vars, or primitive Python types. " + f"Got child of type {type(child)}.", + ) + children = [ Bare.create(contents=Var.create(child, is_string=True)) if not isinstance(child, Component) @@ -348,3 +360,4 @@ class Component(Base, ABC): # Map from component to styling. ComponentStyle = Dict[Union[str, Type[Component]], Any] +ComponentChild = Union[utils.PrimitiveType, Var, Component] diff --git a/pynecone/state.py b/pynecone/state.py index 35aa6f559..30b31bacd 100644 --- a/pynecone/state.py +++ b/pynecone/state.py @@ -10,7 +10,7 @@ from typing import Any, Callable, ClassVar, Dict, List, Optional, Sequence, Set, from pynecone import constants, utils from pynecone.base import Base -from pynecone.event import Event, EventHandler, EventSpec, window_alert +from pynecone.event import Event, EventHandler, window_alert from pynecone.var import BaseVar, ComputedVar, Var Delta = Dict[str, Any] @@ -70,6 +70,9 @@ class State(Base, ABC): Args: **kwargs: The kwargs to pass to the pydantic init_subclass method. + + Raises: + TypeError: If the class has a var with an invalid type. """ super().__init_subclass__(**kwargs) @@ -103,6 +106,13 @@ class State(Base, ABC): # Setup the base vars at the class level. for prop in cls.base_vars.values(): + if not utils._issubclass(prop.type_, utils.StateVar): + raise TypeError( + "State vars must be primitive Python types, " + "Plotly figures, Pandas dataframes, " + "or subclasses of pc.Base. " + f'Found var "{prop.name}" with type {prop.type_}.' + ) cls._set_var(prop) cls._create_setter(prop) cls._set_default_value(prop) diff --git a/pynecone/utils.py b/pynecone/utils.py index d71d11e8c..6efd8c66b 100644 --- a/pynecone/utils.py +++ b/pynecone/utils.py @@ -32,6 +32,8 @@ from plotly.io import to_json from rich.console import Console from pynecone import constants +from pynecone.base import Base + if TYPE_CHECKING: from pynecone.components.component import ImportDict @@ -49,6 +51,10 @@ console = Console() # Union of generic types. GenericType = Union[Type, _GenericAlias] +# Valid state var types. +PrimitiveType = Union[int, float, bool, str, list, dict, tuple] +StateVar = Union[PrimitiveType, Base, None] + def get_args(alias: _GenericAlias) -> Tuple[Type, ...]: """Get the arguments of a type alias. @@ -703,17 +709,33 @@ def format_state(value: Any) -> Dict: Returns: The formatted state. + + Raises: + TypeError: If the given value is not a valid state. """ - if isinstance(value, go.Figure): + # Convert plotly figures to JSON. + if _isinstance(value, go.Figure): return json.loads(to_json(value))["data"] + # Convert pandas dataframes to JSON. if is_dataframe(type(value)): return { "columns": value.columns.tolist(), "data": value.values.tolist(), } - if isinstance(value, dict): + + # Handle dicts. + if _isinstance(value, dict): return {k: format_state(v) for k, v in value.items()} + + # Make sure the value is JSON serializable. + if not _isinstance(value, StateVar): + raise TypeError( + "State vars must be primitive Python types, " + "or subclasses of pc.Base. " + f"Got var of type {type(value)}." + ) + return value diff --git a/pyproject.toml b/pyproject.toml index 3517b4fac..4c153b32b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ plotly = "^5.10.0" pydantic = "1.10.2" requests = "^2.28.1" sqlmodel = "^0.0.8" -typer = "^0.7.0" +typer = "0.4.2" uvicorn = "^0.20.0" rich = "^12.6.0" redis = "^4.3.5"