Refactor: extract shared image upload utilities into bincio/shared/images.py
ALLOWED_IMAGE_TYPES, MAX_IMAGE_BYTES, and unique_image_name() were duplicated identically in both the edit and serve servers. Centralising them means a single change point for any future extension (e.g. adding image/avif support). Tests added in tests/test_shared_images.py cover no-collision, single and chained collisions, no-suffix filenames, and constant values.
This commit is contained in:
+3
-13
@@ -43,19 +43,9 @@ def _check_id(activity_id: str) -> str:
|
||||
return activity_id
|
||||
|
||||
|
||||
_ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/webp", "image/gif"}
|
||||
_MAX_IMAGE_BYTES = 10 * 1024 * 1024 # 10 MB
|
||||
|
||||
|
||||
def _unique_image_name(directory: Path, filename: str) -> str:
|
||||
"""Return a filename that does not collide with existing files in directory."""
|
||||
stem, suffix = Path(filename).stem, Path(filename).suffix
|
||||
candidate = filename
|
||||
counter = 1
|
||||
while (directory / candidate).exists():
|
||||
candidate = f"{stem}_{counter}{suffix}"
|
||||
counter += 1
|
||||
return candidate
|
||||
from bincio.shared.images import ALLOWED_IMAGE_TYPES as _ALLOWED_IMAGE_TYPES
|
||||
from bincio.shared.images import MAX_IMAGE_BYTES as _MAX_IMAGE_BYTES
|
||||
from bincio.shared.images import unique_image_name as _unique_image_name
|
||||
|
||||
|
||||
# ── HTML UI ───────────────────────────────────────────────────────────────────
|
||||
|
||||
+3
-15
@@ -332,21 +332,9 @@ def _set_session_cookie(response: Response, token: str) -> None:
|
||||
response.set_cookie(**kwargs)
|
||||
|
||||
|
||||
# ── Image upload constants ────────────────────────────────────────────────────
|
||||
|
||||
_ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/webp", "image/gif"}
|
||||
_MAX_IMAGE_BYTES = 10 * 1024 * 1024 # 10 MB
|
||||
|
||||
|
||||
def _unique_image_name(directory: Path, filename: str) -> str:
|
||||
"""Return a filename that does not collide with existing files in directory."""
|
||||
stem, suffix = Path(filename).stem, Path(filename).suffix
|
||||
candidate = filename
|
||||
counter = 1
|
||||
while (directory / candidate).exists():
|
||||
candidate = f"{stem}_{counter}{suffix}"
|
||||
counter += 1
|
||||
return candidate
|
||||
from bincio.shared.images import ALLOWED_IMAGE_TYPES as _ALLOWED_IMAGE_TYPES
|
||||
from bincio.shared.images import MAX_IMAGE_BYTES as _MAX_IMAGE_BYTES
|
||||
from bincio.shared.images import unique_image_name as _unique_image_name
|
||||
|
||||
|
||||
# ── Post-write rebuild ────────────────────────────────────────────────────────
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Shared image upload utilities used by both the edit and serve servers."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
ALLOWED_IMAGE_TYPES: frozenset[str] = frozenset({
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/gif",
|
||||
})
|
||||
MAX_IMAGE_BYTES: int = 10 * 1024 * 1024 # 10 MB
|
||||
|
||||
|
||||
def unique_image_name(directory: Path, filename: str) -> str:
|
||||
"""Return a filename that does not collide with existing files in directory."""
|
||||
stem, suffix = Path(filename).stem, Path(filename).suffix
|
||||
candidate = filename
|
||||
counter = 1
|
||||
while (directory / candidate).exists():
|
||||
candidate = f"{stem}_{counter}{suffix}"
|
||||
counter += 1
|
||||
return candidate
|
||||
Reference in New Issue
Block a user