فهرست منبع

Add is_dataframe check.

Nikhil Rao 2 سال پیش
والد
کامیت
03d0aca366
8فایلهای تغییر یافته به همراه110 افزوده شده و 47 حذف شده
  1. 1 1
      .gitignore
  2. 1 1
      pynecone/app.py
  3. 1 1
      pynecone/components/component.py
  4. 3 2
      pynecone/components/datadisplay/datatable.py
  5. 2 0
      pynecone/constants.py
  6. 19 33
      pynecone/state.py
  7. 81 3
      pynecone/utils.py
  8. 2 6
      pynecone/var.py

+ 1 - 1
.gitignore

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

+ 1 - 1
pynecone/app.py

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

+ 1 - 1
pynecone/components/component.py

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

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

@@ -1,7 +1,8 @@
 """Table components."""
 """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.component import Component
 from pynecone.components.tags import Tag
 from pynecone.components.tags import Tag
 from pynecone.var import Var
 from pynecone.var import Var
@@ -44,7 +45,7 @@ import "gridjs/dist/theme/mermaid.css";
 """
 """
 
 
     def _render(self) -> Tag:
     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.columns = Var.create(list(self.data.columns.values.tolist()))  # type: ignore
             self.data = Var.create(list(self.data.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}"
 CONFIG_FILE = f"{CONFIG_MODULE}.{PY_EXT}"
 # The deployment URL.
 # The deployment URL.
 PRODUCTION_BACKEND_URL = "https://{username}-{app_name}.api.pynecone.app"
 PRODUCTION_BACKEND_URL = "https://{username}-{app_name}.api.pynecone.app"
+# Token expiration time in seconds.
+TOKEN_EXPIRATION = 60 * 60
 
 
 
 
 # Env modes
 # Env modes

+ 19 - 33
pynecone/state.py

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

+ 81 - 3
pynecone/utils.py

@@ -36,7 +36,7 @@ from pynecone import constants
 
 
 if TYPE_CHECKING:
 if TYPE_CHECKING:
     from pynecone.components.component import ImportDict
     from pynecone.components.component import ImportDict
-    from pynecone.event import EventHandler, EventSpec
+    from pynecone.event import Event, EventHandler, EventSpec
     from pynecone.var import Var
     from pynecone.var import Var
 
 
 
 
@@ -621,6 +621,18 @@ def get_default_app_name() -> str:
     return os.getcwd().split(os.path.sep)[-1].replace("-", "_")
     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:
 def format_state(value: Dict) -> Dict:
     """Recursively format values in the given state.
     """Recursively format values in the given state.
 
 
@@ -632,9 +644,8 @@ def format_state(value: Dict) -> Dict:
     """
     """
     if isinstance(value, go.Figure):
     if isinstance(value, go.Figure):
         return json.loads(to_json(value))["data"]
         return json.loads(to_json(value))["data"]
-    import pandas as pd
 
 
-    if isinstance(value, pd.DataFrame):
+    if is_dataframe(type(value)):
         return {
         return {
             "columns": value.columns.tolist(),
             "columns": value.columns.tolist(),
             "data": value.values.tolist(),
             "data": value.values.tolist(),
@@ -657,6 +668,26 @@ def get_event(state, event):
     return f"{state.get_name()}.{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:
 def call_event_handler(event_handler: EventHandler, arg: Var) -> EventSpec:
     """Call an event handler to get the event spec.
     """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),)
         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:
 def merge_imports(*imports) -> ImportDict:
     """Merge two import dicts together.
     """Merge two import dicts together.
 
 

+ 2 - 6
pynecone/var.py

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