From ef4f2ec999c947947f48b49dd6cb99d4993a445d Mon Sep 17 00:00:00 2001 From: gitgernit Date: Fri, 21 Nov 2025 12:11:40 +0300 Subject: [PATCH] feat(): storage upload file route --- pyproject.toml | 1 + src/template_project/web_api/entry_point.py | 3 +- .../web_api/routes/storage.py | 28 +++++++++++++++++++ uv.lock | 13 ++++++++- 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 src/template_project/web_api/routes/storage.py diff --git a/pyproject.toml b/pyproject.toml index 1c8117f..8256d2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ dependencies = [ "firebase-admin>=7.1.0", "aioboto3==15.5.0", "prometheus-fastapi-instrumentator>=7.1.0", + "python-multipart>=0.0.20", ] [dependency-groups] diff --git a/src/template_project/web_api/entry_point.py b/src/template_project/web_api/entry_point.py index 55874b8..7b305af 100644 --- a/src/template_project/web_api/entry_point.py +++ b/src/template_project/web_api/entry_point.py @@ -19,7 +19,7 @@ from prometheus_fastapi_instrumentator import Instrumentator from template_project.web_api.configuration import Configuration, load_configuration from template_project.web_api.ioc.make import make_ioc -from template_project.web_api.routes import auth, healthcheck, notification, profile +from template_project.web_api.routes import auth, healthcheck, notification, profile, storage LOG_CONFIG: Final = { "version": 1, @@ -73,6 +73,7 @@ def make_asgi_application( app.include_router(healthcheck.router) app.include_router(profile.router) app.include_router(notification.router) + app.include_router(storage.router) Instrumentator().instrument(app).expose(app) setup_dishka(container=ioc, app=app) diff --git a/src/template_project/web_api/routes/storage.py b/src/template_project/web_api/routes/storage.py new file mode 100644 index 0000000..2e58575 --- /dev/null +++ b/src/template_project/web_api/routes/storage.py @@ -0,0 +1,28 @@ +from io import BytesIO + +from dishka import FromDishka +from dishka.integrations.fastapi import DishkaRoute +from fastapi import APIRouter, Depends, UploadFile, status +from fastapi.responses import JSONResponse +from fastapi.security import HTTPBearer +from uuid_utils.compat import uuid7 + +from template_project.application.common.file_storage import FileStorage + +security = HTTPBearer() +router = APIRouter(route_class=DishkaRoute, tags=["Storage"], dependencies=[Depends(security)]) + + +@router.post("/storage/upload_file") +async def upload_file( + file: UploadFile, + file_storage: FromDishka[FileStorage], +) -> JSONResponse: + path = str(uuid7()) + file_content = await file.read() + file_io = BytesIO(file_content) + await file_storage.upload(path=path, image=file_io) + return JSONResponse( + status_code=status.HTTP_200_OK, + content={"path": path}, + ) diff --git a/uv.lock b/uv.lock index 243a25f..474d2eb 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14'", @@ -1735,6 +1735,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -1943,6 +1952,7 @@ dependencies = [ { name = "httpx" }, { name = "prometheus-fastapi-instrumentator" }, { name = "psycopg", extra = ["binary"] }, + { name = "python-multipart" }, { name = "sqlalchemy" }, { name = "uuid-utils" }, { name = "uvicorn" }, @@ -1991,6 +2001,7 @@ requires-dist = [ { name = "httpx", specifier = "==0.28.1" }, { name = "prometheus-fastapi-instrumentator", specifier = ">=7.1.0" }, { name = "psycopg", extras = ["binary"], specifier = ">=3.2.12" }, + { name = "python-multipart", specifier = ">=0.0.20" }, { name = "sqlalchemy", specifier = "==2.0.44" }, { name = "uuid-utils", specifier = "==0.11.1" }, { name = "uvicorn", specifier = "==0.37.0" },