feature: add s3

This commit is contained in:
ivankirpichnikov
2025-11-21 10:22:04 +03:00
parent 9e917b6a40
commit 97eebe9acd
7 changed files with 85 additions and 1 deletions
+6
View File
@@ -16,3 +16,9 @@ client_secret = "..."
[firebase]
certificate_path = "firebase.json"
[s3]
bucket_name = ""
endpoint_url = ""
access_key = ""
secret_key = ""
+5
View File
@@ -15,6 +15,7 @@ dependencies = [
"httpx==0.28.1",
"psycopg[binary]>=3.2.12",
"firebase-admin>=7.1.0",
"aioboto3==15.5.0",
]
[dependency-groups]
@@ -76,6 +77,10 @@ enable_error_code = [
"deprecated",
]
[[tool.mypy.overrides]]
module = ["aioboto3.*", "aiobotocore.*"]
follow_untyped_imports = true
[tool.ruff]
fix = true
preview = true
@@ -0,0 +1,40 @@
from typing import IO, Protocol, override
from template_project.application.common.file_storage import FileStorage
class AioBoto3ClientLike(Protocol):
async def put_object(
self,
*,
Bucket: str, # noqa: N803
Key: str, # noqa: N803
Body: IO[bytes], # noqa: N803
) -> None: ...
class S3FileStorage(FileStorage):
__slots__ = (
"_bucket_name",
"_client",
)
def __init__(
self,
client: AioBoto3ClientLike,
bucket_name: str,
) -> None:
self._client = client
self._bucket_name = bucket_name
@override
async def upload(
self,
path: str,
image: IO[bytes],
) -> None:
await self._client.put_object(
Bucket=self._bucket_name,
Key=path,
Body=image,
)
@@ -0,0 +1,8 @@
from abc import abstractmethod
from typing import IO, Protocol
class FileStorage(Protocol):
@abstractmethod
async def upload(self, path: str, image: IO[bytes]) -> None:
raise NotImplementedError
@@ -20,6 +20,14 @@ class DatabaseConfiguration:
url: SecretString
@to_configuration
class S3Config:
bucket_name: str
endpoint_url: str
access_key: str
secret_key: str
@to_configuration
class AccessTokenConfiguration:
crypto_key: str
@@ -57,6 +65,7 @@ class FirebaseConfiguration:
@to_configuration
class Configuration:
s3: S3Config
server: ServerConfiguration
database: DatabaseConfiguration
access_token: AccessTokenConfiguration
+15 -1
View File
@@ -1,9 +1,11 @@
from collections.abc import AsyncIterable
from aioboto3.session import Session
from dishka import Provider, Scope, provide
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
from template_project.web_api.configuration import DatabaseConfiguration
from template_project.adapters.s3_storage import AioBoto3ClientLike
from template_project.web_api.configuration import DatabaseConfiguration, S3Config
class ConnectionProvider(Provider):
@@ -21,3 +23,15 @@ class ConnectionProvider(Provider):
)
async with session:
yield session
@provide(scope=Scope.APP)
async def s3_client(self, config: S3Config) -> AsyncIterable[AioBoto3ClientLike]:
session = Session() # type: ignore[no-untyped-call]
async with session.client(
"s3",
endpoint_url=config.endpoint_url,
aws_access_key_id=config.access_key,
aws_secret_access_key=config.secret_key,
) as s3_client:
yield s3_client
+2
View File
@@ -6,6 +6,7 @@ from template_project.web_api.configuration import (
Configuration,
DatabaseConfiguration,
FirebaseConfiguration,
S3Config,
ServerConfiguration,
YandexOAuthConfiguration,
)
@@ -38,5 +39,6 @@ def make_ioc(configuration: Configuration) -> AsyncContainer:
YandexOAuthConfiguration: configuration.yandex_oauth,
FirebaseConfiguration: configuration.firebase,
Configuration: configuration,
S3Config: configuration.s3,
},
)