Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions safe_s3_storage/file_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ def _should_convert_file(self, file_name: str) -> bool:
def _convert_image(self, validated_file: ValidatedFile) -> ValidatedFile:
import pyvips # type: ignore[import-untyped] # noqa: PLC0415

# libvips keeps a process-global operation cache. For one-shot conversions of
# unique upload buffers it never hits and only retains memory, so disable it.
# The guard makes the setter effectively run once (max is 0 after the first
# call) and avoids a module-level global that ruff's ALL ruleset would flag.
if pyvips.cache_get_max():
pyvips.cache_set_max(0)

if not _is_image(validated_file.mime_type):
return validated_file

Expand Down
13 changes: 13 additions & 0 deletions tests/test_file_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import faker
import httpx
import pytest
import pyvips # type: ignore[import-untyped]
from httpx import codes as status_codes

from safe_s3_storage import exceptions
Expand Down Expand Up @@ -212,3 +213,15 @@ async def test_excluded_conversion_formats(
assert validated_file.file_name == f"{file_base_name}.{file_extension}"
assert validated_file.file_content == png_file
assert validated_file.file_size == len(validated_file.file_content)

async def test_conversion_does_not_leave_pyvips_operations_cached(
self, faker: faker.Faker, png_file: bytes
) -> None:
# libvips keeps a process-global operation cache; for one-shot conversions of
# unique buffers it only retains memory. The validator must disable it so the
# cache does not accumulate across conversions (memory leak).
await FileValidator(allowed_mime_types=["image/png"]).validate_file(
file_name=f"{faker.pystr()}.png", file_content=png_file
)

assert pyvips.cache_get_size() == 0
Loading