45 lines
2.1 KiB
Python
45 lines
2.1 KiB
Python
"""Клиент S3 (MinIO) сервиса: чтение исходника и запись файла результата (design D-4).
|
||
|
||
`Storage` — протокол, от которого зависит воркер (design D-7): в unit-тестах его подменяет фейк, в приложении —
|
||
`S3Storage` поверх `miniopy_async.Minio`. Бакет передаётся в каждый вызов: исходник читается из `request.s3.bucket`,
|
||
результат пишется в тот же бакет.
|
||
"""
|
||
|
||
import json
|
||
from io import BytesIO
|
||
from typing import Any, Protocol, runtime_checkable
|
||
|
||
from miniopy_async import Minio
|
||
|
||
JSON_CONTENT_TYPE = "application/json"
|
||
|
||
|
||
@runtime_checkable
|
||
class Storage(Protocol):
|
||
"""Хранилище объектов, нужное воркеру: байты исходника и JSON результата."""
|
||
|
||
async def get_bytes(self, bucket: str, key: str) -> bytes: ...
|
||
|
||
async def put_json(self, bucket: str, key: str, obj: Any) -> None: ...
|
||
|
||
|
||
class S3Storage:
|
||
"""`Storage` поверх MinIO. `bucket` — бакет из настроек (проверяется при старте), методы принимают бакет явно."""
|
||
|
||
def __init__(self, client: Minio, bucket_name: str) -> None:
|
||
self.client = client
|
||
self.bucket = bucket_name
|
||
|
||
async def get_bytes(self, bucket: str, key: str) -> bytes:
|
||
"""Содержимое объекта целиком; ошибки MinIO/сети пробрасываются (воркер превращает их в `FILE_READ_ERROR`)."""
|
||
response = await self.client.get_object(bucket, key)
|
||
try:
|
||
return await response.read()
|
||
finally:
|
||
response.release()
|
||
|
||
async def put_json(self, bucket: str, key: str, obj: Any) -> None:
|
||
"""Записывает JSON-сериализуемый `obj` как UTF-8 (`application/json`, кириллица без экранирования)."""
|
||
body = json.dumps(obj, ensure_ascii=False).encode("utf-8")
|
||
await self.client.put_object(bucket, key, BytesIO(body), length=len(body), content_type=JSON_CONTENT_TYPE)
|