Improve event processing performance (#163)
This commit is contained in:
parent
429b21260d
commit
974d2b4cbb
@ -59,11 +59,11 @@ BUN_PATH = "$HOME/.bun/bin/bun"
|
|||||||
# Command to install bun.
|
# Command to install bun.
|
||||||
INSTALL_BUN = "curl https://bun.sh/install | bash"
|
INSTALL_BUN = "curl https://bun.sh/install | bash"
|
||||||
# Command to run the backend in dev mode.
|
# 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.
|
# The default timeout when launching the gunicorn server.
|
||||||
TIMEOUT = 120
|
TIMEOUT = 120
|
||||||
# The command to run the backend in production mode.
|
# 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.
|
# Compiler variables.
|
||||||
# The extension for compiled Javascript files.
|
# The extension for compiled Javascript files.
|
||||||
|
@ -257,40 +257,6 @@ class State(Base, ABC):
|
|||||||
field.required = False
|
field.required = False
|
||||||
field.default = default_value
|
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):
|
def __setattr__(self, name: str, value: Any):
|
||||||
"""Set the attribute.
|
"""Set the attribute.
|
||||||
|
|
||||||
@ -298,20 +264,13 @@ class State(Base, ABC):
|
|||||||
name: The name of the attribute.
|
name: The name of the attribute.
|
||||||
value: The value 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.
|
# Set the attribute.
|
||||||
super().__setattr__(name, value)
|
super().__setattr__(name, value)
|
||||||
|
|
||||||
# Add the var to the dirty list.
|
# Add the var to the dirty list.
|
||||||
if name in super().__getattribute__("vars"):
|
if name in self.vars:
|
||||||
super().__getattribute__("dirty_vars").add(name)
|
self.dirty_vars.add(name)
|
||||||
super().__getattribute__("mark_dirty")()
|
self.mark_dirty()
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""Reset all the base vars to their default values."""
|
"""Reset all the base vars to their default values."""
|
||||||
@ -358,11 +317,10 @@ class State(Base, ABC):
|
|||||||
Returns:
|
Returns:
|
||||||
The state update after processing the event.
|
The state update after processing the event.
|
||||||
"""
|
"""
|
||||||
# NOTE: We use super().__getattribute__ for performance reasons.
|
|
||||||
# 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]
|
||||||
substate = super().__getattribute__("get_substate")(path)
|
substate = self.get_substate(path)
|
||||||
handler = getattr(substate, name)
|
handler = getattr(substate, name)
|
||||||
|
|
||||||
# Process the event.
|
# Process the event.
|
||||||
@ -383,10 +341,10 @@ class State(Base, ABC):
|
|||||||
events = utils.fix_events(events, event.token)
|
events = utils.fix_events(events, event.token)
|
||||||
|
|
||||||
# Get the delta after processing the event.
|
# Get the delta after processing the event.
|
||||||
delta = super().__getattribute__("get_delta")()
|
delta = self.get_delta()
|
||||||
|
|
||||||
# Reset the dirty vars.
|
# Reset the dirty vars.
|
||||||
super().__getattribute__("clean")()
|
self.clean()
|
||||||
|
|
||||||
# Return the state update.
|
# Return the state update.
|
||||||
return StateUpdate(delta=delta, events=events)
|
return StateUpdate(delta=delta, events=events)
|
||||||
@ -397,22 +355,20 @@ class State(Base, ABC):
|
|||||||
Returns:
|
Returns:
|
||||||
The delta for the state.
|
The delta for the state.
|
||||||
"""
|
"""
|
||||||
# NOTE: We use super().__getattribute__ for performance reasons.
|
|
||||||
delta = {}
|
delta = {}
|
||||||
|
|
||||||
# Return the dirty vars, as well as all computed vars.
|
# Return the dirty vars, as well as all computed vars.
|
||||||
subdelta = {
|
subdelta = {
|
||||||
prop: getattr(self, prop)
|
prop: getattr(self, prop)
|
||||||
for prop in super().__getattribute__("dirty_vars")
|
for prop in self.dirty_vars | self.computed_vars.keys()
|
||||||
| set(super().__getattribute__("computed_vars").keys())
|
|
||||||
}
|
}
|
||||||
if len(subdelta) > 0:
|
if len(subdelta) > 0:
|
||||||
delta[super().__getattribute__("get_full_name")()] = subdelta
|
delta[self.get_full_name()] = subdelta
|
||||||
|
|
||||||
# Recursively find the substate deltas.
|
# Recursively find the substate deltas.
|
||||||
substates = super().__getattribute__("substates")
|
substates = self.substates
|
||||||
for substate in super().__getattribute__("dirty_substates"):
|
for substate in self.dirty_substates:
|
||||||
delta.update(substates[substate].getattr("get_delta")())
|
delta.update(substates[substate].get_delta())
|
||||||
|
|
||||||
# Format the delta.
|
# Format the delta.
|
||||||
delta = utils.format_state(delta)
|
delta = utils.format_state(delta)
|
||||||
@ -428,14 +384,13 @@ class State(Base, ABC):
|
|||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""Reset the dirty vars."""
|
"""Reset the dirty vars."""
|
||||||
# NOTE: We use super().__getattribute__ for performance reasons.
|
|
||||||
# Recursively clean the substates.
|
# Recursively clean the substates.
|
||||||
for substate in super().__getattribute__("dirty_substates"):
|
for substate in self.dirty_substates:
|
||||||
super().__getattribute__("substates")[substate].getattr("clean")()
|
self.substates[substate].clean()
|
||||||
|
|
||||||
# Clean this state.
|
# Clean this state.
|
||||||
super().__setattr__("dirty_vars", set())
|
self.dirty_vars = set()
|
||||||
super().__setattr__("dirty_substates", set())
|
self.dirty_substates = set()
|
||||||
|
|
||||||
def dict(self, include_computed: bool = True, **kwargs) -> Dict[str, Any]:
|
def dict(self, include_computed: bool = True, **kwargs) -> Dict[str, Any]:
|
||||||
"""Convert the object to a dictionary.
|
"""Convert the object to a dictionary.
|
||||||
|
@ -28,9 +28,9 @@ from typing import (
|
|||||||
Type,
|
Type,
|
||||||
Union,
|
Union,
|
||||||
)
|
)
|
||||||
import typer
|
|
||||||
|
|
||||||
import plotly.graph_objects as go
|
import plotly.graph_objects as go
|
||||||
|
import typer
|
||||||
from plotly.io import to_json
|
from plotly.io import to_json
|
||||||
from redis import Redis
|
from redis import Redis
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
@ -124,7 +124,7 @@ def test_initialize_with_state(TestState: Type[State]):
|
|||||||
token = "token"
|
token = "token"
|
||||||
state = app.get_state(token)
|
state = app.get_state(token)
|
||||||
assert isinstance(state, TestState)
|
assert isinstance(state, TestState)
|
||||||
assert state.var == 0
|
assert state.var == 0 # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def test_set_and_get_state(TestState: Type[State]):
|
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.
|
# Get the default state for each token.
|
||||||
state1 = app.get_state(token1)
|
state1 = app.get_state(token1)
|
||||||
state2 = app.get_state(token2)
|
state2 = app.get_state(token2)
|
||||||
assert state1.var == 0
|
assert state1.var == 0 # type: ignore
|
||||||
assert state2.var == 0
|
assert state2.var == 0 # type: ignore
|
||||||
|
|
||||||
# Set the vars to different values.
|
# Set the vars to different values.
|
||||||
state1.var = 1
|
state1.var = 1
|
||||||
@ -154,5 +154,5 @@ def test_set_and_get_state(TestState: Type[State]):
|
|||||||
# Get the states again and check the values.
|
# Get the states again and check the values.
|
||||||
state1 = app.get_state(token1)
|
state1 = app.get_state(token1)
|
||||||
state2 = app.get_state(token2)
|
state2 = app.get_state(token2)
|
||||||
assert state1.var == 1
|
assert state1.var == 1 # type: ignore
|
||||||
assert state2.var == 2
|
assert state2.var == 2 # type: ignore
|
||||||
|
@ -411,12 +411,15 @@ def test_get_child_attribute(TestState, ChildState, ChildState2, GrandchildState
|
|||||||
GrandchildState: The grandchild state class.
|
GrandchildState: The grandchild state class.
|
||||||
"""
|
"""
|
||||||
test_state = TestState()
|
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.num1 == 0
|
||||||
assert test_state.child_state.value == ""
|
assert child_state.value == ""
|
||||||
assert test_state.child_state2.value == ""
|
assert child_state2.value == ""
|
||||||
assert test_state.child_state.count == 23
|
assert child_state.count == 23
|
||||||
assert test_state.child_state.grandchild_state.value2 == ""
|
assert grandchild_state.value2 == ""
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
test_state.invalid
|
test_state.invalid
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
@ -435,77 +438,15 @@ def test_set_child_attribute(TestState, ChildState, ChildState2, GrandchildState
|
|||||||
GrandchildState: The grandchild state class.
|
GrandchildState: The grandchild state class.
|
||||||
"""
|
"""
|
||||||
test_state = TestState()
|
test_state = TestState()
|
||||||
child_state = test_state.child_state
|
child_state = test_state.get_substate(["child_state"])
|
||||||
grandchild_state = child_state.grandchild_state
|
grandchild_state = child_state.get_substate(["grandchild_state"])
|
||||||
|
|
||||||
test_state.num1 = 10
|
test_state.num1 = 10
|
||||||
assert 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"
|
child_state.value = "test"
|
||||||
assert test_state.child_state.value == "test"
|
|
||||||
assert child_state.value == "test"
|
assert child_state.value == "test"
|
||||||
assert grandchild_state.value == "test"
|
grandchild_state.value2 = "test2"
|
||||||
|
assert grandchild_state.value2 == "test2"
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_substate(TestState, ChildState, ChildState2, GrandchildState):
|
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.
|
GrandchildState: The grandchild state class.
|
||||||
"""
|
"""
|
||||||
test_state = TestState()
|
test_state = TestState()
|
||||||
child_state = test_state.child_state
|
child_state = test_state.substates["child_state"]
|
||||||
grandchild_state = child_state.grandchild_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_state",)) == child_state
|
||||||
assert test_state.get_substate(("child_state2",)) == test_state.child_state2
|
assert test_state.get_substate(("child_state2",)) == child_state2
|
||||||
assert (
|
assert (
|
||||||
test_state.get_substate(("child_state", "grandchild_state")) == grandchild_state
|
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.
|
GrandchildState: The grandchild state class.
|
||||||
"""
|
"""
|
||||||
test_state = TestState()
|
test_state = TestState()
|
||||||
child_state = test_state.child_state
|
child_state = test_state.get_substate(["child_state"])
|
||||||
child_state2 = test_state.child_state2
|
child_state2 = test_state.get_substate(["child_state2"])
|
||||||
grandchild_state = child_state.grandchild_state
|
grandchild_state = child_state.get_substate(["grandchild_state"])
|
||||||
|
|
||||||
# Initially there should be no dirty vars.
|
# Initially there should be no dirty vars.
|
||||||
assert test_state.dirty_vars == set()
|
assert test_state.dirty_vars == set()
|
||||||
@ -610,7 +552,7 @@ def test_reset(TestState, ChildState):
|
|||||||
ChildState: The child state class.
|
ChildState: The child state class.
|
||||||
"""
|
"""
|
||||||
test_state = TestState()
|
test_state = TestState()
|
||||||
child_state = test_state.child_state
|
child_state = test_state.get_substate(["child_state"])
|
||||||
|
|
||||||
# Set some values.
|
# Set some values.
|
||||||
test_state.num1 = 1
|
test_state.num1 = 1
|
||||||
@ -664,8 +606,8 @@ async def test_process_event_substate(TestState, ChildState, GrandchildState):
|
|||||||
GrandchildState: The grandchild state class.
|
GrandchildState: The grandchild state class.
|
||||||
"""
|
"""
|
||||||
test_state = TestState()
|
test_state = TestState()
|
||||||
child_state = test_state.child_state
|
child_state = test_state.get_substate(["child_state"])
|
||||||
grandchild_state = child_state.grandchild_state
|
grandchild_state = child_state.get_substate(["grandchild_state"])
|
||||||
|
|
||||||
# Events should bubble down to the substate.
|
# Events should bubble down to the substate.
|
||||||
assert child_state.value == ""
|
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.child_state.grandchild_state": {"value2": "new"},
|
||||||
"test_state": {"sum": 3.14, "upper": ""},
|
"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": ""}}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user