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

[ENG-4326] Async ComputedVar #4711

Merged
merged 15 commits into from
Feb 1, 2025
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
16 changes: 11 additions & 5 deletions reflex/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -908,11 +908,17 @@ def _validate_var_dependencies(
if not var._cache:
continue
deps = var._deps(objclass=state)
for dep in deps:
if dep not in state.vars and dep not in state.backend_vars:
raise exceptions.VarDependencyError(
f"ComputedVar {var._js_expr} on state {state.__name__} has an invalid dependency {dep}"
)
for state_name, dep_set in deps.items():
state_cls = (
state.get_root_state().get_class_substate(state_name)
if state_name != state.get_full_name()
else state
)
for dep in dep_set:
if dep not in state_cls.vars and dep not in state_cls.backend_vars:
raise exceptions.VarDependencyError(
f"ComputedVar {var._js_expr} on state {state.__name__} has an invalid dependency {state_name}.{dep}"
)

for substate in state.class_subclasses:
self._validate_var_dependencies(substate)
Expand Down
24 changes: 22 additions & 2 deletions reflex/compiler/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

from __future__ import annotations

import asyncio
import concurrent.futures
import traceback
from datetime import datetime
from pathlib import Path
from typing import Any, Callable, Dict, Optional, Type, Union
from urllib.parse import urlparse

from reflex.utils.exec import is_in_app_harness
from reflex.utils.prerequisites import get_web_dir
from reflex.vars.base import Var

Expand All @@ -33,7 +36,7 @@
)
from reflex.components.component import Component, ComponentStyle, CustomComponent
from reflex.istate.storage import Cookie, LocalStorage, SessionStorage
from reflex.state import BaseState
from reflex.state import BaseState, _resolve_delta
from reflex.style import Style
from reflex.utils import console, format, imports, path_ops
from reflex.utils.imports import ImportVar, ParsedImportDict
Expand Down Expand Up @@ -177,7 +180,24 @@ def compile_state(state: Type[BaseState]) -> dict:
initial_state = state(_reflex_internal_init=True).dict(
initial=True, include_computed=False
)
return initial_state
try:
_ = asyncio.get_running_loop()
except RuntimeError:
pass
else:
if is_in_app_harness():
Copy link
Member

Choose a reason for hiding this comment

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

is it possible to abstract this logic beyond being specific to app harness? is it possible to detect that an event loop running?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

it's possible to detect it (which is what we're doing just above), but that would be abnormal conditions for a reflex app outside of a testing environment, so maybe we want to fail on it?

Copy link
Member

Choose a reason for hiding this comment

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

an error sounds fine then, if someone complains we will learn something 🤓

# Playwright tests already have an event loop running, so we can't use asyncio.run.
with concurrent.futures.ThreadPoolExecutor() as pool:
resolved_initial_state = pool.submit(
asyncio.run, _resolve_delta(initial_state)
).result()
console.warn(
f"Had to get initial state in a thread 🤮 {resolved_initial_state}",
)
return resolved_initial_state

# Normally the compile runs before any event loop starts, we asyncio.run is available for calling.
return asyncio.run(_resolve_delta(initial_state))


def _compile_client_storage_field(
Expand Down
4 changes: 2 additions & 2 deletions reflex/middleware/hydrate_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from reflex import constants
from reflex.event import Event, get_hydrate_event
from reflex.middleware.middleware import Middleware
from reflex.state import BaseState, StateUpdate
from reflex.state import BaseState, StateUpdate, _resolve_delta

if TYPE_CHECKING:
from reflex.app import App
Expand Down Expand Up @@ -42,7 +42,7 @@ async def preprocess(
setattr(state, constants.CompileVars.IS_HYDRATED, False)

# Get the initial state.
delta = state.dict()
delta = await _resolve_delta(state.dict())
# since a full dict was captured, clean any dirtiness
state._clean()

Expand Down
Loading