Skip to content

Commit a224319

Browse files
masenfadhami3310
andauthored
[ENG-4326] Async ComputedVar (#4711)
* WiP * Save the var from get_var_name * flatten StateManagerRedis.get_state algorithm simplify fetching of states and avoid repeatedly fetching the same state * Get all the states in a single redis round-trip * update docstrings in StateManagerRedis * Move computed var dep tracking to separate module * Fix pre-commit issues * ComputedVar.add_dependency: explicitly dependency declaration Allow var dependencies to be added at runtime, for example, when defining a ComponentState that depends on vars that cannot be known statically. Fix more pyright issues. * Fix/ignore more pyright issues from recent merge * handle cleaning out _potentially_dirty_states on reload * ignore accessed attributes missing on state class these might be added dynamically later in which case we recompute the dependency tracking dicts... if not, they'll blow up anyway at runtime. * fix playwright tests, which insist on running an asyncio loop --------- Co-authored-by: Khaleel Al-Adhami <[email protected]>
1 parent 7da5fa0 commit a224319

File tree

11 files changed

+1091
-492
lines changed

11 files changed

+1091
-492
lines changed

reflex/app.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -908,11 +908,17 @@ def _validate_var_dependencies(
908908
if not var._cache:
909909
continue
910910
deps = var._deps(objclass=state)
911-
for dep in deps:
912-
if dep not in state.vars and dep not in state.backend_vars:
913-
raise exceptions.VarDependencyError(
914-
f"ComputedVar {var._js_expr} on state {state.__name__} has an invalid dependency {dep}"
915-
)
911+
for state_name, dep_set in deps.items():
912+
state_cls = (
913+
state.get_root_state().get_class_substate(state_name)
914+
if state_name != state.get_full_name()
915+
else state
916+
)
917+
for dep in dep_set:
918+
if dep not in state_cls.vars and dep not in state_cls.backend_vars:
919+
raise exceptions.VarDependencyError(
920+
f"ComputedVar {var._js_expr} on state {state.__name__} has an invalid dependency {state_name}.{dep}"
921+
)
916922

917923
for substate in state.class_subclasses:
918924
self._validate_var_dependencies(substate)

reflex/compiler/utils.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
from __future__ import annotations
44

5+
import asyncio
6+
import concurrent.futures
57
import traceback
68
from datetime import datetime
79
from pathlib import Path
810
from typing import Any, Callable, Dict, Optional, Type, Union
911
from urllib.parse import urlparse
1012

13+
from reflex.utils.exec import is_in_app_harness
1114
from reflex.utils.prerequisites import get_web_dir
1215
from reflex.vars.base import Var
1316

@@ -33,7 +36,7 @@
3336
)
3437
from reflex.components.component import Component, ComponentStyle, CustomComponent
3538
from reflex.istate.storage import Cookie, LocalStorage, SessionStorage
36-
from reflex.state import BaseState
39+
from reflex.state import BaseState, _resolve_delta
3740
from reflex.style import Style
3841
from reflex.utils import console, format, imports, path_ops
3942
from reflex.utils.imports import ImportVar, ParsedImportDict
@@ -177,7 +180,24 @@ def compile_state(state: Type[BaseState]) -> dict:
177180
initial_state = state(_reflex_internal_init=True).dict(
178181
initial=True, include_computed=False
179182
)
180-
return initial_state
183+
try:
184+
_ = asyncio.get_running_loop()
185+
except RuntimeError:
186+
pass
187+
else:
188+
if is_in_app_harness():
189+
# Playwright tests already have an event loop running, so we can't use asyncio.run.
190+
with concurrent.futures.ThreadPoolExecutor() as pool:
191+
resolved_initial_state = pool.submit(
192+
asyncio.run, _resolve_delta(initial_state)
193+
).result()
194+
console.warn(
195+
f"Had to get initial state in a thread 🤮 {resolved_initial_state}",
196+
)
197+
return resolved_initial_state
198+
199+
# Normally the compile runs before any event loop starts, we asyncio.run is available for calling.
200+
return asyncio.run(_resolve_delta(initial_state))
181201

182202

183203
def _compile_client_storage_field(

reflex/middleware/hydrate_middleware.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from reflex import constants
99
from reflex.event import Event, get_hydrate_event
1010
from reflex.middleware.middleware import Middleware
11-
from reflex.state import BaseState, StateUpdate
11+
from reflex.state import BaseState, StateUpdate, _resolve_delta
1212

1313
if TYPE_CHECKING:
1414
from reflex.app import App
@@ -42,7 +42,7 @@ async def preprocess(
4242
setattr(state, constants.CompileVars.IS_HYDRATED, False)
4343

4444
# Get the initial state.
45-
delta = state.dict()
45+
delta = await _resolve_delta(state.dict())
4646
# since a full dict was captured, clean any dirtiness
4747
state._clean()
4848

0 commit comments

Comments
 (0)