Przeglądaj źródła

Add is_dataframe check.

Nikhil Rao 2 lat temu
rodzic
commit
03d0aca366

+ 1 - 1
.gitignore

@@ -7,4 +7,4 @@
 bun.lockb
 poetry.lock
 dist/*
-pynetree/
+examples/

+ 1 - 1
pynecone/app.py

@@ -196,7 +196,7 @@ class App(Base):
                 v = BaseVar(
                     name=match.groups()[0],
                     type_=str,
-                    state="router.query",
+                    state=f"{constants.ROUTER}.query",
                 )
                 args.append(v)
 

+ 1 - 1
pynecone/components/component.py

@@ -224,7 +224,7 @@ class Component(Base, ABC):
 
         # Add component props to the tag.
         props = {attr: getattr(self, attr) for attr in self.get_props()}
-        
+
         # Special case for props named `type_`.
         if hasattr(self, "type_"):
             props["type"] = getattr(self, "type_")

+ 3 - 2
pynecone/components/datadisplay/datatable.py

@@ -1,7 +1,8 @@
 """Table components."""
 
-from typing import Any, Dict, List, Union
+from typing import Any, List
 
+from pynecone import utils
 from pynecone.components.component import Component
 from pynecone.components.tags import Tag
 from pynecone.var import Var
@@ -44,7 +45,7 @@ import "gridjs/dist/theme/mermaid.css";
 """
 
     def _render(self) -> Tag:
-        if type(self.data).__name__ == "DataFrame":
+        if utils.is_dataframe(type(self.data)):
             self.columns = Var.create(list(self.data.columns.values.tolist()))  # type: ignore
             self.data = Var.create(list(self.data.values.tolist()))  # type: ignore
 

+ 2 - 0
pynecone/constants.py

@@ -94,6 +94,8 @@ CONFIG_MODULE = "pcconfig"
 CONFIG_FILE = f"{CONFIG_MODULE}.{PY_EXT}"
 # The deployment URL.
 PRODUCTION_BACKEND_URL = "https://{username}-{app_name}.api.pynecone.app"
+# Token expiration time in seconds.
+TOKEN_EXPIRATION = 60 * 60
 
 
 # Env modes

+ 19 - 33
pynecone/state.py

@@ -8,7 +8,7 @@ import traceback
 from abc import ABC
 from typing import Any, Callable, ClassVar, Dict, List, Optional, Sequence, Set, Type
 
-from pynecone import utils
+from pynecone import constants, utils
 from pynecone.base import Base
 from pynecone.event import Event, EventHandler, EventSpec, window_alert
 from pynecone.var import BaseVar, ComputedVar, Var
@@ -320,35 +320,6 @@ class State(Base, ABC):
         Returns:
             The state update after processing the event.
         """
-
-        def fix_events(events):
-            if events is None:
-                return []
-            if not isinstance(events, List):
-                events = [events]
-            out = []
-            for e in events:
-                if isinstance(e, Event):
-                    out.append(
-                        Event(
-                            token=event.token,
-                            name=e.name,
-                            payload=e.payload,
-                        )
-                    )
-                else:
-                    if isinstance(e, EventHandler):
-                        e = e()
-                    assert isinstance(e, EventSpec)
-                    out.append(
-                        Event(
-                            token=event.token,
-                            name=utils.to_snake_case(e.handler.fn.__qualname__),
-                            payload=dict(e.args),
-                        )
-                    )
-            return out
-
         # Get the event handler.
         path = event.name.split(".")
         path, name = path[:-1], path[-1]
@@ -367,10 +338,16 @@ class State(Base, ABC):
             print(error)
             return StateUpdate(events=[window_alert(error)])
 
-        # Return the substate and the delta.
-        events = fix_events(events)
+        # Fix the returned events.
+        events = utils.fix_events(events, event.token)
+
+        # Get the delta after processing the event.
         delta = self.get_delta()
+
+        # Reset the dirty vars.
         self.clean()
+
+        # Return the state update.
         return StateUpdate(delta=delta, events=events)
 
     def get_delta(self) -> Delta:
@@ -379,15 +356,21 @@ class State(Base, ABC):
         Returns:
             The delta for the state.
         """
+        # Return the dirty vars, as well as all computed vars.
         delta = {
             self.get_full_name(): {
                 prop: getattr(self, prop)
                 for prop in self.dirty_vars | set(self.computed_vars.keys())
             }
         }
+        # Recursively find the substate deltas.
         for substate in self.dirty_substates:
             delta.update(self.substates[substate].get_delta())
+
+        # Format the delta.
         delta = utils.format_state(delta)
+
+        # Return the delta.
         return delta
 
     def mark_dirty(self):
@@ -398,8 +381,11 @@ class State(Base, ABC):
 
     def clean(self):
         """Reset the dirty vars."""
+        # Recursively clean the substates.
         for substate in self.dirty_substates:
             self.substates[substate].clean()
+
+        # Clean this state.
         self.dirty_vars = set()
         self.dirty_substates = set()
 
@@ -463,7 +449,7 @@ class StateManager(Base):
     states: Dict[str, State] = {}
 
     # The token expiration time (s).
-    token_expiration: int = 60 * 60
+    token_expiration: int = constants.TOKEN_EXPIRATION
 
     def __init__(self, *args, **kwargs):
         """Initialize the state manager.

+ 81 - 3
pynecone/utils.py

@@ -36,7 +36,7 @@ from pynecone import constants
 
 if TYPE_CHECKING:
     from pynecone.components.component import ImportDict
-    from pynecone.event import EventHandler, EventSpec
+    from pynecone.event import Event, EventHandler, EventSpec
     from pynecone.var import Var
 
 
@@ -621,6 +621,18 @@ def get_default_app_name() -> str:
     return os.getcwd().split(os.path.sep)[-1].replace("-", "_")
 
 
+def is_dataframe(value: Type) -> bool:
+    """Check if the given value is a dataframe.
+
+    Args:
+        value: The value to check.
+
+    Returns:
+        Whether the value is a dataframe.
+    """
+    return value.__name__ == "DataFrame"
+
+
 def format_state(value: Dict) -> Dict:
     """Recursively format values in the given state.
 
@@ -632,9 +644,8 @@ def format_state(value: Dict) -> Dict:
     """
     if isinstance(value, go.Figure):
         return json.loads(to_json(value))["data"]
-    import pandas as pd
 
-    if isinstance(value, pd.DataFrame):
+    if is_dataframe(type(value)):
         return {
             "columns": value.columns.tolist(),
             "data": value.values.tolist(),
@@ -657,6 +668,26 @@ def get_event(state, event):
     return f"{state.get_name()}.{event}"
 
 
+def format_string(string: str) -> str:
+    """Format the given string as a JS string literal..
+
+    Args:
+        string: The string to format.
+
+    Returns:
+        The formatted string.
+    """
+    # Escale backticks.
+    string = string.replace("\`", "`")  # type: ignore
+    string = string.replace("`", "\`")  # type: ignore
+
+    # Wrap the string so it looks like {`string`}.
+    string = wrap(string, "`")
+    string = wrap(string, "{")
+
+    return string
+
+
 def call_event_handler(event_handler: EventHandler, arg: Var) -> EventSpec:
     """Call an event handler to get the event spec.
 
@@ -723,6 +754,53 @@ def get_handler_args(event_spec: EventSpec, arg: Var) -> Tuple[Tuple[str, str],
         return ((args[1], arg.name),)
 
 
+def fix_events(events: Optional[List[Event]], token: str) -> List[Event]:
+    """Fix a list of events returned by an event handler.
+
+    Args:
+        events: The events to fix.
+        token: The user token.
+
+    Returns:
+        The fixed events.
+    """
+    # If the event handler returns nothing, return an empty list.
+    if events is None:
+        return []
+
+    # If the handler returns a single event, wrap it in a list.
+    if not isinstance(events, List):
+        events = [events]
+
+    # Fix the events created by the handler.
+    out = []
+    for e in events:
+
+        # If it is already an event, don't modify it.
+        if isinstance(e, Event):
+            name = e.name
+            payload = e.payload
+
+        # Otherwise, create an event from the event spec.
+        else:
+            if isinstance(e, EventHandler):
+                e = e()
+            assert isinstance(e, EventSpec), f"Unexpected event type, {type(e)}."
+            name = to_snake_case(e.handler.fn.__qualname__)
+            payload = dict(e.args)
+
+        # Create an event and append it to the list.
+        out.append(
+            Event(
+                token=token,
+                name=name,
+                payload=payload,
+            )
+        )
+
+    return out
+
+
 def merge_imports(*imports) -> ImportDict:
     """Merge two import dicts together.
 

+ 2 - 6
pynecone/var.py

@@ -123,10 +123,7 @@ class Var(ABC):
         else:
             out = utils.wrap(self.full_name, "{")
         if self.is_string:
-            out = out.replace("\`", "`")  # type: ignore
-            out = out.replace("`", "\`")  # type: ignore
-            out = utils.wrap(out, "`")
-            out = utils.wrap(out, "{")
+            out = utils.format_string(out)
         return out
 
     def __getitem__(self, i) -> Var:
@@ -140,7 +137,6 @@ class Var(ABC):
         """
         # The type of the indexed var.
         type_ = str
-        import pandas as pd
 
         # Convert any vars to local vars.
         if isinstance(i, Var):
@@ -154,7 +150,7 @@ class Var(ABC):
                 type_ = utils.get_args(self.type_)[0]
             else:
                 type_ = Any
-        elif utils._issubclass(self.type_, Union[dict, pd.DataFrame]):
+        elif utils.is_dataframe(self.type_):
             if isinstance(i, str):
                 i = utils.wrap(i, '"')
             if isinstance(self.type_, _GenericAlias):