Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve event processing performance #163

Merged
merged 6 commits into from
Dec 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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": ""},
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice I think it won't be a big deal getting rid of this


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