arm-core/api/src/core/storage.py
Andrey Epifancev e45502b2ea fix(files): детерминированное освобождение S3-потока при отдаче исходного файла (T9.1, T9.2, T9.4, T9.5)
- ClosingStreamingResponse закрывает body_iterator в finally (под shield), ChainedStream закрывает
  исходный поток и до начала чтения; заголовок X-Content-Type-Options: nosniff;
- ASCII-имя в Content-Disposition строится из file_type, а не из расширения исходного имени;
- ObjectStorage.stream типизирован как AsyncGenerator, убраны два type: ignore.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-07 13:35:18 +04:00

31 lines
1.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Контракт хранилища объектов (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."""
...