From 0b1b8ee6394694e5e1da36b3b36d4c1dfbbd07f2 Mon Sep 17 00:00:00 2001 From: benedikt-bartscher <31854409+benedikt-bartscher@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:20:06 +0100 Subject: [PATCH] auto enable /_upload endpoint only if Upload component is used (#2430) --- reflex/app.py | 7 +++++-- reflex/components/core/upload.py | 21 +++++++++++++++++++-- reflex/components/core/upload.pyi | 13 ++++++------- scripts/pyi_generator.py | 14 +++++++++++++- 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/reflex/app.py b/reflex/app.py index 874eb5a30..5b93e02d0 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -43,6 +43,7 @@ from reflex.components.core.client_side_routing import ( Default404Page, wait_for_client_redirect, ) +from reflex.components.core.upload import UploadFilesProvider from reflex.config import get_config from reflex.event import Event, EventHandler, EventSpec from reflex.middleware import HydrateMiddleware, Middleware @@ -180,7 +181,6 @@ class App(Base): # Set up the API. self.api = FastAPI() self.add_cors() - self.add_default_endpoints() if self.state: # Set up the state manager. @@ -242,7 +242,8 @@ class App(Base): self.api.get(str(constants.Endpoint.PING))(ping) # To upload files. - self.api.post(str(constants.Endpoint.UPLOAD))(upload(self)) + if UploadFilesProvider.is_used: + self.api.post(str(constants.Endpoint.UPLOAD))(upload(self)) def add_cors(self): """Add CORS middleware to the app.""" @@ -800,6 +801,8 @@ class App(Base): for future in concurrent.futures.as_completed(write_page_futures): future.result() + self.add_default_endpoints() + @contextlib.asynccontextmanager async def modify_state(self, token: str) -> AsyncIterator[BaseState]: """Modify the state out of band. diff --git a/reflex/components/core/upload.py b/reflex/components/core/upload.py index 1dab30083..9d0b6023f 100644 --- a/reflex/components/core/upload.py +++ b/reflex/components/core/upload.py @@ -1,7 +1,7 @@ """A file upload component.""" from __future__ import annotations -from typing import Any, Dict, List, Optional, Union +from typing import Any, ClassVar, Dict, List, Optional, Union from reflex import constants from reflex.components.chakra.forms.input import Input @@ -98,6 +98,23 @@ class UploadFilesProvider(Component): library = f"/{Dirs.CONTEXTS_PATH}" tag = "UploadFilesProvider" + is_used: ClassVar[bool] = False + + @classmethod + def create(cls, *children, **props) -> Component: + """Create an UploadFilesProvider component. + + Args: + *children: The children of the component. + **props: The properties of the component. + + Returns: + The UploadFilesProvider component. + """ + cls.is_used = True + + return super().create(*children, **props) + class Upload(Component): """A file upload component.""" @@ -192,5 +209,5 @@ class Upload(Component): @staticmethod def _get_app_wrap_components() -> dict[tuple[int, str], Component]: return { - (5, "UploadFilesProvider"): UploadFilesProvider(), + (5, "UploadFilesProvider"): UploadFilesProvider.create(), } diff --git a/reflex/components/core/upload.pyi b/reflex/components/core/upload.pyi index 6984be09c..95fa5c8bf 100644 --- a/reflex/components/core/upload.pyi +++ b/reflex/components/core/upload.pyi @@ -7,7 +7,7 @@ from typing import Any, Dict, Literal, Optional, Union, overload from reflex.vars import Var, BaseVar, ComputedVar from reflex.event import EventChain, EventHandler, EventSpec from reflex.style import Style -from typing import Any, Dict, List, Optional, Union +from typing import Any, ClassVar, Dict, List, Optional, Union from reflex import constants from reflex.components.chakra.forms.input import Input from reflex.components.chakra.layout.box import Box @@ -29,6 +29,8 @@ def clear_selected_files(id_: str = DEFAULT_UPLOAD_ID) -> EventSpec: ... def cancel_upload(upload_id: str) -> EventSpec: ... class UploadFilesProvider(Component): + is_used: ClassVar[bool] = False + @overload @classmethod def create( # type: ignore @@ -87,7 +89,7 @@ class UploadFilesProvider(Component): ] = None, **props ) -> "UploadFilesProvider": - """Create the component. + """Create an UploadFilesProvider component. Args: *children: The children of the component. @@ -97,13 +99,10 @@ class UploadFilesProvider(Component): class_name: The class name for the component. autofocus: Whether the component should take the focus once the page is loaded custom_attrs: custom attribute - **props: The props of the component. + **props: The properties of the component. Returns: - The component. - - Raises: - TypeError: If an invalid child is passed. + The UploadFilesProvider component. """ ... diff --git a/scripts/pyi_generator.py b/scripts/pyi_generator.py index 9dc4141a8..a1a24859b 100644 --- a/scripts/pyi_generator.py +++ b/scripts/pyi_generator.py @@ -245,7 +245,12 @@ def _extract_class_props_as_ast_nodes( # Import from the target class to ensure type hints are resolvable. exec(f"from {target_class.__module__} import *", type_hint_globals) for name, value in target_class.__annotations__.items(): - if name in spec.kwonlyargs or name in EXCLUDED_PROPS or name in all_props: + if ( + name in spec.kwonlyargs + or name in EXCLUDED_PROPS + or name in all_props + or (isinstance(value, str) and "ClassVar" in value) + ): continue all_props.append(name) @@ -559,6 +564,13 @@ class StubGenerator(ast.NodeTransformer): Returns: The modified AnnAssign node (or None). """ + # skip ClassVars + if ( + isinstance(node.annotation, ast.Subscript) + and isinstance(node.annotation.value, ast.Name) + and node.annotation.value.id == "ClassVar" + ): + return node if isinstance(node.target, ast.Name) and node.target.id.startswith("_"): return None if self.current_class in self.classes: