Переглянути джерело

Upload Workflow Refactor (#2309)

* upload with StaticFiles

* always create uploaded files folder

* just use /_upload to serve uploaded files

* Upload: update pyi file

* app.py: only mount Upload StaticFiles if the upload component is used
Masen Furer 1 рік тому
батько
коміт
10e8bd010c
3 змінених файлів з 55 додано та 1 видалено
  1. 9 1
      reflex/app.py
  2. 39 0
      reflex/components/core/upload.py
  3. 7 0
      reflex/components/core/upload.pyi

+ 9 - 1
reflex/app.py

@@ -27,6 +27,7 @@ from typing import (
 from fastapi import FastAPI, HTTPException, Request, UploadFile
 from fastapi import FastAPI, HTTPException, Request, UploadFile
 from fastapi.middleware import cors
 from fastapi.middleware import cors
 from fastapi.responses import StreamingResponse
 from fastapi.responses import StreamingResponse
+from fastapi.staticfiles import StaticFiles
 from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
 from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
 from socketio import ASGIApp, AsyncNamespace, AsyncServer
 from socketio import ASGIApp, AsyncNamespace, AsyncServer
 from starlette_admin.contrib.sqla.admin import Admin
 from starlette_admin.contrib.sqla.admin import Admin
@@ -46,7 +47,7 @@ from reflex.components.core.client_side_routing import (
     Default404Page,
     Default404Page,
     wait_for_client_redirect,
     wait_for_client_redirect,
 )
 )
-from reflex.components.core.upload import Upload
+from reflex.components.core.upload import Upload, get_uploaded_files_dir
 from reflex.components.radix import themes
 from reflex.components.radix import themes
 from reflex.config import get_config
 from reflex.config import get_config
 from reflex.event import Event, EventHandler, EventSpec
 from reflex.event import Event, EventHandler, EventSpec
@@ -252,6 +253,13 @@ class App(Base):
         if Upload.is_used:
         if Upload.is_used:
             self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
             self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
 
 
+            # To access uploaded files.
+            self.api.mount(
+                str(constants.Endpoint.UPLOAD),
+                StaticFiles(directory=get_uploaded_files_dir()),
+                name="uploaded_files",
+            )
+
     def add_cors(self):
     def add_cors(self):
         """Add CORS middleware to the app."""
         """Add CORS middleware to the app."""
         self.api.add_middleware(
         self.api.add_middleware(

+ 39 - 0
reflex/components/core/upload.py

@@ -1,6 +1,8 @@
 """A file upload component."""
 """A file upload component."""
 from __future__ import annotations
 from __future__ import annotations
 
 
+import os
+from pathlib import Path
 from typing import Any, ClassVar, Dict, List, Optional, Union
 from typing import Any, ClassVar, Dict, List, Optional, Union
 
 
 from reflex import constants
 from reflex import constants
@@ -92,6 +94,43 @@ def cancel_upload(upload_id: str) -> EventSpec:
     return call_script(f"upload_controllers[{upload_id!r}]?.abort()")
     return call_script(f"upload_controllers[{upload_id!r}]?.abort()")
 
 
 
 
+def get_uploaded_files_dir() -> Path:
+    """Get the directory where uploaded files are stored.
+
+    Returns:
+        The directory where uploaded files are stored.
+    """
+    uploaded_files_dir = Path(
+        os.environ.get("REFLEX_UPLOADED_FILES_DIR", "./uploaded_files")
+    )
+    uploaded_files_dir.mkdir(parents=True, exist_ok=True)
+    return uploaded_files_dir
+
+
+uploaded_files_url_prefix: Var = Var.create_safe(
+    "${getBackendURL(env.UPLOAD)}"
+)._replace(
+    merge_var_data=VarData(  # type: ignore
+        imports={
+            f"/{Dirs.STATE_PATH}": {imports.ImportVar(tag="getBackendURL")},
+            "/env.json": {imports.ImportVar(tag="env", is_default=True)},
+        }
+    )
+)
+
+
+def get_uploaded_file_url(file_path: str) -> str:
+    """Get the URL of an uploaded file.
+
+    Args:
+        file_path: The path of the uploaded file.
+
+    Returns:
+        The URL of the uploaded file to be rendered from the frontend (as a str-encoded Var).
+    """
+    return f"{uploaded_files_url_prefix}/{file_path}"
+
+
 class UploadFilesProvider(Component):
 class UploadFilesProvider(Component):
     """AppWrap component that provides a dict of selected files by ID via useContext."""
     """AppWrap component that provides a dict of selected files by ID via useContext."""
 
 

+ 7 - 0
reflex/components/core/upload.pyi

@@ -7,6 +7,8 @@ from typing import Any, Dict, Literal, Optional, Union, overload
 from reflex.vars import Var, BaseVar, ComputedVar
 from reflex.vars import Var, BaseVar, ComputedVar
 from reflex.event import EventChain, EventHandler, EventSpec
 from reflex.event import EventChain, EventHandler, EventSpec
 from reflex.style import Style
 from reflex.style import Style
+import os
+from pathlib import Path
 from typing import Any, ClassVar, Dict, List, Optional, Union
 from typing import Any, ClassVar, Dict, List, Optional, Union
 from reflex import constants
 from reflex import constants
 from reflex.components.chakra.forms.input import Input
 from reflex.components.chakra.forms.input import Input
@@ -27,6 +29,11 @@ def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> BaseVar: ...
 @CallableEventSpec
 @CallableEventSpec
 def clear_selected_files(id_: str = DEFAULT_UPLOAD_ID) -> EventSpec: ...
 def clear_selected_files(id_: str = DEFAULT_UPLOAD_ID) -> EventSpec: ...
 def cancel_upload(upload_id: str) -> EventSpec: ...
 def cancel_upload(upload_id: str) -> EventSpec: ...
+def get_uploaded_files_dir() -> Path: ...
+
+uploaded_files_url_prefix: Var
+
+def get_uploaded_file_url(file_path: str) -> str: ...
 
 
 class UploadFilesProvider(Component):
 class UploadFilesProvider(Component):
     @overload
     @overload