Explorar o código

Add better error messages (#18)

Nikhil Rao %!s(int64=2) %!d(string=hai) anos
pai
achega
f0355e7f39
Modificáronse 4 ficheiros con 49 adicións e 4 borrados
  1. 13 0
      pynecone/components/component.py
  2. 11 1
      pynecone/state.py
  3. 24 2
      pynecone/utils.py
  4. 1 1
      pyproject.toml

+ 13 - 0
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]

+ 11 - 1
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)

+ 24 - 2
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
 
 

+ 1 - 1
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"