|
@@ -53,11 +53,9 @@ from reflex.route import (
|
|
verify_route_validity,
|
|
verify_route_validity,
|
|
)
|
|
)
|
|
from reflex.state import (
|
|
from reflex.state import (
|
|
- DefaultState,
|
|
|
|
RouterData,
|
|
RouterData,
|
|
State,
|
|
State,
|
|
StateManager,
|
|
StateManager,
|
|
- StateManagerMemory,
|
|
|
|
StateUpdate,
|
|
StateUpdate,
|
|
)
|
|
)
|
|
from reflex.utils import console, format, prerequisites, types
|
|
from reflex.utils import console, format, prerequisites, types
|
|
@@ -96,10 +94,10 @@ class App(Base):
|
|
socket_app: Optional[ASGIApp] = None
|
|
socket_app: Optional[ASGIApp] = None
|
|
|
|
|
|
# The state class to use for the app.
|
|
# The state class to use for the app.
|
|
- state: Type[State] = DefaultState
|
|
|
|
|
|
+ state: Optional[Type[State]] = None
|
|
|
|
|
|
# Class to manage many client states.
|
|
# Class to manage many client states.
|
|
- state_manager: StateManager = StateManagerMemory(state=DefaultState)
|
|
|
|
|
|
+ _state_manager: Optional[StateManager] = None
|
|
|
|
|
|
# The styling to apply to each component.
|
|
# The styling to apply to each component.
|
|
style: ComponentStyle = {}
|
|
style: ComponentStyle = {}
|
|
@@ -148,19 +146,19 @@ class App(Base):
|
|
)
|
|
)
|
|
super().__init__(*args, **kwargs)
|
|
super().__init__(*args, **kwargs)
|
|
state_subclasses = State.__subclasses__()
|
|
state_subclasses = State.__subclasses__()
|
|
- inferred_state = state_subclasses[-1]
|
|
|
|
|
|
+ inferred_state = state_subclasses[-1] if state_subclasses else None
|
|
is_testing_env = constants.PYTEST_CURRENT_TEST in os.environ
|
|
is_testing_env = constants.PYTEST_CURRENT_TEST in os.environ
|
|
|
|
|
|
# Special case to allow test cases have multiple subclasses of rx.State.
|
|
# Special case to allow test cases have multiple subclasses of rx.State.
|
|
if not is_testing_env:
|
|
if not is_testing_env:
|
|
- # Only the default state and the client state should be allowed as subclasses.
|
|
|
|
- if len(state_subclasses) > 2:
|
|
|
|
|
|
+ # Only one State class is allowed.
|
|
|
|
+ if len(state_subclasses) > 1:
|
|
raise ValueError(
|
|
raise ValueError(
|
|
"rx.State has been subclassed multiple times. Only one subclass is allowed"
|
|
"rx.State has been subclassed multiple times. Only one subclass is allowed"
|
|
)
|
|
)
|
|
|
|
|
|
# verify that provided state is valid
|
|
# verify that provided state is valid
|
|
- if self.state not in [DefaultState, inferred_state]:
|
|
|
|
|
|
+ if self.state and inferred_state and self.state is not inferred_state:
|
|
console.warn(
|
|
console.warn(
|
|
f"Using substate ({self.state.__name__}) as root state in `rx.App` is currently not supported."
|
|
f"Using substate ({self.state.__name__}) as root state in `rx.App` is currently not supported."
|
|
f" Defaulting to root state: ({inferred_state.__name__})"
|
|
f" Defaulting to root state: ({inferred_state.__name__})"
|
|
@@ -172,15 +170,15 @@ class App(Base):
|
|
# Add middleware.
|
|
# Add middleware.
|
|
self.middleware.append(HydrateMiddleware())
|
|
self.middleware.append(HydrateMiddleware())
|
|
|
|
|
|
- # Set up the state manager.
|
|
|
|
- self.state_manager = StateManager.create(state=self.state)
|
|
|
|
-
|
|
|
|
# Set up the API.
|
|
# Set up the API.
|
|
self.api = FastAPI()
|
|
self.api = FastAPI()
|
|
self.add_cors()
|
|
self.add_cors()
|
|
self.add_default_endpoints()
|
|
self.add_default_endpoints()
|
|
|
|
|
|
- if self.state is not DefaultState:
|
|
|
|
|
|
+ if self.state:
|
|
|
|
+ # Set up the state manager.
|
|
|
|
+ self._state_manager = StateManager.create(state=self.state)
|
|
|
|
+
|
|
# Set up the Socket.IO AsyncServer.
|
|
# Set up the Socket.IO AsyncServer.
|
|
self.sio = AsyncServer(
|
|
self.sio = AsyncServer(
|
|
async_mode="asgi",
|
|
async_mode="asgi",
|
|
@@ -212,10 +210,7 @@ class App(Base):
|
|
self.setup_admin_dash()
|
|
self.setup_admin_dash()
|
|
|
|
|
|
# If a State is not used and no overlay_component is specified, do not render the connection modal
|
|
# If a State is not used and no overlay_component is specified, do not render the connection modal
|
|
- if (
|
|
|
|
- self.state is DefaultState
|
|
|
|
- and self.overlay_component is default_overlay_component
|
|
|
|
- ):
|
|
|
|
|
|
+ if self.state is None and self.overlay_component is default_overlay_component:
|
|
self.overlay_component = None
|
|
self.overlay_component = None
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
def __repr__(self) -> str:
|
|
@@ -224,7 +219,7 @@ class App(Base):
|
|
Returns:
|
|
Returns:
|
|
The string representation of the app.
|
|
The string representation of the app.
|
|
"""
|
|
"""
|
|
- return f"<App state={self.state.__name__}>"
|
|
|
|
|
|
+ return f"<App state={self.state.__name__ if self.state else None}>"
|
|
|
|
|
|
def __call__(self) -> FastAPI:
|
|
def __call__(self) -> FastAPI:
|
|
"""Run the backend api instance.
|
|
"""Run the backend api instance.
|
|
@@ -252,6 +247,20 @@ class App(Base):
|
|
allow_origins=["*"],
|
|
allow_origins=["*"],
|
|
)
|
|
)
|
|
|
|
|
|
|
|
+ @property
|
|
|
|
+ def state_manager(self) -> StateManager:
|
|
|
|
+ """Get the state manager.
|
|
|
|
+
|
|
|
|
+ Returns:
|
|
|
|
+ The initialized state manager.
|
|
|
|
+
|
|
|
|
+ Raises:
|
|
|
|
+ ValueError: if the state has not been initialized.
|
|
|
|
+ """
|
|
|
|
+ if self._state_manager is None:
|
|
|
|
+ raise ValueError("The state manager has not been initialized.")
|
|
|
|
+ return self._state_manager
|
|
|
|
+
|
|
async def preprocess(self, state: State, event: Event) -> StateUpdate | None:
|
|
async def preprocess(self, state: State, event: Event) -> StateUpdate | None:
|
|
"""Preprocess the event.
|
|
"""Preprocess the event.
|
|
|
|
|
|
@@ -385,7 +394,8 @@ class App(Base):
|
|
verify_route_validity(route)
|
|
verify_route_validity(route)
|
|
|
|
|
|
# Apply dynamic args to the route.
|
|
# Apply dynamic args to the route.
|
|
- self.state.setup_dynamic_args(get_route_args(route))
|
|
|
|
|
|
+ if self.state:
|
|
|
|
+ self.state.setup_dynamic_args(get_route_args(route))
|
|
|
|
|
|
# Generate the component if it is a callable.
|
|
# Generate the component if it is a callable.
|
|
component = self._generate_component(component)
|
|
component = self._generate_component(component)
|
|
@@ -715,6 +725,7 @@ class App(Base):
|
|
"""
|
|
"""
|
|
if self.event_namespace is None:
|
|
if self.event_namespace is None:
|
|
raise RuntimeError("App has not been initialized yet.")
|
|
raise RuntimeError("App has not been initialized yet.")
|
|
|
|
+
|
|
# Get exclusive access to the state.
|
|
# Get exclusive access to the state.
|
|
async with self.state_manager.modify_state(token) as state:
|
|
async with self.state_manager.modify_state(token) as state:
|
|
# No other event handler can modify the state while in this context.
|
|
# No other event handler can modify the state while in this context.
|
|
@@ -862,6 +873,7 @@ def upload(app: App):
|
|
for file in files:
|
|
for file in files:
|
|
assert file.filename is not None
|
|
assert file.filename is not None
|
|
file.filename = file.filename.split(":")[-1]
|
|
file.filename = file.filename.split(":")[-1]
|
|
|
|
+
|
|
# Get the state for the session.
|
|
# Get the state for the session.
|
|
async with app.state_manager.modify_state(token) as state:
|
|
async with app.state_manager.modify_state(token) as state:
|
|
# get the current session ID
|
|
# get the current session ID
|