ADR-018 — Extraction du traitement d'image hors du core : forge-mvc-images¶
Statut¶
Proposé — Forge 1.0.0-beta.x (ticket IMAGES-EXTRACT-RENAME-001).
Décision validée sur le principe (option « B — renommer + rapatrier »). Périmètre figé ci-dessous ; exécution par étapes.
Date¶
2026-06-03
Contexte¶
Forge distribue un opt-in forge-mvc-media dont la responsabilité réelle, à ce
jour, est la gestion applicative des médias : un repository SQL
(attach_media_to_entity, create_media_record, rôles, positions, alt_text)
et des helpers de galerie/couverture (get_media_gallery, get_cover_media,
media_url). Le traitement d'image proprement dit (génération de variantes
et de miniatures, save_image, vérification de contenu, garde anti-bombe de
décompression, constantes ALLOWED_IMAGE_*/IMAGE_VARIANT_SIZES) vit lui dans
le core, à core/uploads/image.py, et Pillow est une dépendance runtime
du core.
Depuis l'arrivée de forge-mvc-video (qui possède toute la chaîne vidéo :
ingest, stockage, transcodage, lecture), le nom « media » est devenu trompeur
— un développeur attend de forge-mvc-media qu'il gère aussi la vidéo, ce qui
n'est pas le cas. Par ailleurs, embarquer Pillow et le traitement d'image dans
le core contredit le principe de noyau minimal (charte principe 8, ADR-004) :
le core devrait fournir l'upload brut sûr, pas le pipeline d'images.
Couplage existant à dénouer¶
core/uploads/manager.py:save_upload(category="images", variants=True)appelleverify_image_content,generate_image_variants,image_variant_relative_paths— le chemin d'upload générique du core porte donc un comportement spécifique aux images.core/uploads/__init__.pyréexporte l'API image.forge_mvc_media/media_gallery.pydépend decore.uploads.image(ALLOWED_IMAGE_EXTENSIONS,image_variant_relative_paths).make:crudgénèrefrom forge_mvc_media import ...(forge_cli/entities/crud/controller_builder.py), idemforge_cli/public_list.pyet le catalogue opt-inforge_cli/optins/catalog.py(entrée « media »).- CI (
.github/workflows/tests.yml), README,docs/reference/api.md,docs/install/core-dev.md, CONTRIBUTING. - Garde-fous qui imposent Pillow dans le core :
tests/meta/test_packaging.py(test_pillow_dans_pyproject,test_pillow_dans_requirements_txt),tests/release/test_release_compat.py,tests/meta/test_app_default_no_mfa_001.py.
Décision¶
Créer un opt-in forge-mvc-images (forge_mvc_images) qui remplace
forge-mvc-media et devient l'unique propriétaire de tout l'image :
- Traitement d'image (déplacé du core) :
save_image, génération de variantes/miniatures,verify_image_content, garde anti-bombe, constantesALLOWED_IMAGE_*/IMAGE_VARIANT_SIZES,MediaRecord. Pillow devient dépendance deforge-mvc-images, retirée du core. - Couche applicative (déplacée de
forge-mvc-media) : repository SQL, galerie, couverture,alt_text.
Le core ne garde que l'upload brut générique : save_upload (sans
le cas particulier images), save_bytes, validation générique, storage,
rate-limit, exceptions. save_upload devient purement générique ;
forge-mvc-images fournit le chemin d'upload image-aware (validation de
contenu + écriture + variantes).
Conformément à la convention pré-1.0 (pas d'utilisateurs externes), le
renommage se fait sans alias déprécié ni guide de migration : forge-mvc-media
est supprimé, les réexports image du core sont retirés.
Hors périmètre¶
- La vidéo (
forge-mvc-video) reste inchangée. - Aucune nouvelle fonctionnalité image n'est ajoutée dans les tickets
d'extraction (1 à 7) : ils déplacent le comportement existant tel quel.
L'extension (ci-dessous) fait l'objet de tickets
IMAGES-FEATURE-*ultérieurs, sur la base propre obtenue après extraction.
Fonctionnalités cibles (ambition « étendre »)¶
Existant déplacé tel quel (figé, non configurable)¶
- Deux variantes en dur :
medium(1280×1280 max),thumbnail(300×300 max), +originalconservé.IMAGE_VARIANT_SIZESnon extensible sans éditer le code. - Redimensionnement : ratio préservé, réduction seule (jamais d'agrandissement), rééchantillonnage LANCZOS.
- Formats :
jpg, jpeg, png, webp; la variante garde le format de l'original. - Rangement :
<dossier>/medium/…,<dossier>/thumbnail/…. - Sécurité :
verify_image_content, garde anti-bombe de décompression. - Galerie :
url,medium_url,thumbnail_urlpar média.
Cibles d'extension (tickets IMAGES-FEATURE-*)¶
| Capacité | Aujourd'hui | Cible |
|---|---|---|
| Profils de variantes | 2 tailles en dur | profils configurables (noms + dimensions arbitraires) via la config forge_mvc_images |
| Conversion de format | format = celui de l'original | générer une variante dans un format choisi (ex. WebP) tout en conservant l'original |
| Recadrage | réduction « fit » seule | mode crop (ratio fixe : carré avatar, bannière) avec point focal optionnel |
| Qualité / compression | défaut Pillow | qualité réglable par variante (poids vs. netteté) |
| Dimensions sémantiques | medium / thumbnail | profils nommés cover / og-image prêts pour les métadonnées sociales |
| Idempotence / régénération | génération à l'upload | commande/CLI pour régénérer les variantes après changement de profil |
Ces capacités restent opt-in et explicites (pas de magie cachée, charte
principe 3) : un profil non déclaré n'est pas produit ; aucune conversion n'a
lieu sans demande. Chaque capacité = un ticket IMAGES-FEATURE-* séparé
(charte principe 2 : une responsabilité par ticket).
Conséquences¶
Positives¶
- Nom honnête et responsabilité nette :
forge-mvc-images= tout l'image,forge-mvc-video= toute la vidéo,core/uploads= upload brut sûr. - Core minimal : Pillow quitte le runtime du core (principe 8, ADR-004).
- Une seule façon officielle de traiter une image (principe 11).
Coûts / ruptures¶
- Renommage d'un paquet publié (PyPI
forge-mvc-media1.0.0b13) → nouveau paquetforge-mvc-images. Acceptable en bêta pré-1.0. - Les garde-fous « Pillow dans le core » s'inversent (Pillow absent du core,
présent dans
forge-mvc-images). make:crudgénère désormaisfrom forge_mvc_images import ...; tests de snapshot de génération à mettre à jour.- Deux fichiers protégés par le hook doivent être modifiés par le mainteneur,
pas par un agent :
pyproject.tomlracine (retrait de Pillow) etCLAUDE.md(§1, §3, §9 décrivantforge-mvc-media).
Plan d'exécution (tickets)¶
IMAGES-PKG-SCAFFOLD-001— squelettepackages/forge-mvc-images(pyproject.tomlavec Pillow,forge_mvc_images/__init__.py).IMAGES-MOVE-PROCESSING-001— déplacer le traitement image du core versforge_mvc_images; rendrecore/uploads/save_uploadgénérique ; exposer un chemin image-aware dans le module.IMAGES-MOVE-APPLICATIVE-001— déplacer repository + galerie depuisforge-mvc-mediaversforge_mvc_images.CORE-DROP-PILLOW-001— retrait de Pillow du core (pyproject racine : mainteneur),requirements.txt, inversion des garde-fous packaging.CLI-CRUD-IMAGES-RENAME-001—controller_builder,public_list,optins/catalog→forge_mvc_images; mise à jour des tests de génération.CI-DOCS-IMAGES-RENAME-001— matrice CI, README,docs/reference/api.md,docs/install/core-dev.md, CONTRIBUTING ; CLAUDE.md : mainteneur.REMOVE-MEDIA-PKG-001— suppression depackages/forge-mvc-media; entrée CHANGELOG.IMAGES-FEATURE-*(ultérieur) — extension des fonctionnalités image.
Alternatives écartées¶
- A — renommer seulement :
media → imagessans bouger le traitement. Rejeté : le nom « images » resterait un abus de langage tant que le traitement vit dans le core, et le couplagesave_upload/Pillow/core subsisterait. - C — statu quo : garder
forge-mvc-mediacomme couche générique d'attachement multi-type. Rejeté : ambiguïté persistante vis-à-vis deforge-mvc-video, et Pillow reste dans le core.
Références¶
- Charte principes 8 (noyau minimal), 11 (une seule façon officielle).
- ADR-004 (périmètre du core minimal strict).
- ADR-005 (packaging hybride monorepo + multi-distributions PyPI).