|
@@ -100,6 +100,7 @@ from reflex.state import (
|
|
|
StateManager,
|
|
|
StateUpdate,
|
|
|
_substate_key,
|
|
|
+ all_base_state_classes,
|
|
|
code_uses_state_contexts,
|
|
|
)
|
|
|
from reflex.utils import (
|
|
@@ -117,6 +118,7 @@ from reflex.utils.imports import ImportVar
|
|
|
if TYPE_CHECKING:
|
|
|
from reflex.vars import Var
|
|
|
|
|
|
+
|
|
|
# Define custom types.
|
|
|
ComponentCallable = Callable[[], Component]
|
|
|
Reducer = Callable[[Event], Coroutine[Any, Any, StateUpdate]]
|
|
@@ -375,6 +377,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
# A map from a page route to the component to render. Users should use `add_page`.
|
|
|
_pages: Dict[str, Component] = dataclasses.field(default_factory=dict)
|
|
|
|
|
|
+ # A mapping of pages which created states as they were being evaluated.
|
|
|
+ _stateful_pages: Dict[str, None] = dataclasses.field(default_factory=dict)
|
|
|
+
|
|
|
# The backend API object.
|
|
|
_api: FastAPI | None = None
|
|
|
|
|
@@ -592,8 +597,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
"""Add optional api endpoints (_upload)."""
|
|
|
if not self.api:
|
|
|
return
|
|
|
-
|
|
|
- if Upload.is_used:
|
|
|
+ upload_is_used_marker = (
|
|
|
+ prerequisites.get_backend_dir() / constants.Dirs.UPLOAD_IS_USED
|
|
|
+ )
|
|
|
+ if Upload.is_used or upload_is_used_marker.exists():
|
|
|
# To upload files.
|
|
|
self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
|
|
|
|
|
@@ -603,10 +610,15 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
StaticFiles(directory=get_upload_dir()),
|
|
|
name="uploaded_files",
|
|
|
)
|
|
|
+
|
|
|
+ upload_is_used_marker.parent.mkdir(parents=True, exist_ok=True)
|
|
|
+ upload_is_used_marker.touch()
|
|
|
if codespaces.is_running_in_codespaces():
|
|
|
self.api.get(str(constants.Endpoint.AUTH_CODESPACE))(
|
|
|
codespaces.auth_codespace
|
|
|
)
|
|
|
+ if environment.REFLEX_ADD_ALL_ROUTES_ENDPOINT.get():
|
|
|
+ self.add_all_routes_endpoint()
|
|
|
|
|
|
def _add_cors(self):
|
|
|
"""Add CORS middleware to the app."""
|
|
@@ -747,13 +759,19 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
route: The route of the page to compile.
|
|
|
save_page: If True, the compiled page is saved to self._pages.
|
|
|
"""
|
|
|
+ n_states_before = len(all_base_state_classes)
|
|
|
component, enable_state = compiler.compile_unevaluated_page(
|
|
|
route, self._unevaluated_pages[route], self._state, self.style, self.theme
|
|
|
)
|
|
|
|
|
|
+ # Indicate that the app should use state.
|
|
|
if enable_state:
|
|
|
self._enable_state()
|
|
|
|
|
|
+ # Indicate that evaluating this page creates one or more state classes.
|
|
|
+ if len(all_base_state_classes) > n_states_before:
|
|
|
+ self._stateful_pages[route] = None
|
|
|
+
|
|
|
# Add the page.
|
|
|
self._check_routes_conflict(route)
|
|
|
if save_page:
|
|
@@ -1042,6 +1060,20 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
def get_compilation_time() -> str:
|
|
|
return str(datetime.now().time()).split(".")[0]
|
|
|
|
|
|
+ should_compile = self._should_compile()
|
|
|
+ backend_dir = prerequisites.get_backend_dir()
|
|
|
+ if not should_compile and backend_dir.exists():
|
|
|
+ stateful_pages_marker = backend_dir / constants.Dirs.STATEFUL_PAGES
|
|
|
+ if stateful_pages_marker.exists():
|
|
|
+ with stateful_pages_marker.open("r") as f:
|
|
|
+ stateful_pages = json.load(f)
|
|
|
+ for route in stateful_pages:
|
|
|
+ console.info(f"BE Evaluating stateful page: {route}")
|
|
|
+ self._compile_page(route, save_page=False)
|
|
|
+ self._enable_state()
|
|
|
+ self._add_optional_endpoints()
|
|
|
+ return
|
|
|
+
|
|
|
# Render a default 404 page if the user didn't supply one
|
|
|
if constants.Page404.SLUG not in self._unevaluated_pages:
|
|
|
self.add_page(route=constants.Page404.SLUG)
|
|
@@ -1343,6 +1375,24 @@ class App(MiddlewareMixin, LifespanMixin):
|
|
|
for output_path, code in compile_results:
|
|
|
compiler_utils.write_page(output_path, code)
|
|
|
|
|
|
+ # Write list of routes that create dynamic states for backend to use.
|
|
|
+ if self._state is not None:
|
|
|
+ stateful_pages_marker = (
|
|
|
+ prerequisites.get_backend_dir() / constants.Dirs.STATEFUL_PAGES
|
|
|
+ )
|
|
|
+ stateful_pages_marker.parent.mkdir(parents=True, exist_ok=True)
|
|
|
+ with stateful_pages_marker.open("w") as f:
|
|
|
+ json.dump(list(self._stateful_pages), f)
|
|
|
+
|
|
|
+ def add_all_routes_endpoint(self):
|
|
|
+ """Add an endpoint to the app that returns all the routes."""
|
|
|
+ if not self.api:
|
|
|
+ return
|
|
|
+
|
|
|
+ @self.api.get(str(constants.Endpoint.ALL_ROUTES))
|
|
|
+ async def all_routes():
|
|
|
+ return list(self._unevaluated_pages.keys())
|
|
|
+
|
|
|
@contextlib.asynccontextmanager
|
|
|
async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
|
|
|
"""Modify the state out of band.
|