Skip to content

Commit

Permalink
Improve event processing performance (#163)
Browse files Browse the repository at this point in the history
  • Loading branch information
picklelo authored Dec 22, 2022
1 parent 429b212 commit 974d2b4
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 162 deletions.
4 changes: 2 additions & 2 deletions pynecone/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@
# 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.
Expand Down
75 changes: 15 additions & 60 deletions pynecone/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,61 +257,20 @@ def _set_default_value(cls, prop: BaseVar):
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.
Args:
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."""
Expand Down Expand Up @@ -358,11 +317,10 @@ async def process(self, event: Event) -> StateUpdate:
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.
Expand All @@ -383,10 +341,10 @@ async def process(self, event: Event) -> StateUpdate:
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)
Expand All @@ -397,22 +355,20 @@ def get_delta(self) -> Delta:
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)
Expand All @@ -428,14 +384,13 @@ def mark_dirty(self):

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.
Expand Down
2 changes: 1 addition & 1 deletion pynecone/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
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
Expand Down
10 changes: 5 additions & 5 deletions tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]):
Expand All @@ -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
Expand All @@ -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
115 changes: 21 additions & 94 deletions tests/test_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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
)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 == ""
Expand Down Expand Up @@ -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": ""}}

0 comments on commit 974d2b4

Please sign in to comment.