Improve event processing performance (#163)

This commit is contained in:
Nikhil Rao 2022-12-21 20:04:13 -08:00 committed by GitHub
parent 429b21260d
commit 974d2b4cbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 44 additions and 162 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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": ""}}