From b78baa4502c7132ee7e634a52a4256d46cd77b43 Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Sun, 5 May 2024 15:39:11 -0300 Subject: [PATCH 01/21] feat: gitea workflow push repo in v2 if main and to staging if dev --- .gitea/workflows/main.yml | 22 ++++++++++++++-------- .gitignore | 3 ++- README.md | 15 +++++++++++++++ 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 587c919..3a081ba 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -1,8 +1,5 @@ -name: 'deploy' -on: - push: - branches: - - main +name: 'Deploy on Push' +on: [push] jobs: deploy: @@ -21,10 +18,19 @@ jobs: id: branch_name run: echo "::set-output name=branch::$(echo ${GITHUB_REF##*/})" - - name: Push to dokku + - name: Push to dokku for main branch + if: github.ref == 'refs/heads/main' uses: dokku/github-action@master with: branch: 'main' - git_remote_url: 'ssh://dokku@staging.discours.io:22/${{ steps.repo_name.outputs.repo }}' + git_remote_url: 'ssh://dokku@v2.discours.io:22/uploader' ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }} - git_push_flags: '--force' + + - name: Push to dokku for staging branch + if: github.ref == 'refs/heads/dev' + uses: dokku/github-action@master + with: + branch: 'main' + git_remote_url: 'ssh://dokku@stagging.discours.io:22/uploader' + ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }} + diff --git a/.gitignore b/.gitignore index 871cfd6..f2f4f01 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ __pycache__ .ruff_cache .pytest_cache .venv -*.gz.tar \ No newline at end of file +*.gz.tar +DELETEME diff --git a/README.md b/README.md index fdf659d..a0ae99d 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,18 @@ poetry env use .venv/bin/python3.12 poetry update poetry run python main.py ``` +### Интеграция в Core + +Добавить в nginx.conf.sigil ссылку на /upload +``` +# Custom location block for /upload +location /connect/ { + http://upload-8080/; + {{ $proxy_settings }} + {{ $gzip_settings }} + {{ $cors_headers_options }} + {{ $cors_headers_post }} + {{ $cors_headers_get }} +} +``` +при такой структуре ссылка будет core.domain.com/upload \ No newline at end of file From c9998f6bd07e763603f2569e33479c6e914b2565 Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Sun, 5 May 2024 15:45:19 -0300 Subject: [PATCH 02/21] debuig: secret key not found From 4d1535ff63d46222d297027e8492963986bd5743 Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Sun, 5 May 2024 16:01:52 -0300 Subject: [PATCH 03/21] debug: hostname for v2 is IP --- .gitea/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 3a081ba..f7a3979 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -23,7 +23,7 @@ jobs: uses: dokku/github-action@master with: branch: 'main' - git_remote_url: 'ssh://dokku@v2.discours.io:22/uploader' + git_remote_url: 'ssh://dokku@5.255.100.133:22/uploader' ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }} - name: Push to dokku for staging branch From f964aa380eaaf108f87fd3e7670aabb1d7f736de Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Sun, 5 May 2024 16:19:27 -0300 Subject: [PATCH 04/21] feat: working tree --- .gitea/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index f7a3979..3a081ba 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -23,7 +23,7 @@ jobs: uses: dokku/github-action@master with: branch: 'main' - git_remote_url: 'ssh://dokku@5.255.100.133:22/uploader' + git_remote_url: 'ssh://dokku@v2.discours.io:22/uploader' ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }} - name: Push to dokku for staging branch From cc36b46fd73862844e712b4388db890cb445eb81 Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Sun, 5 May 2024 16:27:22 -0300 Subject: [PATCH 05/21] feat: dev branch deploying on staging --- .gitea/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 3a081ba..114f497 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -31,6 +31,6 @@ jobs: uses: dokku/github-action@master with: branch: 'main' - git_remote_url: 'ssh://dokku@stagging.discours.io:22/uploader' + git_remote_url: 'ssh://dokku@staging.discours.io:22/uploader' ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }} From 2ba6aa64d29b60dbc5a2a1adb136bcf45f4f375c Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Sun, 5 May 2024 22:34:07 -0300 Subject: [PATCH 06/21] debug: route to / instead of /upload --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index dd8e4ea..51c0360 100644 --- a/main.py +++ b/main.py @@ -59,7 +59,7 @@ async def upload_handler(request: Request): return JSONResponse({'error': 'Failed to upload file'}, status_code=500) routes = [ - Route('/upload', upload_handler, methods=['POST']), + Route('/', upload_handler, methods=['POST']), ] app = Starlette(debug=True, routes=routes) From 06a912f1a966643747c833c2f5077c47e6b375ed Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Mon, 6 May 2024 02:23:00 -0300 Subject: [PATCH 07/21] debug: route is /upload for testing --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 51c0360..dd8e4ea 100644 --- a/main.py +++ b/main.py @@ -59,7 +59,7 @@ async def upload_handler(request: Request): return JSONResponse({'error': 'Failed to upload file'}, status_code=500) routes = [ - Route('/', upload_handler, methods=['POST']), + Route('/upload', upload_handler, methods=['POST']), ] app = Starlette(debug=True, routes=routes) From ae122412f4ee002d3e40121b8c46963a105aab9d Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Mon, 6 May 2024 02:37:08 -0300 Subject: [PATCH 08/21] debug: add GET results in / , /upload_test and /upload/test --- main.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/main.py b/main.py index dd8e4ea..1443b0d 100644 --- a/main.py +++ b/main.py @@ -58,8 +58,15 @@ async def upload_handler(request: Request): print(e) return JSONResponse({'error': 'Failed to upload file'}, status_code=500) +async def home(request: Request): + return JSONResponse({'message': 'Hello World!'}) + routes = [ + Route('/', home, methods=['GET']), + Route('/upload_test', home, methods=['GET']), + Route('/upload/test', home, methods=['GET']), Route('/upload', upload_handler, methods=['POST']), + ] app = Starlette(debug=True, routes=routes) From 0d430c9f654d52b40bc461ceb856cf71b5d84f56 Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Mon, 6 May 2024 02:42:31 -0300 Subject: [PATCH 09/21] debug: add GET results in /upload and / --- main.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/main.py b/main.py index 1443b0d..9616306 100644 --- a/main.py +++ b/main.py @@ -62,11 +62,7 @@ async def home(request: Request): return JSONResponse({'message': 'Hello World!'}) routes = [ - Route('/', home, methods=['GET']), - Route('/upload_test', home, methods=['GET']), - Route('/upload/test', home, methods=['GET']), - Route('/upload', upload_handler, methods=['POST']), - + Route('/upload', home, methods=['GET']), ] app = Starlette(debug=True, routes=routes) From f2d8883ba5aa28dfc1c3bc2463a48c82a0b087bb Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Mon, 6 May 2024 03:27:46 -0300 Subject: [PATCH 10/21] debug: GET pointing to root / --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 9616306..5b5e976 100644 --- a/main.py +++ b/main.py @@ -62,7 +62,7 @@ async def home(request: Request): return JSONResponse({'message': 'Hello World!'}) routes = [ - Route('/upload', home, methods=['GET']), + Route('/', home, methods=['GET']) ] app = Starlette(debug=True, routes=routes) From 0934b583da0b382b26c8e3f52236374db1e54949 Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Mon, 6 May 2024 03:38:59 -0300 Subject: [PATCH 11/21] debug: change port to 8080 --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 5b5e976..bfda5bd 100644 --- a/main.py +++ b/main.py @@ -69,5 +69,5 @@ app = Starlette(debug=True, routes=routes) if __name__ == "__main__": import uvicorn - uvicorn.run(app, host='0.0.0.0', port=80) + uvicorn.run(app, host='0.0.0.0', port=8080) \ No newline at end of file From cf37dbf1034c56228d8a8cf074ba6533f45817a0 Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Mon, 6 May 2024 03:59:11 -0300 Subject: [PATCH 12/21] feat: on port 8080, add on /test - hellow world --- main.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index bfda5bd..6104d97 100644 --- a/main.py +++ b/main.py @@ -62,7 +62,11 @@ async def home(request: Request): return JSONResponse({'message': 'Hello World!'}) routes = [ - Route('/', home, methods=['GET']) + Route('/test', home, methods=['GET']) +] + +routes = [ + Route('/', upload_handler, methods=['POST']), ] app = Starlette(debug=True, routes=routes) From f35dcf2b1e3c85c90450ee6de7a0c23b1e107425 Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Mon, 6 May 2024 04:06:08 -0300 Subject: [PATCH 13/21] debug: on port 8080, add on /test - hellow world --- main.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/main.py b/main.py index 6104d97..dfba7c8 100644 --- a/main.py +++ b/main.py @@ -62,11 +62,8 @@ async def home(request: Request): return JSONResponse({'message': 'Hello World!'}) routes = [ - Route('/test', home, methods=['GET']) -] - -routes = [ - Route('/', upload_handler, methods=['POST']), + Route('/test', home, methods=['GET']), + Route('/', upload_handler, methods=['POST']) ] app = Starlette(debug=True, routes=routes) From 6ef45d27f589e40fe61509fe57c621811267fd5f Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Mon, 6 May 2024 04:11:10 -0300 Subject: [PATCH 14/21] debug: /upload/test --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index dfba7c8..5116cb3 100644 --- a/main.py +++ b/main.py @@ -62,7 +62,7 @@ async def home(request: Request): return JSONResponse({'message': 'Hello World!'}) routes = [ - Route('/test', home, methods=['GET']), + Route('test', home, methods=['GET']), Route('/', upload_handler, methods=['POST']) ] From 72fc9bd667fab632df8e2fe5994431a72d6d7aea Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Mon, 6 May 2024 04:14:43 -0300 Subject: [PATCH 15/21] feat: /upload/test has hello world --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 5116cb3..dfba7c8 100644 --- a/main.py +++ b/main.py @@ -62,7 +62,7 @@ async def home(request: Request): return JSONResponse({'message': 'Hello World!'}) routes = [ - Route('test', home, methods=['GET']), + Route('/test', home, methods=['GET']), Route('/', upload_handler, methods=['POST']) ] From ef2c902b32e823b683fba7b24f4bf5805f5a19e0 Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Mon, 6 May 2024 04:50:19 -0300 Subject: [PATCH 16/21] feat: aioboto3 for instead of boto3 --- main.py | 33 +++++++++++---------------------- pyproject.toml | 3 +-- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/main.py b/main.py index dfba7c8..69fe0b8 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,7 @@ import os import tempfile import uuid -import boto3 -from botocore.exceptions import BotoCoreError, ClientError +import aioboto3 # Ensure this is only imported once from starlette.applications import Starlette from starlette.responses import JSONResponse from starlette.routing import Route @@ -16,7 +15,6 @@ STORJ_END_POINT = os.environ.get('STORJ_END_POINT') STORJ_BUCKET_NAME = os.environ.get('STORJ_BUCKET_NAME') CDN_DOMAIN = os.environ.get('CDN_DOMAIN') - @check_auth async def upload_handler(request: Request): form = await request.form() @@ -28,36 +26,28 @@ async def upload_handler(request: Request): file_name, file_extension = os.path.splitext(file.filename) key = str(uuid.uuid4()) + file_extension - s3 = boto3.client('s3', - aws_access_key_id=STORJ_ACCESS_KEY, - aws_secret_access_key=STORJ_SECRET_KEY, - endpoint_url=STORJ_END_POINT) - - try: + async with aioboto3.client('s3', + aws_access_key_id=STORJ_ACCESS_KEY, + aws_secret_access_key=STORJ_SECRET_KEY, + endpoint_url=STORJ_END_POINT) as s3: with tempfile.NamedTemporaryFile() as tmp_file: while True: - chunk = await file.read(8192) # 8192 bytes by default. + chunk = await file.read(8192) if not chunk: break tmp_file.write(chunk) + tmp_file.flush() - s3.upload_file( + await s3.upload_file( Filename=tmp_file.name, Bucket=STORJ_BUCKET_NAME, Key=key, - ExtraArgs={ - "ContentType": file.content_type - } + ExtraArgs={"ContentType": file.content_type} ) - url = 'http://' + CDN_DOMAIN + '/' + key - + url = f'http://{CDN_DOMAIN}/{key}' return JSONResponse({'url': url, 'originalFilename': file.filename}) - except (BotoCoreError, ClientError) as e: - print(e) - return JSONResponse({'error': 'Failed to upload file'}, status_code=500) - async def home(request: Request): return JSONResponse({'message': 'Hello World!'}) @@ -70,5 +60,4 @@ app = Starlette(debug=True, routes=routes) if __name__ == "__main__": import uvicorn - uvicorn.run(app, host='0.0.0.0', port=8080) - \ No newline at end of file + uvicorn.run(app, host='0.0.0.0', port=8080) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index ed67076..b81f107 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,8 +11,7 @@ python = "^3.12" aiohttp = "^3.9.1" uvicorn = "^0.24.0.post1" starlette = "^0.33.0" -boto3 = "^1.33.6" -botocore = "^1.33.6" +aioboto3 = "^9.0.0" [tool.poetry.dev-dependencies] black = "^23.10.1" From 46759142df156b7e1465505aae20bc9893cbd755 Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Mon, 6 May 2024 05:03:24 -0300 Subject: [PATCH 17/21] feat: debug! more logs --- main.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 69fe0b8..37024c7 100644 --- a/main.py +++ b/main.py @@ -1,13 +1,16 @@ +import logging import os import tempfile import uuid -import aioboto3 # Ensure this is only imported once +import aioboto3 from starlette.applications import Starlette from starlette.responses import JSONResponse from starlette.routing import Route from starlette.requests import Request from auth import check_auth +# Logging configuration +logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') STORJ_ACCESS_KEY = os.environ.get('STORJ_ACCESS_KEY') STORJ_SECRET_KEY = os.environ.get('STORJ_SECRET_KEY') @@ -17,14 +20,17 @@ CDN_DOMAIN = os.environ.get('CDN_DOMAIN') @check_auth async def upload_handler(request: Request): + logging.debug("Received upload request") form = await request.form() file = form.get('file') if file is None: + logging.error("No file uploaded") return JSONResponse({'error': 'No file uploaded'}, status_code=400) file_name, file_extension = os.path.splitext(file.filename) key = str(uuid.uuid4()) + file_extension + logging.debug(f"Generated file key: {key}") async with aioboto3.client('s3', aws_access_key_id=STORJ_ACCESS_KEY, @@ -38,17 +44,21 @@ async def upload_handler(request: Request): tmp_file.write(chunk) tmp_file.flush() + logging.debug("Starting file upload to S3") await s3.upload_file( Filename=tmp_file.name, Bucket=STORJ_BUCKET_NAME, Key=key, ExtraArgs={"ContentType": file.content_type} ) + logging.debug("File upload completed") url = f'http://{CDN_DOMAIN}/{key}' + logging.info(f"File uploaded successfully: {url}") return JSONResponse({'url': url, 'originalFilename': file.filename}) async def home(request: Request): + logging.debug("Home route called") return JSONResponse({'message': 'Hello World!'}) routes = [ @@ -60,4 +70,4 @@ app = Starlette(debug=True, routes=routes) if __name__ == "__main__": import uvicorn - uvicorn.run(app, host='0.0.0.0', port=8080) \ No newline at end of file + uvicorn.run(app, host='0.0.0.0', port=8080) From 41c52a4f082b2bbbb9effceefcd0faed93343cc1 Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Mon, 6 May 2024 05:09:56 -0300 Subject: [PATCH 18/21] feat: debug! more logs --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 37024c7..2551cb8 100644 --- a/main.py +++ b/main.py @@ -18,7 +18,7 @@ STORJ_END_POINT = os.environ.get('STORJ_END_POINT') STORJ_BUCKET_NAME = os.environ.get('STORJ_BUCKET_NAME') CDN_DOMAIN = os.environ.get('CDN_DOMAIN') -@check_auth +#@check_auth async def upload_handler(request: Request): logging.debug("Received upload request") form = await request.form() From 765fa28ecc0c1d0732ce515534c90d8b3998e698 Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Mon, 6 May 2024 05:14:46 -0300 Subject: [PATCH 19/21] feat: now with python-multipart --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 2551cb8..37024c7 100644 --- a/main.py +++ b/main.py @@ -18,7 +18,7 @@ STORJ_END_POINT = os.environ.get('STORJ_END_POINT') STORJ_BUCKET_NAME = os.environ.get('STORJ_BUCKET_NAME') CDN_DOMAIN = os.environ.get('CDN_DOMAIN') -#@check_auth +@check_auth async def upload_handler(request: Request): logging.debug("Received upload request") form = await request.form() From 2e6678c657ce18fbf60cb096c6c9a4461302e2a1 Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Mon, 6 May 2024 05:18:09 -0300 Subject: [PATCH 20/21] debug: without @check_auth --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 37024c7..bcfe9a9 100644 --- a/main.py +++ b/main.py @@ -18,7 +18,7 @@ STORJ_END_POINT = os.environ.get('STORJ_END_POINT') STORJ_BUCKET_NAME = os.environ.get('STORJ_BUCKET_NAME') CDN_DOMAIN = os.environ.get('CDN_DOMAIN') -@check_auth +# @check_auth async def upload_handler(request: Request): logging.debug("Received upload request") form = await request.form() From 2964b726352b4fd3d85751ea6a3c94d78d98d13b Mon Sep 17 00:00:00 2001 From: Stepan Vladovskiy Date: Mon, 6 May 2024 05:24:54 -0300 Subject: [PATCH 21/21] feat: auth.py total recomp --- auth.py | 34 ++++++++++++---------------------- main.py | 2 +- pyproject.toml | 1 + 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/auth.py b/auth.py index 30e3d09..cae595a 100644 --- a/auth.py +++ b/auth.py @@ -1,22 +1,18 @@ from functools import wraps +from starlette.responses import JSONResponse import aiohttp -from aiohttp import web AUTH_URL = 'https://auth.discours.io' - async def check_auth(req): token = req.headers.get("Authorization") - headers = {"Authorization": token, "Content-Type": "application/json"} # "Bearer " + removed + headers = {"Authorization": token, "Content-Type": "application/json"} + print(f"[services.auth] checking auth token: {token}") - query_name = "session" - query_type = "query" - operation = "GetUserId" - gql = { - "query": query_type + " " + operation + " { " + query_name + " { user { id } } }", - "operationName": operation, + "query": "query GetUserId { session { user { id } } }", + "operationName": "GetUserId", "variables": None, } @@ -27,25 +23,19 @@ async def check_auth(req): return False, None r = await response.json() if r: - user_id = r.get("data", {}).get(query_name, {}).get("user", {}).get("id", None) + user_id = r.get("data", {}).get("session", {}).get("user", {}).get("id", None) is_authenticated = user_id is not None return is_authenticated, user_id return False, None - def login_required(f): @wraps(f) - async def decorated_function(*args, **kwargs): - info = args[1] - context = info.context - req = context.get("request") - is_authenticated, user_id = await check_auth(req) + async def decorated_function(request, *args, **kwargs): + is_authenticated, user_id = await check_auth(request) if not is_authenticated: - raise web.HTTPUnauthorized(text="You are not logged in") # Return HTTP 401 Unauthorized - else: - context["user_id"] = user_id - - # If the user is authenticated, execute the resolver - return await f(*args, **kwargs) + return JSONResponse({'error': 'Unauthorized'}, status_code=401) + # Make user_id available to the route handler, if needed + request.state.user_id = user_id + return await f(request, *args, **kwargs) return decorated_function diff --git a/main.py b/main.py index bcfe9a9..37024c7 100644 --- a/main.py +++ b/main.py @@ -18,7 +18,7 @@ STORJ_END_POINT = os.environ.get('STORJ_END_POINT') STORJ_BUCKET_NAME = os.environ.get('STORJ_BUCKET_NAME') CDN_DOMAIN = os.environ.get('CDN_DOMAIN') -# @check_auth +@check_auth async def upload_handler(request: Request): logging.debug("Received upload request") form = await request.form() diff --git a/pyproject.toml b/pyproject.toml index b81f107..8d5ea3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ aiohttp = "^3.9.1" uvicorn = "^0.24.0.post1" starlette = "^0.33.0" aioboto3 = "^9.0.0" +python-multipart = "^0.0.5" [tool.poetry.dev-dependencies] black = "^23.10.1"