From 974d2b4cbb7e47e52dd712ce4b4ef55e8b98046d Mon Sep 17 00:00:00 2001 From: Nikhil Rao Date: Wed, 21 Dec 2022 20:04:13 -0800 Subject: [PATCH] Improve event processing performance (#163) --- pynecone/constants.py | 4 +- pynecone/state.py | 75 ++++++--------------------- pynecone/utils.py | 2 +- tests/test_app.py | 10 ++-- tests/test_state.py | 115 ++++++++---------------------------------- 5 files changed, 44 insertions(+), 162 deletions(-) diff --git a/pynecone/constants.py b/pynecone/constants.py index e21b6bbf4..881b9c82b 100644 --- a/pynecone/constants.py +++ b/pynecone/constants.py @@ -59,11 +59,11 @@ BUN_PATH = "$HOME/.bun/bin/bun" # Command to install bun. INSTALL_BUN = "curl https://bun.sh/install | bash" # Command to run the backend in dev mode. -RUN_BACKEND = "uvicorn --log-level critical --reload --host 0.0.0.0".split() +RUN_BACKEND = "uvicorn --log-level debug --reload --host 0.0.0.0".split() # The default timeout when launching the gunicorn server. TIMEOUT = 120 # The command to run the backend in production mode. -RUN_BACKEND_PROD = f"gunicorn --worker-class uvicorn.workers.UvicornH11Worker --bind 0.0.0.0:8000 --preload --timeout {TIMEOUT} --log-level debug".split() +RUN_BACKEND_PROD = f"gunicorn --worker-class uvicorn.workers.UvicornH11Worker --bind 0.0.0.0:8000 --preload --timeout {TIMEOUT} --log-level critical".split() # Compiler variables. # The extension for compiled Javascript files. diff --git a/pynecone/state.py b/pynecone/state.py index 57a9d78b0..6aa985a09 100644 --- a/pynecone/state.py +++ b/pynecone/state.py @@ -257,40 +257,6 @@ class State(Base, ABC): field.required = False field.default = default_value - def getattr(self, name: str) -> Any: - """Get a non-prop attribute. - - Args: - name: The name of the attribute. - - Returns: - The attribute. - """ - return super().__getattribute__(name) - - def __getattribute__(self, name: str) -> Any: - """Get the attribute. - - Args: - name: The name of the attribute. - - Returns: - The attribute. - - Raises: - Exception: If the attribute is not found. - """ - # If it is an inherited var, return from the parent state. - if name != "inherited_vars" and name in self.inherited_vars: - return getattr(self.parent_state, name) - try: - return super().__getattribute__(name) - except Exception as e: - # Check if the attribute is a substate. - if name in self.substates: - return self.substates[name] - raise e - def __setattr__(self, name: str, value: Any): """Set the attribute. @@ -298,20 +264,13 @@ class State(Base, ABC): name: The name of the attribute. value: The value of the attribute. """ - # NOTE: We use super().__getattribute__ for performance reasons. - if name != "inherited_vars" and name in super().__getattribute__( - "inherited_vars" - ): - setattr(super().__getattribute__("parent_state"), name, value) - return - # Set the attribute. super().__setattr__(name, value) # Add the var to the dirty list. - if name in super().__getattribute__("vars"): - super().__getattribute__("dirty_vars").add(name) - super().__getattribute__("mark_dirty")() + if name in self.vars: + self.dirty_vars.add(name) + self.mark_dirty() def reset(self): """Reset all the base vars to their default values.""" @@ -358,11 +317,10 @@ class State(Base, ABC): Returns: The state update after processing the event. """ - # NOTE: We use super().__getattribute__ for performance reasons. # Get the event handler. path = event.name.split(".") path, name = path[:-1], path[-1] - substate = super().__getattribute__("get_substate")(path) + substate = self.get_substate(path) handler = getattr(substate, name) # Process the event. @@ -383,10 +341,10 @@ class State(Base, ABC): events = utils.fix_events(events, event.token) # Get the delta after processing the event. - delta = super().__getattribute__("get_delta")() + delta = self.get_delta() # Reset the dirty vars. - super().__getattribute__("clean")() + self.clean() # Return the state update. return StateUpdate(delta=delta, events=events) @@ -397,22 +355,20 @@ class State(Base, ABC): Returns: The delta for the state. """ - # NOTE: We use super().__getattribute__ for performance reasons. delta = {} # Return the dirty vars, as well as all computed vars. subdelta = { prop: getattr(self, prop) - for prop in super().__getattribute__("dirty_vars") - | set(super().__getattribute__("computed_vars").keys()) + for prop in self.dirty_vars | self.computed_vars.keys() } if len(subdelta) > 0: - delta[super().__getattribute__("get_full_name")()] = subdelta + delta[self.get_full_name()] = subdelta # Recursively find the substate deltas. - substates = super().__getattribute__("substates") - for substate in super().__getattribute__("dirty_substates"): - delta.update(substates[substate].getattr("get_delta")()) + substates = self.substates + for substate in self.dirty_substates: + delta.update(substates[substate].get_delta()) # Format the delta. delta = utils.format_state(delta) @@ -428,14 +384,13 @@ class State(Base, ABC): def clean(self): """Reset the dirty vars.""" - # NOTE: We use super().__getattribute__ for performance reasons. # Recursively clean the substates. - for substate in super().__getattribute__("dirty_substates"): - super().__getattribute__("substates")[substate].getattr("clean")() + for substate in self.dirty_substates: + self.substates[substate].clean() # Clean this state. - super().__setattr__("dirty_vars", set()) - super().__setattr__("dirty_substates", set()) + self.dirty_vars = set() + self.dirty_substates = set() def dict(self, include_computed: bool = True, **kwargs) -> Dict[str, Any]: """Convert the object to a dictionary. diff --git a/pynecone/utils.py b/pynecone/utils.py index 5b1dfe4c2..34e7dc04c 100644 --- a/pynecone/utils.py +++ b/pynecone/utils.py @@ -28,9 +28,9 @@ from typing import ( Type, Union, ) -import typer import plotly.graph_objects as go +import typer from plotly.io import to_json from redis import Redis from rich.console import Console diff --git a/tests/test_app.py b/tests/test_app.py index 6acfee94d..17c895f42 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -124,7 +124,7 @@ def test_initialize_with_state(TestState: Type[State]): token = "token" state = app.get_state(token) assert isinstance(state, TestState) - assert state.var == 0 + assert state.var == 0 # type: ignore def test_set_and_get_state(TestState: Type[State]): @@ -142,8 +142,8 @@ def test_set_and_get_state(TestState: Type[State]): # Get the default state for each token. state1 = app.get_state(token1) state2 = app.get_state(token2) - assert state1.var == 0 - assert state2.var == 0 + assert state1.var == 0 # type: ignore + assert state2.var == 0 # type: ignore # Set the vars to different values. state1.var = 1 @@ -154,5 +154,5 @@ def test_set_and_get_state(TestState: Type[State]): # Get the states again and check the values. state1 = app.get_state(token1) state2 = app.get_state(token2) - assert state1.var == 1 - assert state2.var == 2 + assert state1.var == 1 # type: ignore + assert state2.var == 2 # type: ignore diff --git a/tests/test_state.py b/tests/test_state.py index 751476465..3ca9d2381 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -411,12 +411,15 @@ def test_get_child_attribute(TestState, ChildState, ChildState2, GrandchildState GrandchildState: The grandchild state class. """ test_state = TestState() + child_state = test_state.get_substate(["child_state"]) + child_state2 = test_state.get_substate(["child_state2"]) + grandchild_state = child_state.get_substate(["grandchild_state"]) assert test_state.num1 == 0 - assert test_state.child_state.value == "" - assert test_state.child_state2.value == "" - assert test_state.child_state.count == 23 - assert test_state.child_state.grandchild_state.value2 == "" + assert child_state.value == "" + assert child_state2.value == "" + assert child_state.count == 23 + assert grandchild_state.value2 == "" with pytest.raises(AttributeError): test_state.invalid with pytest.raises(AttributeError): @@ -435,77 +438,15 @@ def test_set_child_attribute(TestState, ChildState, ChildState2, GrandchildState GrandchildState: The grandchild state class. """ test_state = TestState() - child_state = test_state.child_state - grandchild_state = child_state.grandchild_state + child_state = test_state.get_substate(["child_state"]) + grandchild_state = child_state.get_substate(["grandchild_state"]) test_state.num1 = 10 assert test_state.num1 == 10 - test_state.child_state.value = "test" - assert test_state.child_state.value == "test" - assert child_state.value == "test" - - test_state.child_state.grandchild_state.value2 = "test2" - assert test_state.child_state.grandchild_state.value2 == "test2" - assert child_state.grandchild_state.value2 == "test2" - assert grandchild_state.value2 == "test2" - - -def test_get_parent_attribute(TestState, ChildState, ChildState2, GrandchildState): - """Test setting the attribute of a state. - - Args: - TestState: The state class. - ChildState: The child state class. - ChildState2: The child state class. - GrandchildState: The grandchild state class. - """ - test_state = TestState() - child_state = test_state.child_state - grandchild_state = child_state.grandchild_state - - assert test_state.num1 == 0 - assert child_state.num1 == 0 - assert grandchild_state.num1 == 0 - - # Changing the parent var should change the child var. - test_state.num1 = 1 - assert test_state.num1 == 1 - assert child_state.num1 == 1 - assert grandchild_state.num1 == 1 - child_state.value = "test" - assert test_state.child_state.value == "test" assert child_state.value == "test" - assert grandchild_state.value == "test" - - -def test_set_parent_attribute(TestState, ChildState, ChildState2, GrandchildState): - """Test setting the attribute of a state. - - Args: - TestState: The state class. - ChildState: The child state class. - ChildState2: The child state class. - GrandchildState: The grandchild state class. - """ - test_state = TestState() - child_state = test_state.child_state - grandchild_state = child_state.grandchild_state - - # Changing the child var should not change the parent var. - child_state.num1 = 2 - assert child_state.num1 == 2 - assert test_state.num1 == 2 - assert grandchild_state.num1 == 2 - - grandchild_state.num1 = 3 - assert grandchild_state.num1 == 3 - assert child_state.num1 == 3 - assert test_state.num1 == 3 - - grandchild_state.value = "test2" - assert grandchild_state.value == "test2" - assert child_state.value == "test2" + grandchild_state.value2 = "test2" + assert grandchild_state.value2 == "test2" def test_get_substate(TestState, ChildState, ChildState2, GrandchildState): @@ -518,11 +459,12 @@ def test_get_substate(TestState, ChildState, ChildState2, GrandchildState): GrandchildState: The grandchild state class. """ test_state = TestState() - child_state = test_state.child_state - grandchild_state = child_state.grandchild_state + child_state = test_state.substates["child_state"] + child_state2 = test_state.substates["child_state2"] + grandchild_state = child_state.substates["grandchild_state"] assert test_state.get_substate(("child_state",)) == child_state - assert test_state.get_substate(("child_state2",)) == test_state.child_state2 + assert test_state.get_substate(("child_state2",)) == child_state2 assert ( test_state.get_substate(("child_state", "grandchild_state")) == grandchild_state ) @@ -569,9 +511,9 @@ def test_set_dirty_substate(TestState, ChildState, ChildState2, GrandchildState) GrandchildState: The grandchild state class. """ test_state = TestState() - child_state = test_state.child_state - child_state2 = test_state.child_state2 - grandchild_state = child_state.grandchild_state + child_state = test_state.get_substate(["child_state"]) + child_state2 = test_state.get_substate(["child_state2"]) + grandchild_state = child_state.get_substate(["grandchild_state"]) # Initially there should be no dirty vars. assert test_state.dirty_vars == set() @@ -610,7 +552,7 @@ def test_reset(TestState, ChildState): ChildState: The child state class. """ test_state = TestState() - child_state = test_state.child_state + child_state = test_state.get_substate(["child_state"]) # Set some values. test_state.num1 = 1 @@ -664,8 +606,8 @@ async def test_process_event_substate(TestState, ChildState, GrandchildState): GrandchildState: The grandchild state class. """ test_state = TestState() - child_state = test_state.child_state - grandchild_state = child_state.grandchild_state + child_state = test_state.get_substate(["child_state"]) + grandchild_state = child_state.get_substate(["grandchild_state"]) # Events should bubble down to the substate. assert child_state.value == "" @@ -695,18 +637,3 @@ async def test_process_event_substate(TestState, ChildState, GrandchildState): "test_state.child_state.grandchild_state": {"value2": "new"}, "test_state": {"sum": 3.14, "upper": ""}, } - - -@pytest.mark.asyncio -async def test_process_event_substate_set_parent_state(TestState, ChildState): - """Test setting the parent state on a substate. - - Args: - TestState: The state class. - ChildState: The child state class. - """ - test_state = TestState() - event = Event(token="t", name="child_state.set_num1", payload={"value": 69}) - update = await test_state.process(event) - assert test_state.num1 == 69 - assert update.delta == {"test_state": {"num1": 69, "sum": 72.14, "upper": ""}}