Clean up config and app API (#3197)
This commit is contained in:
parent
db47d39979
commit
7903a1020d
@ -237,7 +237,7 @@ def test_app_10_compile_time_cold(benchmark, app_with_10_components):
|
||||
|
||||
def benchmark_fn():
|
||||
with chdir(app_with_10_components.app_path):
|
||||
app_with_10_components.app_instance.compile_()
|
||||
app_with_10_components.app_instance._compile()
|
||||
|
||||
benchmark.pedantic(benchmark_fn, setup=setup, rounds=10)
|
||||
|
||||
@ -262,7 +262,7 @@ def test_app_10_compile_time_warm(benchmark, app_with_10_components):
|
||||
|
||||
def benchmark_fn():
|
||||
with chdir(app_with_10_components.app_path):
|
||||
app_with_10_components.app_instance.compile_()
|
||||
app_with_10_components.app_instance._compile()
|
||||
|
||||
benchmark(benchmark_fn)
|
||||
|
||||
@ -290,7 +290,7 @@ def test_app_100_compile_time_cold(benchmark, app_with_100_components):
|
||||
|
||||
def benchmark_fn():
|
||||
with chdir(app_with_100_components.app_path):
|
||||
app_with_100_components.app_instance.compile_()
|
||||
app_with_100_components.app_instance._compile()
|
||||
|
||||
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
|
||||
|
||||
@ -315,7 +315,7 @@ def test_app_100_compile_time_warm(benchmark, app_with_100_components):
|
||||
|
||||
def benchmark_fn():
|
||||
with chdir(app_with_100_components.app_path):
|
||||
app_with_100_components.app_instance.compile_()
|
||||
app_with_100_components.app_instance._compile()
|
||||
|
||||
benchmark(benchmark_fn)
|
||||
|
||||
@ -343,7 +343,7 @@ def test_app_1000_compile_time_cold(benchmark, app_with_1000_components):
|
||||
|
||||
def benchmark_fn():
|
||||
with chdir(app_with_1000_components.app_path):
|
||||
app_with_1000_components.app_instance.compile_()
|
||||
app_with_1000_components.app_instance._compile()
|
||||
|
||||
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
|
||||
|
||||
@ -368,6 +368,6 @@ def test_app_1000_compile_time_warm(benchmark, app_with_1000_components):
|
||||
|
||||
def benchmark_fn():
|
||||
with chdir(app_with_1000_components.app_path):
|
||||
app_with_1000_components.app_instance.compile_()
|
||||
app_with_1000_components.app_instance._compile()
|
||||
|
||||
benchmark(benchmark_fn)
|
||||
|
@ -326,7 +326,7 @@ def test_app_1_compile_time_cold(benchmark, app_with_one_page):
|
||||
|
||||
def benchmark_fn():
|
||||
with chdir(app_with_one_page.app_path):
|
||||
app_with_one_page.app_instance.compile_()
|
||||
app_with_one_page.app_instance._compile()
|
||||
|
||||
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
|
||||
app_with_one_page._reload_state_module()
|
||||
@ -352,7 +352,7 @@ def test_app_1_compile_time_warm(benchmark, app_with_one_page):
|
||||
|
||||
def benchmark_fn():
|
||||
with chdir(app_with_one_page.app_path):
|
||||
app_with_one_page.app_instance.compile_()
|
||||
app_with_one_page.app_instance._compile()
|
||||
|
||||
benchmark(benchmark_fn)
|
||||
app_with_one_page._reload_state_module()
|
||||
@ -381,7 +381,7 @@ def test_app_10_compile_time_cold(benchmark, app_with_ten_pages):
|
||||
|
||||
def benchmark_fn():
|
||||
with chdir(app_with_ten_pages.app_path):
|
||||
app_with_ten_pages.app_instance.compile_()
|
||||
app_with_ten_pages.app_instance._compile()
|
||||
|
||||
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
|
||||
app_with_ten_pages._reload_state_module()
|
||||
@ -407,7 +407,7 @@ def test_app_10_compile_time_warm(benchmark, app_with_ten_pages):
|
||||
|
||||
def benchmark_fn():
|
||||
with chdir(app_with_ten_pages.app_path):
|
||||
app_with_ten_pages.app_instance.compile_()
|
||||
app_with_ten_pages.app_instance._compile()
|
||||
|
||||
benchmark(benchmark_fn)
|
||||
app_with_ten_pages._reload_state_module()
|
||||
@ -436,7 +436,7 @@ def test_app_100_compile_time_cold(benchmark, app_with_hundred_pages):
|
||||
|
||||
def benchmark_fn():
|
||||
with chdir(app_with_hundred_pages.app_path):
|
||||
app_with_hundred_pages.app_instance.compile_()
|
||||
app_with_hundred_pages.app_instance._compile()
|
||||
|
||||
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
|
||||
app_with_hundred_pages._reload_state_module()
|
||||
@ -462,7 +462,7 @@ def test_app_100_compile_time_warm(benchmark, app_with_hundred_pages):
|
||||
|
||||
def benchmark_fn():
|
||||
with chdir(app_with_hundred_pages.app_path):
|
||||
app_with_hundred_pages.app_instance.compile_()
|
||||
app_with_hundred_pages.app_instance._compile()
|
||||
|
||||
benchmark(benchmark_fn)
|
||||
app_with_hundred_pages._reload_state_module()
|
||||
@ -491,7 +491,7 @@ def test_app_1000_compile_time_cold(benchmark, app_with_thousand_pages):
|
||||
|
||||
def benchmark_fn():
|
||||
with chdir(app_with_thousand_pages.app_path):
|
||||
app_with_thousand_pages.app_instance.compile_()
|
||||
app_with_thousand_pages.app_instance._compile()
|
||||
|
||||
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
|
||||
app_with_thousand_pages._reload_state_module()
|
||||
@ -517,7 +517,7 @@ def test_app_1000_compile_time_warm(benchmark, app_with_thousand_pages):
|
||||
|
||||
def benchmark_fn():
|
||||
with chdir(app_with_thousand_pages.app_path):
|
||||
app_with_thousand_pages.app_instance.compile_()
|
||||
app_with_thousand_pages.app_instance._compile()
|
||||
|
||||
benchmark(benchmark_fn)
|
||||
app_with_thousand_pages._reload_state_module()
|
||||
@ -546,7 +546,7 @@ def test_app_10000_compile_time_cold(benchmark, app_with_ten_thousand_pages):
|
||||
|
||||
def benchmark_fn():
|
||||
with chdir(app_with_ten_thousand_pages.app_path):
|
||||
app_with_ten_thousand_pages.app_instance.compile_()
|
||||
app_with_ten_thousand_pages.app_instance._compile()
|
||||
|
||||
benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
|
||||
app_with_ten_thousand_pages._reload_state_module()
|
||||
@ -570,7 +570,7 @@ def test_app_10000_compile_time_warm(benchmark, app_with_ten_thousand_pages):
|
||||
|
||||
def benchmark_fn():
|
||||
with chdir(app_with_ten_thousand_pages.app_path):
|
||||
app_with_ten_thousand_pages.app_instance.compile_()
|
||||
app_with_ten_thousand_pages.app_instance._compile()
|
||||
|
||||
benchmark(benchmark_fn)
|
||||
app_with_ten_thousand_pages._reload_state_module()
|
||||
|
150
reflex/app.py
150
reflex/app.py
@ -102,61 +102,79 @@ class OverlayFragment(Fragment):
|
||||
|
||||
|
||||
class App(Base):
|
||||
"""A Reflex application."""
|
||||
"""The main Reflex app that encapsulates the backend and frontend.
|
||||
|
||||
# A map from a page route to the component to render.
|
||||
pages: Dict[str, Component] = {}
|
||||
Every Reflex app needs an app defined in its main module.
|
||||
|
||||
# A list of URLs to stylesheets to include in the app.
|
||||
stylesheets: List[str] = []
|
||||
```python
|
||||
# app.py
|
||||
import reflex as rx
|
||||
|
||||
# The backend API object.
|
||||
api: FastAPI = None # type: ignore
|
||||
# Define state and pages
|
||||
...
|
||||
|
||||
# The Socket.IO AsyncServer.
|
||||
sio: Optional[AsyncServer] = None
|
||||
app = rx.App(
|
||||
# Set global level style.
|
||||
style={...},
|
||||
# Set the top level theme.
|
||||
theme=rx.theme(accent_color="blue"),
|
||||
)
|
||||
```
|
||||
"""
|
||||
|
||||
# The state class to use for the app.
|
||||
state: Optional[Type[BaseState]] = None
|
||||
# The global [theme](https://reflex.dev/docs/styling/theming/#theme) for the entire app.
|
||||
theme: Optional[Component] = themes.theme(accent_color="blue")
|
||||
|
||||
# Class to manage many client states.
|
||||
_state_manager: Optional[StateManager] = None
|
||||
|
||||
# The styling to apply to each component.
|
||||
# The [global style](https://reflex.dev/docs/styling/overview/#global-styles}) for the app.
|
||||
style: ComponentStyle = {}
|
||||
|
||||
# Middleware to add to the app.
|
||||
middleware: List[Middleware] = []
|
||||
# A list of URLs to [stylesheets](https://reflex.dev/docs/styling/custom-stylesheets/) to include in the app.
|
||||
stylesheets: List[str] = []
|
||||
|
||||
# List of event handlers to trigger when a page loads.
|
||||
load_events: Dict[str, List[Union[EventHandler, EventSpec]]] = {}
|
||||
|
||||
# Admin dashboard
|
||||
admin_dash: Optional[AdminDash] = None
|
||||
|
||||
# The async server name space
|
||||
event_namespace: Optional[EventNamespace] = None
|
||||
# A component that is present on every page (defaults to the Connection Error banner).
|
||||
overlay_component: Optional[
|
||||
Union[Component, ComponentCallable]
|
||||
] = default_overlay_component
|
||||
|
||||
# Components to add to the head of every page.
|
||||
head_components: List[Component] = []
|
||||
|
||||
# The Socket.IO AsyncServer instance.
|
||||
sio: Optional[AsyncServer] = None
|
||||
|
||||
# The language to add to the html root tag of every page.
|
||||
html_lang: Optional[str] = None
|
||||
|
||||
# Attributes to add to the html root tag of every page.
|
||||
html_custom_attrs: Optional[Dict[str, str]] = None
|
||||
|
||||
# A component that is present on every page.
|
||||
overlay_component: Optional[
|
||||
Union[Component, ComponentCallable]
|
||||
] = default_overlay_component
|
||||
# A map from a page route to the component to render. Users should use `add_page`. PRIVATE.
|
||||
pages: Dict[str, Component] = {}
|
||||
|
||||
# Background tasks that are currently running
|
||||
# The backend API object. PRIVATE.
|
||||
api: FastAPI = None # type: ignore
|
||||
|
||||
# The state class to use for the app. PRIVATE.
|
||||
state: Optional[Type[BaseState]] = None
|
||||
|
||||
# Class to manage many client states.
|
||||
_state_manager: Optional[StateManager] = None
|
||||
|
||||
# Middleware to add to the app. Users should use `add_middleware`. PRIVATE.
|
||||
middleware: List[Middleware] = []
|
||||
|
||||
# Mapping from a route to event handlers to trigger when the page loads. PRIVATE.
|
||||
load_events: Dict[str, List[Union[EventHandler, EventSpec]]] = {}
|
||||
|
||||
# Admin dashboard to view and manage the database. PRIVATE.
|
||||
admin_dash: Optional[AdminDash] = None
|
||||
|
||||
# The async server name space. PRIVATE.
|
||||
event_namespace: Optional[EventNamespace] = None
|
||||
|
||||
# Background tasks that are currently running. PRIVATE.
|
||||
background_tasks: Set[asyncio.Task] = set()
|
||||
|
||||
# The radix theme for the entire app
|
||||
theme: Optional[Component] = themes.theme(accent_color="blue")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Initialize the app.
|
||||
|
||||
@ -195,25 +213,25 @@ class App(Base):
|
||||
|
||||
# Set up the API.
|
||||
self.api = FastAPI()
|
||||
self.add_cors()
|
||||
self.add_default_endpoints()
|
||||
self._add_cors()
|
||||
self._add_default_endpoints()
|
||||
|
||||
self.setup_state()
|
||||
self._setup_state()
|
||||
|
||||
# Set up the admin dash.
|
||||
self.setup_admin_dash()
|
||||
self._setup_admin_dash()
|
||||
|
||||
def enable_state(self) -> None:
|
||||
def _enable_state(self) -> None:
|
||||
"""Enable state for the app."""
|
||||
if not self.state:
|
||||
self.state = State
|
||||
self.setup_state()
|
||||
self._setup_state()
|
||||
|
||||
def setup_state(self) -> None:
|
||||
def _setup_state(self) -> None:
|
||||
"""Set up the state for the app.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If custom `sio` does not use `async_mode='asgi'`.
|
||||
RuntimeError: If the socket server is invalid.
|
||||
"""
|
||||
if not self.state:
|
||||
return
|
||||
@ -244,7 +262,6 @@ class App(Base):
|
||||
|
||||
# Create the socket app. Note event endpoint constant replaces the default 'socket.io' path.
|
||||
socket_app = ASGIApp(self.sio, socketio_path="")
|
||||
|
||||
namespace = config.get_event_namespace()
|
||||
|
||||
# Create the event namespace and attach the main app. Not related to any paths.
|
||||
@ -271,12 +288,12 @@ class App(Base):
|
||||
"""
|
||||
return self.api
|
||||
|
||||
def add_default_endpoints(self):
|
||||
def _add_default_endpoints(self):
|
||||
"""Add default api endpoints (ping)."""
|
||||
# To test the server.
|
||||
self.api.get(str(constants.Endpoint.PING))(ping)
|
||||
|
||||
def add_optional_endpoints(self):
|
||||
def _add_optional_endpoints(self):
|
||||
"""Add optional api endpoints (_upload)."""
|
||||
# To upload files.
|
||||
if Upload.is_used:
|
||||
@ -289,7 +306,7 @@ class App(Base):
|
||||
name="uploaded_files",
|
||||
)
|
||||
|
||||
def add_cors(self):
|
||||
def _add_cors(self):
|
||||
"""Add CORS middleware to the app."""
|
||||
self.api.add_middleware(
|
||||
cors.CORSMiddleware,
|
||||
@ -313,7 +330,7 @@ class App(Base):
|
||||
raise ValueError("The state manager has not been initialized.")
|
||||
return self._state_manager
|
||||
|
||||
async def preprocess(self, state: BaseState, event: Event) -> StateUpdate | None:
|
||||
async def _preprocess(self, state: BaseState, event: Event) -> StateUpdate | None:
|
||||
"""Preprocess the event.
|
||||
|
||||
This is where middleware can modify the event before it is processed.
|
||||
@ -337,7 +354,7 @@ class App(Base):
|
||||
if out is not None:
|
||||
return out # type: ignore
|
||||
|
||||
async def postprocess(
|
||||
async def _postprocess(
|
||||
self, state: BaseState, event: Event, update: StateUpdate
|
||||
) -> StateUpdate:
|
||||
"""Postprocess the event.
|
||||
@ -468,14 +485,14 @@ class App(Base):
|
||||
# Ensure state is enabled if this page uses state.
|
||||
if self.state is None:
|
||||
if on_load or component._has_event_triggers():
|
||||
self.enable_state()
|
||||
self._enable_state()
|
||||
else:
|
||||
for var in component._get_vars(include_children=True):
|
||||
if not var._var_data:
|
||||
continue
|
||||
if not var._var_data.state:
|
||||
continue
|
||||
self.enable_state()
|
||||
self._enable_state()
|
||||
break
|
||||
|
||||
component = OverlayFragment.create(component)
|
||||
@ -580,7 +597,7 @@ class App(Base):
|
||||
"""Define a custom 404 page for any url having no match.
|
||||
|
||||
If there is no page defined on 'index' route, add the 404 page to it.
|
||||
If there is no global catchall defined, add the 404 page with a catchall
|
||||
If there is no global catchall defined, add the 404 page with a catchall.
|
||||
|
||||
Args:
|
||||
component: The component to display at the page.
|
||||
@ -602,7 +619,7 @@ class App(Base):
|
||||
meta=meta,
|
||||
)
|
||||
|
||||
def setup_admin_dash(self):
|
||||
def _setup_admin_dash(self):
|
||||
"""Setup the admin dash."""
|
||||
# Get the admin dash.
|
||||
admin_dash = self.admin_dash
|
||||
@ -625,14 +642,14 @@ class App(Base):
|
||||
|
||||
admin.mount_to(self.api)
|
||||
|
||||
def get_frontend_packages(self, imports: Dict[str, set[ImportVar]]):
|
||||
def _get_frontend_packages(self, imports: Dict[str, set[ImportVar]]):
|
||||
"""Gets the frontend packages to be installed and filters out the unnecessary ones.
|
||||
|
||||
Args:
|
||||
imports: A dictionary containing the imports used in the current page.
|
||||
|
||||
Example:
|
||||
>>> get_frontend_packages({"react": "16.14.0", "react-dom": "16.14.0"})
|
||||
>>> _get_frontend_packages({"react": "16.14.0", "react-dom": "16.14.0"})
|
||||
"""
|
||||
page_imports = {
|
||||
i
|
||||
@ -715,19 +732,6 @@ class App(Base):
|
||||
for k, component in self.pages.items():
|
||||
self.pages[k] = self._add_overlay_to_component(component)
|
||||
|
||||
def compile(self):
|
||||
"""compile_() is the new function for performing compilation.
|
||||
Reflex framework will call it automatically as needed.
|
||||
"""
|
||||
console.deprecate(
|
||||
feature_name="app.compile()",
|
||||
reason="Explicit calls to app.compile() are not needed."
|
||||
" Method will be removed in 0.4.0",
|
||||
deprecation_version="0.3.8",
|
||||
removal_version="0.5.0",
|
||||
)
|
||||
return
|
||||
|
||||
def _apply_decorated_pages(self):
|
||||
"""Add @rx.page decorated pages to the app.
|
||||
|
||||
@ -741,7 +745,7 @@ class App(Base):
|
||||
for render, kwargs in DECORATED_PAGES[get_config().app_name]:
|
||||
self.add_page(render, **kwargs)
|
||||
|
||||
def compile_(self, export: bool = False):
|
||||
def _compile(self, export: bool = False):
|
||||
"""Compile the app and output it to the pages folder.
|
||||
|
||||
Args:
|
||||
@ -755,7 +759,7 @@ class App(Base):
|
||||
self.add_custom_404_page()
|
||||
|
||||
# Add the optional endpoints (_upload)
|
||||
self.add_optional_endpoints()
|
||||
self._add_optional_endpoints()
|
||||
|
||||
if not self._should_compile():
|
||||
return
|
||||
@ -953,7 +957,7 @@ class App(Base):
|
||||
progress.stop()
|
||||
|
||||
# Install frontend packages.
|
||||
self.get_frontend_packages(all_imports)
|
||||
self._get_frontend_packages(all_imports)
|
||||
|
||||
# Setup the next.config.js
|
||||
transpile_packages = [
|
||||
@ -1028,7 +1032,7 @@ class App(Base):
|
||||
handler=handler, state=substate, payload=event.payload
|
||||
):
|
||||
# Postprocess the event.
|
||||
update = await self.postprocess(state, event, update)
|
||||
update = await self._postprocess(state, event, update)
|
||||
|
||||
# Send the update to the client.
|
||||
await self.event_namespace.emit_update(
|
||||
@ -1079,7 +1083,7 @@ async def process(
|
||||
state.router = RouterData(router_data)
|
||||
|
||||
# Preprocess the event.
|
||||
update = await app.preprocess(state, event)
|
||||
update = await app._preprocess(state, event)
|
||||
|
||||
# If there was an update, yield it.
|
||||
if update is not None:
|
||||
@ -1095,7 +1099,7 @@ async def process(
|
||||
# Process the event synchronously.
|
||||
async for update in state._process(event):
|
||||
# Postprocess the event.
|
||||
update = await app.postprocess(state, event, update)
|
||||
update = await app._postprocess(state, event, update)
|
||||
|
||||
# Yield the update.
|
||||
yield update
|
||||
@ -1216,7 +1220,7 @@ def upload(app: App):
|
||||
async with app.state_manager.modify_state(event.substate_token) as state:
|
||||
async for update in state._process(event):
|
||||
# Postprocess the event.
|
||||
update = await app.postprocess(state, event, update)
|
||||
update = await app._postprocess(state, event, update)
|
||||
yield update.json() + "\n"
|
||||
|
||||
# Stream updates to client
|
||||
|
148
reflex/app.pyi
148
reflex/app.pyi
@ -1,148 +0,0 @@
|
||||
""" Generated with stubgen from mypy, then manually edited, do not regen."""
|
||||
|
||||
import asyncio
|
||||
from fastapi import FastAPI
|
||||
from fastapi import UploadFile as UploadFile
|
||||
from reflex import constants as constants
|
||||
from reflex.admin import AdminDash as AdminDash
|
||||
from reflex.base import Base as Base
|
||||
from reflex.compiler import compiler as compiler
|
||||
from reflex.components import connection_modal as connection_modal
|
||||
from reflex.components.component import (
|
||||
Component as Component,
|
||||
ComponentStyle as ComponentStyle,
|
||||
)
|
||||
from reflex.components.base.fragment import Fragment as Fragment
|
||||
from reflex.config import get_config as get_config
|
||||
from reflex.event import (
|
||||
Event as Event,
|
||||
EventHandler as EventHandler,
|
||||
EventSpec as EventSpec,
|
||||
)
|
||||
from reflex.middleware import (
|
||||
HydrateMiddleware as HydrateMiddleware,
|
||||
Middleware as Middleware,
|
||||
)
|
||||
from reflex.model import Model as Model
|
||||
from reflex.page import DECORATED_PAGES as DECORATED_PAGES
|
||||
from reflex.route import (
|
||||
catchall_in_route as catchall_in_route,
|
||||
catchall_prefix as catchall_prefix,
|
||||
get_route_args as get_route_args,
|
||||
verify_route_validity as verify_route_validity,
|
||||
)
|
||||
from reflex.state import (
|
||||
State as State,
|
||||
BaseState as BaseState,
|
||||
StateManager as StateManager,
|
||||
StateUpdate as StateUpdate,
|
||||
)
|
||||
from reflex.utils import (
|
||||
console as console,
|
||||
format as format,
|
||||
prerequisites as prerequisites,
|
||||
types as types,
|
||||
)
|
||||
from socketio import ASGIApp, AsyncNamespace, AsyncServer
|
||||
from typing import (
|
||||
Any,
|
||||
AsyncContextManager,
|
||||
AsyncIterator,
|
||||
Callable,
|
||||
Coroutine,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Set,
|
||||
Type,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
|
||||
ComponentCallable = Callable[[], Component]
|
||||
Reducer = Callable[[Event], Coroutine[Any, Any, StateUpdate]]
|
||||
|
||||
def default_overlay_component() -> Component: ...
|
||||
|
||||
class OverlayFragment(Fragment):
|
||||
@overload
|
||||
@classmethod
|
||||
def create(cls, *children, **props) -> "OverlayFragment": ... # type: ignore
|
||||
|
||||
class App(Base):
|
||||
pages: Dict[str, Component]
|
||||
stylesheets: List[str]
|
||||
api: FastAPI
|
||||
sio: Optional[AsyncServer]
|
||||
socket_app: Optional[ASGIApp]
|
||||
state: Type[BaseState]
|
||||
state_manager: StateManager
|
||||
style: ComponentStyle
|
||||
middleware: List[Middleware]
|
||||
load_events: Dict[str, List[Union[EventHandler, EventSpec]]]
|
||||
admin_dash: Optional[AdminDash]
|
||||
event_namespace: Optional[AsyncNamespace]
|
||||
overlay_component: Optional[Union[Component, ComponentCallable]]
|
||||
background_tasks: Set[asyncio.Task] = set()
|
||||
def __init__(
|
||||
self,
|
||||
stylesheets: Optional[List[str]] = None,
|
||||
style: Optional[ComponentStyle] = None,
|
||||
admin_dash: Optional[AdminDash] = None,
|
||||
overlay_component: Optional[Union[Component, ComponentCallable]] = None,
|
||||
**kwargs
|
||||
) -> None: ...
|
||||
def __call__(self) -> FastAPI: ...
|
||||
def enable_state(self) -> None: ...
|
||||
def add_default_endpoints(self) -> None: ...
|
||||
def add_optional_endpoints(self): ...
|
||||
def add_cors(self) -> None: ...
|
||||
async def preprocess(self, state: State, event: Event) -> StateUpdate | None: ...
|
||||
async def postprocess(
|
||||
self, state: State, event: Event, update: StateUpdate
|
||||
) -> StateUpdate: ...
|
||||
def add_middleware(self, middleware: Middleware, index: int | None = ...): ...
|
||||
def add_page(
|
||||
self,
|
||||
component: Component | ComponentCallable,
|
||||
route: str | None = ...,
|
||||
title: str = ...,
|
||||
description: str = ...,
|
||||
image=...,
|
||||
on_load: EventHandler | EventSpec | list[EventHandler | EventSpec] | None = ...,
|
||||
meta: list[dict[str, str]] = ...,
|
||||
script_tags: list[Component] | None = ...,
|
||||
): ...
|
||||
def get_load_events(self, route: str) -> list[EventHandler | EventSpec]: ...
|
||||
def add_custom_404_page(
|
||||
self,
|
||||
component: Component | ComponentCallable | None = ...,
|
||||
title: str = ...,
|
||||
image: str = ...,
|
||||
description: str = ...,
|
||||
on_load: EventHandler | EventSpec | list[EventHandler | EventSpec] | None = ...,
|
||||
meta: list[dict[str, str]] = ...,
|
||||
): ...
|
||||
def setup_admin_dash(self) -> None: ...
|
||||
def get_frontend_packages(self, imports: Dict[str, str]): ...
|
||||
def compile(self) -> None: ...
|
||||
def compile_(self) -> None: ...
|
||||
def modify_state(self, token: str) -> AsyncContextManager[State]: ...
|
||||
def _setup_overlay_component(self) -> None: ...
|
||||
def _process_background(
|
||||
self, state: State, event: Event
|
||||
) -> asyncio.Task | None: ...
|
||||
|
||||
def process(
|
||||
app: App, event: Event, sid: str, headers: Dict, client_ip: str
|
||||
) -> AsyncIterator[StateUpdate]: ...
|
||||
async def ping() -> str: ...
|
||||
def upload(app: App): ...
|
||||
|
||||
class EventNamespace(AsyncNamespace):
|
||||
app: App
|
||||
def __init__(self, namespace: str, app: App) -> None: ...
|
||||
def on_connect(self, sid, environ) -> None: ...
|
||||
def on_disconnect(self, sid) -> None: ...
|
||||
async def on_event(self, sid, data) -> None: ...
|
||||
async def on_ping(self, sid) -> None: ...
|
@ -15,7 +15,7 @@ app = getattr(app_module, constants.CompileVars.APP)
|
||||
# For py3.8 and py3.9 compatibility when redis is used, we MUST add any decorator pages
|
||||
# before compiling the app in a thread to avoid event loop error (REF-2172).
|
||||
app._apply_decorated_pages()
|
||||
compile_future = ThreadPoolExecutor(max_workers=1).submit(app.compile_)
|
||||
compile_future = ThreadPoolExecutor(max_workers=1).submit(app._compile)
|
||||
compile_future.add_done_callback(
|
||||
# Force background compile errors to print eagerly
|
||||
lambda f: f.result()
|
||||
|
@ -135,29 +135,47 @@ class DBConfig(Base):
|
||||
|
||||
|
||||
class Config(Base):
|
||||
"""A Reflex config."""
|
||||
"""The config defines runtime settings for the app.
|
||||
|
||||
By default, the config is defined in an `rxconfig.py` file in the root of the app.
|
||||
|
||||
```python
|
||||
# rxconfig.py
|
||||
import reflex as rx
|
||||
|
||||
config = rx.Config(
|
||||
app_name="myapp",
|
||||
api_url="http://localhost:8000",
|
||||
)
|
||||
```
|
||||
|
||||
Every config value can be overridden by an environment variable with the same name in uppercase.
|
||||
For example, `db_url` can be overridden by setting the `DB_URL` environment variable.
|
||||
|
||||
See the [configuration](https://reflex.dev/docs/getting-started/configuration/) docs for more info.
|
||||
"""
|
||||
|
||||
class Config:
|
||||
"""Pydantic config for the config."""
|
||||
|
||||
validate_assignment = True
|
||||
|
||||
# The name of the app.
|
||||
# The name of the app (should match the name of the app directory).
|
||||
app_name: str
|
||||
|
||||
# The log level to use.
|
||||
loglevel: constants.LogLevel = constants.LogLevel.INFO
|
||||
|
||||
# The port to run the frontend on.
|
||||
# The port to run the frontend on. NOTE: When running in dev mode, the next available port will be used if this is taken.
|
||||
frontend_port: int = 3000
|
||||
|
||||
# The path to run the frontend on.
|
||||
# The path to run the frontend on. For example, "/app" will run the frontend on http://localhost:3000/app
|
||||
frontend_path: str = ""
|
||||
|
||||
# The port to run the backend on.
|
||||
# The port to run the backend on. NOTE: When running in dev mode, the next available port will be used if this is taken.
|
||||
backend_port: int = 8000
|
||||
|
||||
# The backend url the frontend will connect to.
|
||||
# The backend url the frontend will connect to. This must be updated if the backend is hosted elsewhere, or in production.
|
||||
api_url: str = f"http://localhost:{backend_port}"
|
||||
|
||||
# The url the frontend will be hosted on.
|
||||
@ -166,10 +184,10 @@ class Config(Base):
|
||||
# The url the backend will be hosted on.
|
||||
backend_host: str = "0.0.0.0"
|
||||
|
||||
# The database url.
|
||||
# The database url used by rx.Model.
|
||||
db_url: Optional[str] = "sqlite:///reflex.db"
|
||||
|
||||
# The redis url.
|
||||
# The redis url
|
||||
redis_url: Optional[str] = None
|
||||
|
||||
# Telemetry opt-in.
|
||||
@ -190,9 +208,6 @@ class Config(Base):
|
||||
# Whether to enable or disable nextJS gzip compression.
|
||||
next_compression: bool = True
|
||||
|
||||
# The event namespace for ws connection
|
||||
event_namespace: Optional[str] = None
|
||||
|
||||
# Additional frontend packages to install.
|
||||
frontend_packages: List[str] = []
|
||||
|
||||
@ -216,9 +231,6 @@ class Config(Base):
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Check for deprecated values.
|
||||
self.check_deprecated_values(**kwargs)
|
||||
|
||||
# Update the config from environment variables.
|
||||
env_kwargs = self.update_from_env()
|
||||
for key, env_value in env_kwargs.items():
|
||||
@ -238,29 +250,8 @@ class Config(Base):
|
||||
"""
|
||||
return ".".join([self.app_name, self.app_name])
|
||||
|
||||
@staticmethod
|
||||
def check_deprecated_values(**kwargs):
|
||||
"""Check for deprecated config values.
|
||||
|
||||
Args:
|
||||
**kwargs: The kwargs passed to the config.
|
||||
|
||||
Raises:
|
||||
ValueError: If a deprecated config value is found.
|
||||
"""
|
||||
if "db_config" in kwargs:
|
||||
raise ValueError("db_config is deprecated - use db_url instead")
|
||||
if "admin_dash" in kwargs:
|
||||
raise ValueError(
|
||||
"admin_dash is deprecated in the config - pass it as a param to rx.App instead"
|
||||
)
|
||||
if "env_path" in kwargs:
|
||||
raise ValueError(
|
||||
"env_path is deprecated - use environment variables instead"
|
||||
)
|
||||
|
||||
def update_from_env(self) -> dict[str, Any]:
|
||||
"""Update the config from environment variables.
|
||||
"""Update the config values based on set environment variables.
|
||||
|
||||
Returns:
|
||||
The updated config values.
|
||||
@ -300,20 +291,11 @@ class Config(Base):
|
||||
return updated_values
|
||||
|
||||
def get_event_namespace(self) -> str:
|
||||
"""Get the websocket event namespace.
|
||||
"""Get the path that the backend Websocket server lists on.
|
||||
|
||||
Returns:
|
||||
The namespace for websocket.
|
||||
"""
|
||||
if self.event_namespace:
|
||||
console.deprecate(
|
||||
feature_name="Passing event_namespace in the config",
|
||||
reason="",
|
||||
deprecation_version="0.3.5",
|
||||
removal_version="0.5.0",
|
||||
)
|
||||
return f'/{self.event_namespace.strip("/")}'
|
||||
|
||||
event_url = constants.Endpoint.EVENT.get_url()
|
||||
return urllib.parse.urlsplit(event_url).path
|
||||
|
||||
|
@ -157,7 +157,7 @@ def _run(
|
||||
if prerequisites.needs_reinit(frontend=frontend):
|
||||
_init(name=config.app_name, loglevel=loglevel)
|
||||
|
||||
# If something is running on the ports, ask the user if they want to kill or change it.
|
||||
# Find the next available open port.
|
||||
if frontend and processes.is_process_on_port(frontend_port):
|
||||
frontend_port = processes.change_port(frontend_port, "frontend")
|
||||
|
||||
|
@ -1909,7 +1909,7 @@ class OnLoadInternalState(State):
|
||||
Returns:
|
||||
The list of events to queue for on load handling.
|
||||
"""
|
||||
# Do not app.compile_()! It should be already compiled by now.
|
||||
# Do not app._compile()! It should be already compiled by now.
|
||||
app = getattr(prerequisites.get_app(), constants.CompileVars.APP)
|
||||
load_events = app.get_load_events(self.router.page.path)
|
||||
if not load_events:
|
||||
@ -1927,8 +1927,45 @@ class OnLoadInternalState(State):
|
||||
|
||||
|
||||
class ComponentState(Base):
|
||||
"""The base class for a State that is copied for each Component associated with it."""
|
||||
"""Base class to allow for the creation of a state instance per component.
|
||||
|
||||
This allows for the bundling of UI and state logic into a single class,
|
||||
where each instance has a separate instance of the state.
|
||||
|
||||
Subclass this class and define vars and event handlers in the traditional way.
|
||||
Then define a `get_component` method that returns the UI for the component instance.
|
||||
|
||||
See the full [docs](https://reflex.dev/docs/substates/component-state/) for more.
|
||||
|
||||
Basic example:
|
||||
```python
|
||||
# Subclass ComponentState and define vars and event handlers.
|
||||
class Counter(rx.ComponentState):
|
||||
# Define vars that change.
|
||||
count: int = 0
|
||||
|
||||
# Define event handlers.
|
||||
def increment(self):
|
||||
self.count += 1
|
||||
|
||||
def decrement(self):
|
||||
self.count -= 1
|
||||
|
||||
@classmethod
|
||||
def get_component(cls, **props):
|
||||
# Access the state vars and event handlers using `cls`.
|
||||
return rx.hstack(
|
||||
rx.button("Decrement", on_click=cls.decrement),
|
||||
rx.text(cls.count),
|
||||
rx.button("Increment", on_click=cls.increment),
|
||||
**props,
|
||||
)
|
||||
|
||||
counter = Counter.create()
|
||||
```
|
||||
"""
|
||||
|
||||
# The number of components created from this class.
|
||||
_per_component_state_instance_count: ClassVar[int] = 0
|
||||
|
||||
@classmethod
|
||||
|
@ -42,7 +42,6 @@ import reflex.utils.prerequisites
|
||||
import reflex.utils.processes
|
||||
from reflex.state import (
|
||||
BaseState,
|
||||
State,
|
||||
StateManagerMemory,
|
||||
StateManagerRedis,
|
||||
reload_state_module,
|
||||
@ -598,7 +597,7 @@ class AppHarness:
|
||||
await self.state_manager.close()
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
async def modify_state(self, token: str) -> AsyncIterator[State]:
|
||||
async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
|
||||
"""Modify the state associated with the given token and send update to frontend.
|
||||
|
||||
Args:
|
||||
|
@ -240,7 +240,7 @@ def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
|
||||
# For py3.8 and py3.9 compatibility when redis is used, we MUST add any decorator pages
|
||||
# before compiling the app in a thread to avoid event loop error (REF-2172).
|
||||
app._apply_decorated_pages()
|
||||
app.compile_(export=export)
|
||||
app._compile(export=export)
|
||||
return app_module
|
||||
|
||||
|
||||
|
@ -34,7 +34,7 @@ PWD = Path(".").resolve()
|
||||
|
||||
EXCLUDED_FILES = [
|
||||
"__init__.py",
|
||||
"app.py",
|
||||
# "app.py",
|
||||
"component.py",
|
||||
"bare.py",
|
||||
"foreach.py",
|
||||
|
@ -260,6 +260,7 @@ def test_add_page_set_route_dynamic(index_page, windows_platform: bool):
|
||||
windows_platform: Whether the system is windows.
|
||||
"""
|
||||
app = App(state=EmptyState)
|
||||
assert app.state is not None
|
||||
route = "/test/[dynamic]"
|
||||
if windows_platform:
|
||||
route.lstrip("/").replace("/", "\\")
|
||||
@ -953,6 +954,7 @@ async def test_dynamic_route_var_route_change_completed_on_load(
|
||||
if windows_platform:
|
||||
route.lstrip("/").replace("/", "\\")
|
||||
app = app_module_mock.app = App(state=DynamicState)
|
||||
assert app.state is not None
|
||||
assert arg_name not in app.state.vars
|
||||
app.add_page(index_page, route=route, on_load=DynamicState.on_load) # type: ignore
|
||||
assert arg_name in app.state.vars
|
||||
@ -1147,7 +1149,7 @@ async def test_process_events(mocker, token: str):
|
||||
"ip": "127.0.0.1",
|
||||
}
|
||||
app = App(state=GenState)
|
||||
mocker.patch.object(app, "postprocess", AsyncMock())
|
||||
mocker.patch.object(app, "_postprocess", AsyncMock())
|
||||
event = Event(
|
||||
token=token, name="gen_state.go", payload={"c": 5}, router_data=router_data
|
||||
)
|
||||
@ -1156,7 +1158,7 @@ async def test_process_events(mocker, token: str):
|
||||
pass
|
||||
|
||||
assert (await app.state_manager.get_state(event.substate_token)).value == 5
|
||||
assert app.postprocess.call_count == 6
|
||||
assert app._postprocess.call_count == 6
|
||||
|
||||
if isinstance(app.state_manager, StateManagerRedis):
|
||||
await app.state_manager.close()
|
||||
@ -1236,7 +1238,7 @@ def compilable_app(tmp_path) -> Generator[tuple[App, Path], None, None]:
|
||||
web_dir.mkdir(parents=True)
|
||||
(web_dir / "package.json").touch()
|
||||
app = App(theme=None)
|
||||
app.get_frontend_packages = unittest.mock.Mock()
|
||||
app._get_frontend_packages = unittest.mock.Mock()
|
||||
with chdir(app_path):
|
||||
yield app, web_dir
|
||||
|
||||
@ -1249,7 +1251,7 @@ def test_app_wrap_compile_theme(compilable_app):
|
||||
"""
|
||||
app, web_dir = compilable_app
|
||||
app.theme = rx.theme(accent_color="plum")
|
||||
app.compile_()
|
||||
app._compile()
|
||||
app_js_contents = (web_dir / "pages" / "_app.js").read_text()
|
||||
app_js_lines = [
|
||||
line.strip() for line in app_js_contents.splitlines() if line.strip()
|
||||
@ -1299,7 +1301,7 @@ def test_app_wrap_priority(compilable_app):
|
||||
return Fragment1.create(Fragment3.create())
|
||||
|
||||
app.add_page(page)
|
||||
app.compile_()
|
||||
app._compile()
|
||||
app_js_contents = (web_dir / "pages" / "_app.js").read_text()
|
||||
app_js_lines = [
|
||||
line.strip() for line in app_js_contents.splitlines() if line.strip()
|
||||
@ -1371,7 +1373,7 @@ def test_raise_on_state():
|
||||
"""Test that the state is set."""
|
||||
# state kwargs is deprecated, we just make sure the app is created anyway.
|
||||
_app = App(state=State)
|
||||
print(_app.state)
|
||||
assert _app.state is not None
|
||||
assert issubclass(_app.state, State)
|
||||
|
||||
|
||||
@ -1387,7 +1389,7 @@ def test_app_with_optional_endpoints():
|
||||
|
||||
app = App()
|
||||
Upload.is_used = True
|
||||
app.add_optional_endpoints()
|
||||
app._add_optional_endpoints()
|
||||
# TODO: verify the availability of the endpoints in app.api
|
||||
|
||||
|
||||
@ -1395,7 +1397,7 @@ def test_app_state_manager():
|
||||
app = App()
|
||||
with pytest.raises(ValueError):
|
||||
app.state_manager
|
||||
app.enable_state()
|
||||
app._enable_state()
|
||||
assert app.state_manager is not None
|
||||
assert isinstance(app.state_manager, (StateManagerMemory, StateManagerRedis))
|
||||
|
||||
@ -1479,7 +1481,7 @@ def test_app_with_transpile_packages(compilable_app, export):
|
||||
C1.create(), C2.create(), C3.create(), C4.create(), C5.create()
|
||||
)
|
||||
app.add_page(page, route="/")
|
||||
app.compile_(export=export)
|
||||
app._compile(export=export)
|
||||
|
||||
next_config = (web_dir / "next.config.js").read_text()
|
||||
transpile_packages_match = re.search(r"transpilePackages: (\[.*?\])", next_config)
|
||||
|
@ -1,6 +1,5 @@
|
||||
import multiprocessing
|
||||
import os
|
||||
from typing import Any, Dict
|
||||
|
||||
import pytest
|
||||
|
||||
@ -25,25 +24,6 @@ def test_set_app_name(base_config_values):
|
||||
assert config.app_name == base_config_values["app_name"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"param",
|
||||
[
|
||||
"db_config",
|
||||
"admin_dash",
|
||||
"env_path",
|
||||
],
|
||||
)
|
||||
def test_deprecated_params(base_config_values: Dict[str, Any], param):
|
||||
"""Test that deprecated params are removed from the config.
|
||||
|
||||
Args:
|
||||
base_config_values: Config values.
|
||||
param: The deprecated param.
|
||||
"""
|
||||
with pytest.raises(ValueError):
|
||||
rx.Config(**base_config_values, **{param: "test"}) # type: ignore
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"env_var, value",
|
||||
[
|
||||
@ -87,12 +67,6 @@ def test_update_from_env(base_config_values, monkeypatch, env_var, value):
|
||||
{"app_name": "test_app", "api_url": "http://example.com/api"},
|
||||
f"/api{Endpoint.EVENT}",
|
||||
),
|
||||
({"app_name": "test_app", "event_namespace": "/event"}, f"/event"),
|
||||
({"app_name": "test_app", "event_namespace": "event"}, f"/event"),
|
||||
({"app_name": "test_app", "event_namespace": "event/"}, f"/event"),
|
||||
({"app_name": "test_app", "event_namespace": "/_event"}, f"{Endpoint.EVENT}"),
|
||||
({"app_name": "test_app", "event_namespace": "_event"}, f"{Endpoint.EVENT}"),
|
||||
({"app_name": "test_app", "event_namespace": "_event/"}, f"{Endpoint.EVENT}"),
|
||||
],
|
||||
)
|
||||
def test_event_namespace(mocker, kwargs, expected):
|
||||
|
@ -24,7 +24,7 @@ def test_app_harness(tmp_path):
|
||||
|
||||
app = rx.App(state=State)
|
||||
app.add_page(lambda: rx.text("Basic App"), route="/", title="index")
|
||||
app.compile_()
|
||||
app._compile()
|
||||
|
||||
with AppHarness.create(
|
||||
root=tmp_path,
|
||||
|
Loading…
Reference in New Issue
Block a user