rx.App state arg should not be required (#1361)

This commit is contained in:
Elijah Ahianyo 2023-07-21 18:47:38 +00:00 committed by GitHub
parent 278b9ab7e2
commit 549ab4e708
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 77 additions and 25 deletions

View File

@ -2,6 +2,7 @@
import asyncio
import inspect
import os
from multiprocessing.pool import ThreadPool
from typing import (
Any,
@ -42,7 +43,7 @@ from reflex.route import (
verify_route_validity,
)
from reflex.state import DefaultState, State, StateManager, StateUpdate
from reflex.utils import format, types
from reflex.utils import console, format, types
# Define custom types.
ComponentCallable = Callable[[], Component]
@ -100,8 +101,25 @@ class App(Base):
Raises:
ValueError: If the event namespace is not provided in the config.
Also, if there are multiple client subclasses of rx.State(Subclasses of rx.State should consist
of the DefaultState and the client app state).
"""
super().__init__(*args, **kwargs)
state_subclasses = State.__subclasses__()
is_testing_env = constants.PYTEST_CURRENT_TEST in os.environ
# Special case to allow test cases have multiple subclasses of rx.State.
if not is_testing_env:
# Only the default state and the client state should be allowed as subclasses.
if len(state_subclasses) > 2:
raise ValueError(
"rx.State has been subclassed multiple times. Only one subclass is allowed"
)
if self.state != DefaultState:
console.deprecate(
"Passing the state as keyword argument to `rx.App` is deprecated."
)
self.state = state_subclasses[-1]
# Get the config
config = get_config()

View File

@ -200,6 +200,10 @@ TOKEN_EXPIRATION = 60 * 60
# The event namespace for websocket
EVENT_NAMESPACE = get_value("EVENT_NAMESPACE")
# Testing variables.
# Testing os env set by pytest when running a test case.
PYTEST_CURRENT_TEST = "PYTEST_CURRENT_TEST"
# Env modes
class Env(str, Enum):
"""The environment modes."""

View File

@ -9,9 +9,20 @@ import pytest
import reflex as rx
from reflex import constants
from reflex.app import App
from reflex.event import EventSpec
@pytest.fixture
def app() -> App:
"""A base app.
Returns:
The app.
"""
return App()
@pytest.fixture(scope="function")
def windows_platform() -> Generator:
"""Check if system is windows.

View File

@ -78,25 +78,27 @@ def hydrate_middleware() -> HydrateMiddleware:
@pytest.mark.asyncio
@pytest.mark.parametrize(
"State, expected, event_fixture",
"test_state, expected, event_fixture",
[
(TestState, {"test_state": {"num": 1}}, "event1"),
(TestState2, {"test_state2": {"num": 1}}, "event2"),
(TestState3, {"test_state3": {"num": 1}}, "event3"),
],
)
async def test_preprocess(State, hydrate_middleware, request, event_fixture, expected):
async def test_preprocess(
test_state, hydrate_middleware, request, event_fixture, expected
):
"""Test that a state hydrate event is processed correctly.
Args:
State: state to process event
hydrate_middleware: instance of HydrateMiddleware
request: pytest fixture request
event_fixture: The event fixture(an Event)
expected: expected delta
test_state: State to process event.
hydrate_middleware: Instance of HydrateMiddleware.
request: Pytest fixture request.
event_fixture: The event fixture(an Event).
expected: Expected delta.
"""
app = App(state=State, load_events={"index": [State.test_handler]})
state = State()
app = App(state=test_state, load_events={"index": [test_state.test_handler]})
state = test_state()
update = await hydrate_middleware.preprocess(
app=app, event=request.getfixturevalue(event_fixture), state=state
@ -120,8 +122,8 @@ async def test_preprocess_multiple_load_events(hydrate_middleware, event1):
"""Test that a state hydrate event for multiple on-load events is processed correctly.
Args:
hydrate_middleware: instance of HydrateMiddleware
event1: an Event.
hydrate_middleware: Instance of HydrateMiddleware
event1: An Event.
"""
app = App(
state=TestState,
@ -151,8 +153,8 @@ async def test_preprocess_no_events(hydrate_middleware, event1):
"""Test that app without on_load is processed correctly.
Args:
hydrate_middleware: instance of HydrateMiddleware
event1: an Event.
hydrate_middleware: Instance of HydrateMiddleware
event1: An Event.
"""
state = TestState()
update = await hydrate_middleware.preprocess(

View File

@ -27,16 +27,6 @@ from reflex.utils import format
from reflex.vars import ComputedVar
@pytest.fixture
def app() -> App:
"""A base app.
Returns:
The app.
"""
return App()
@pytest.fixture
def index_page():
"""An index page.
@ -79,6 +69,20 @@ def test_state() -> Type[State]:
return TestState
@pytest.fixture()
def redundant_test_state() -> Type[State]:
"""A default state.
Returns:
A default state.
"""
class RedundantTestState(State):
var: int
return RedundantTestState
@pytest.fixture()
def test_model() -> Type[Model]:
"""A default model.
@ -170,6 +174,19 @@ def test_default_app(app: App):
assert app.admin_dash is None
def test_multiple_states_error(monkeypatch, test_state, redundant_test_state):
"""Test that an error is thrown when multiple classes subclass rx.State.
Args:
monkeypatch: Pytest monkeypatch object.
test_state: A test state subclassing rx.State.
redundant_test_state: Another test state subclassing rx.State.
"""
monkeypatch.delenv(constants.PYTEST_CURRENT_TEST)
with pytest.raises(ValueError):
App()
def test_add_page_default_route(app: App, index_page, about_page):
"""Test adding a page to an app.
@ -708,7 +725,7 @@ class DynamicState(State):
There are several counters:
* loaded: counts how many times `on_load` was triggered by the hydrate middleware
* counter: counts how many times `on_counter` was triggered by a non-naviagational event
* counter: counts how many times `on_counter` was triggered by a non-navigational event
-> these events should NOT trigger reload or recalculation of router_data dependent vars
* side_effect_counter: counts how many times a computed var was
recalculated when the dynamic route var was dirty