feat(fc-cleanup): audits/transparency.py + tests — Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

This commit is contained in:
2026-05-26 01:18:07 -04:00
parent 929d3fc092
commit fd80d40a34
3 changed files with 79 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
"""Audit rule modules. Each module exposes evaluate(pil_image, **params) -> bool.
The retroactive library-cleanup tab and (future) import-time filter logic
both consume these. Importers should NOT inline rule logic going forward;
add the rule here and call from both sides.
"""
@@ -0,0 +1,27 @@
"""Transparency audit: matches images whose transparent-pixel fraction
exceeds the threshold. Animated images short-circuit (skipped) to avoid
the multi-frame PIL decode that hits Celery's hard time limit."""
def evaluate(pil_image, *, threshold: float) -> bool:
"""True iff the image's transparent-pixel fraction exceeds threshold.
False for non-alpha modes and animated images. Mirrors the import-side
Importer._transparency_pct logic so retroactive enforcement matches
prospective filtering.
"""
if getattr(pil_image, "is_animated", False):
return False
if pil_image.mode not in ("RGBA", "LA") and not (
pil_image.mode == "P" and "transparency" in pil_image.info
):
return False
im = pil_image
if im.mode != "RGBA":
im = im.convert("RGBA")
alpha = im.getchannel("A")
histogram = alpha.histogram()
transparent = histogram[0]
total = sum(histogram)
pct = transparent / total if total else 0.0
return pct > threshold
+46
View File
@@ -0,0 +1,46 @@
"""Tests for the transparency audit rule.
The rule mirrors `Importer._transparency_pct` semantics for retroactive
enforcement: returns True iff the fraction of fully-transparent pixels
exceeds the threshold. Animated images short-circuit to False to avoid
the multi-frame PIL decode that triggered SoftTimeLimitExceeded
2026-05-26 against animated WebPs.
"""
from PIL import Image
from backend.app.services.audits import transparency
def test_transparency_evaluate_true_when_fully_transparent():
im = Image.new("RGBA", (10, 10), (0, 0, 0, 0))
assert transparency.evaluate(im, threshold=0.5) is True
def test_transparency_evaluate_false_when_fully_opaque():
im = Image.new("RGBA", (10, 10), (200, 100, 50, 255))
assert transparency.evaluate(im, threshold=0.5) is False
def test_transparency_evaluate_respects_threshold_boundary():
# Half-transparent image: 50% alpha=0 pixels, 50% alpha=255.
im = Image.new("RGBA", (10, 10), (0, 0, 0, 0))
for x in range(5):
for y in range(10):
im.putpixel((x, y), (0, 0, 0, 255))
# 50% transparent. threshold=0.4 → True; threshold=0.6 → False.
assert transparency.evaluate(im, threshold=0.4) is True
assert transparency.evaluate(im, threshold=0.6) is False
def test_transparency_evaluate_false_for_rgb_image_without_alpha():
im = Image.new("RGB", (10, 10), (128, 128, 128))
assert transparency.evaluate(im, threshold=0.5) is False
def test_transparency_evaluate_false_for_animated_image():
im = Image.new("RGBA", (10, 10), (0, 0, 0, 0))
# Mark as animated (mimics PIL's WebP/GIF multi-frame attribute).
im.is_animated = True # type: ignore[attr-defined]
im.n_frames = 5 # type: ignore[attr-defined]
assert transparency.evaluate(im, threshold=0.5) is False