You've already forked RekomenciBackend
feature: add s3
This commit is contained in:
@@ -16,3 +16,9 @@ client_secret = "..."
|
|||||||
|
|
||||||
[firebase]
|
[firebase]
|
||||||
certificate_path = "firebase.json"
|
certificate_path = "firebase.json"
|
||||||
|
|
||||||
|
[s3]
|
||||||
|
bucket_name = ""
|
||||||
|
endpoint_url = ""
|
||||||
|
access_key = ""
|
||||||
|
secret_key = ""
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ dependencies = [
|
|||||||
"httpx==0.28.1",
|
"httpx==0.28.1",
|
||||||
"psycopg[binary]>=3.2.12",
|
"psycopg[binary]>=3.2.12",
|
||||||
"firebase-admin>=7.1.0",
|
"firebase-admin>=7.1.0",
|
||||||
|
"aioboto3==15.5.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
@@ -76,6 +77,10 @@ enable_error_code = [
|
|||||||
"deprecated",
|
"deprecated",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[tool.mypy.overrides]]
|
||||||
|
module = ["aioboto3.*", "aiobotocore.*"]
|
||||||
|
follow_untyped_imports = true
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
fix = true
|
fix = true
|
||||||
preview = 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
|
url: SecretString
|
||||||
|
|
||||||
|
|
||||||
|
@to_configuration
|
||||||
|
class S3Config:
|
||||||
|
bucket_name: str
|
||||||
|
endpoint_url: str
|
||||||
|
access_key: str
|
||||||
|
secret_key: str
|
||||||
|
|
||||||
|
|
||||||
@to_configuration
|
@to_configuration
|
||||||
class AccessTokenConfiguration:
|
class AccessTokenConfiguration:
|
||||||
crypto_key: str
|
crypto_key: str
|
||||||
@@ -57,6 +65,7 @@ class FirebaseConfiguration:
|
|||||||
|
|
||||||
@to_configuration
|
@to_configuration
|
||||||
class Configuration:
|
class Configuration:
|
||||||
|
s3: S3Config
|
||||||
server: ServerConfiguration
|
server: ServerConfiguration
|
||||||
database: DatabaseConfiguration
|
database: DatabaseConfiguration
|
||||||
access_token: AccessTokenConfiguration
|
access_token: AccessTokenConfiguration
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
from collections.abc import AsyncIterable
|
from collections.abc import AsyncIterable
|
||||||
|
|
||||||
|
from aioboto3.session import Session
|
||||||
from dishka import Provider, Scope, provide
|
from dishka import Provider, Scope, provide
|
||||||
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
|
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):
|
class ConnectionProvider(Provider):
|
||||||
@@ -21,3 +23,15 @@ class ConnectionProvider(Provider):
|
|||||||
)
|
)
|
||||||
async with session:
|
async with session:
|
||||||
yield 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
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from template_project.web_api.configuration import (
|
|||||||
Configuration,
|
Configuration,
|
||||||
DatabaseConfiguration,
|
DatabaseConfiguration,
|
||||||
FirebaseConfiguration,
|
FirebaseConfiguration,
|
||||||
|
S3Config,
|
||||||
ServerConfiguration,
|
ServerConfiguration,
|
||||||
YandexOAuthConfiguration,
|
YandexOAuthConfiguration,
|
||||||
)
|
)
|
||||||
@@ -38,5 +39,6 @@ def make_ioc(configuration: Configuration) -> AsyncContainer:
|
|||||||
YandexOAuthConfiguration: configuration.yandex_oauth,
|
YandexOAuthConfiguration: configuration.yandex_oauth,
|
||||||
FirebaseConfiguration: configuration.firebase,
|
FirebaseConfiguration: configuration.firebase,
|
||||||
Configuration: configuration,
|
Configuration: configuration,
|
||||||
|
S3Config: configuration.s3,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user