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
This commit is contained in:
Masen Furer 2024-02-12 12:21:56 -08:00 committed by GitHub
parent 3df4eac33d
commit 10e8bd010c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 55 additions and 1 deletions

View File

@ -27,6 +27,7 @@ from typing import (
from fastapi import FastAPI, HTTPException, Request, UploadFile
from fastapi.middleware import cors
from fastapi.responses import StreamingResponse
from fastapi.staticfiles import StaticFiles
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
from socketio import ASGIApp, AsyncNamespace, AsyncServer
from starlette_admin.contrib.sqla.admin import Admin
@ -46,7 +47,7 @@ from reflex.components.core.client_side_routing import (
Default404Page,
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.config import get_config
from reflex.event import Event, EventHandler, EventSpec
@ -252,6 +253,13 @@ class App(Base):
if Upload.is_used:
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):
"""Add CORS middleware to the app."""
self.api.add_middleware(

View File

@ -1,6 +1,8 @@
"""A file upload component."""
from __future__ import annotations
import os
from pathlib import Path
from typing import Any, ClassVar, Dict, List, Optional, Union
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()")
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):
"""AppWrap component that provides a dict of selected files by ID via useContext."""

View File

@ -7,6 +7,8 @@ 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
import os
from pathlib import Path
from typing import Any, ClassVar, Dict, List, Optional, Union
from reflex import constants
from reflex.components.chakra.forms.input import Input
@ -27,6 +29,11 @@ def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> BaseVar: ...
@CallableEventSpec
def clear_selected_files(id_: str = DEFAULT_UPLOAD_ID) -> 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):
@overload