"""Контракт хранилища объектов (S3), которым пользуются сервисы ядра. Реализация — `src.core.clients.S3Storage`, заглушка тестов — `tests.conftest.FakeStorage`. Протокол вынесен из `services/directory_import.py`: тот же интерфейс нужен загрузке файлов и маппингу результатов (Срез 2c, D-12). """ from collections.abc import AsyncGenerator from typing import Protocol class ObjectNotFoundError(Exception): """Объекта с таким ключом нет в бакете (S3 `NoSuchKey`).""" class ObjectStorage(Protocol): """Что ядру нужно от S3: бакет, запись, чтение объекта целиком и порциями.""" bucket: str async def put_object(self, obj_name: str, file_data: bytes) -> None: ... async def get_bytes(self, obj_name: str) -> bytes: """Содержимое объекта; отсутствие объекта — `ObjectNotFoundError`.""" ... def stream(self, obj_name: str, chunk_size: int = 1 << 20) -> AsyncGenerator[bytes, None]: """Объект порциями по `chunk_size` байт; отсутствие объекта — `ObjectNotFoundError` до первой порции. Закрытие итератора (`aclose`) освобождает соединение с S3.""" ...