瀏覽代碼

Clean up config and app API (#3197)

Nikhil Rao 1 年之前
父節點
當前提交
7903a1020d

+ 6 - 6
benchmarks/test_benchmark_compile_components.py

@@ -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)

+ 10 - 10
benchmarks/test_benchmark_compile_pages.py

@@ -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()

+ 77 - 73
reflex/app.py

@@ -102,60 +102,78 @@ 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] = {}
-
-    # A list of URLs to stylesheets to include in the app.
-    stylesheets: List[str] = []
+    Every Reflex app needs an app defined in its main module.
 
-    # The backend API object.
-    api: FastAPI = None  # type: ignore
+    ```python
+    # app.py
+    import reflex as rx
 
-    # The Socket.IO AsyncServer.
-    sio: Optional[AsyncServer] = None
+    # Define state and pages
+    ...
 
-    # The state class to use for the app.
-    state: Optional[Type[BaseState]] = None
+    app = rx.App(
+        # Set global level style.
+        style={...},
+        # Set the top level theme.
+        theme=rx.theme(accent_color="blue"),
+    )
+    ```
+    """
 
-    # Class to manage many client states.
-    _state_manager: Optional[StateManager] = None
+    # The global [theme](https://reflex.dev/docs/styling/theming/#theme) for the entire app.
+    theme: Optional[Component] = themes.theme(accent_color="blue")
 
-    # 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] = []
-
-    # 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
+    # A list of URLs to [stylesheets](https://reflex.dev/docs/styling/custom-stylesheets/) to include in the app.
+    stylesheets: List[str] = []
 
-    # 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
-    background_tasks: Set[asyncio.Task] = set()
+    # The backend API object. PRIVATE.
+    api: FastAPI = None  # type: ignore
 
-    # The radix theme for the entire app
-    theme: Optional[Component] = themes.theme(accent_color="blue")
+    # 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()
 
     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

+ 0 - 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: ...

+ 1 - 1
reflex/app_module_for_backend.py

@@ -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()

+ 28 - 46
reflex/config.py

@@ -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
 

+ 1 - 1
reflex/reflex.py

@@ -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")
 

+ 39 - 2
reflex/state.py

@@ -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

+ 1 - 2
reflex/testing.py

@@ -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:

+ 1 - 1
reflex/utils/prerequisites.py

@@ -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
 
 

+ 1 - 1
reflex/utils/pyi_generator.py

@@ -34,7 +34,7 @@ PWD = Path(".").resolve()
 
 EXCLUDED_FILES = [
     "__init__.py",
-    "app.py",
+    # "app.py",
     "component.py",
     "bare.py",
     "foreach.py",

+ 11 - 9
tests/test_app.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)

+ 0 - 26
tests/test_config.py

@@ -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):

+ 1 - 1
tests/test_testing.py

@@ -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,