SFM/tests/unit/test_main_attachments.py
Andrey Epifancev 27a1f22be4 Stream uploaded attachments to disk and cover the progress callback
UploadedFile already holds the whole attachment in memory; getvalue()
made a second full copy of each of the 230 files before writing. Copy
through the file interface instead. Smoke tests pin the progress
callback contract: once per XML file, including unreadable ones.

Refs #1

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 19:14:43 +04:00

51 lines
1.9 KiB
Python
Raw Permalink 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.

"""Сохранение вложений Streamlit на диск не должно удваивать их в памяти.
UploadedFile — это BytesIO: Streamlit уже держит всё содержимое вложения в
памяти. getvalue() делает ещё одну полную копию каждого файла перед записью
на диск; при 230 вложениях в контейнере на 1 ГБ это лишняя пиковая нагрузка.
Копировать нужно потоково, через файловый интерфейс, не трогая getvalue().
"""
from __future__ import annotations
import io
from pathlib import Path
from main import _save_attachments
class _UploadWithoutGetValue(io.BytesIO):
"""Вложение, у которого есть только файловый интерфейс и имя."""
def __init__(self, name: str, payload: bytes) -> None:
super().__init__(payload)
self.name = name
def getvalue(self) -> bytes: # type: ignore[override]
raise AssertionError("getvalue() копирует всё вложение в память")
def test_save_attachments_streams_without_getvalue(tmp_path: Path) -> None:
uploads = [
_UploadWithoutGetValue("a.xml", b"<a/>"),
_UploadWithoutGetValue("a.xml", b"<b/>" * 1000),
]
paths = _save_attachments(uploads, tmp_path)
assert [path.name for path in paths] == ["a.xml", "a.xml"]
assert paths[0] != paths[1]
assert paths[0].read_bytes() == b"<a/>"
assert paths[1].read_bytes() == b"<b/>" * 1000
def test_save_attachments_copies_from_start_even_if_upload_was_read(
tmp_path: Path,
) -> None:
upload = _UploadWithoutGetValue("f.xml", b"0123456789")
upload.read(4)
(path,) = _save_attachments([upload], tmp_path)
assert path.read_bytes() == b"0123456789"