Les paramètres applicatifs dans Forge (forge-mvc-settings)¶
Ce document explique ce que fait l'opt-in forge-mvc-settings, ce qu'il expose, et comment on s'en sert.
forge-mvc-settings persiste des réglages d'application en paires clé/valeur typées dans une table, avec une API explicite get_setting / set_setting.
Le cœur de Forge ignore tout des paramètres : ce paquet fournit l'API, l'application décide de ce qu'elle stocke (nom d'établissement, mode maintenance, options pédagogiques).
1. Rôle du module
Une application a besoin de réglages modifiables sans redéploiement.
L'opt-in stocke ces réglages dans une table SQL (app_settings) et expose quatre fonctions pour les lire et les écrire.
Il reste fidèle à la charte : le SQL est visible, et l'exécuteur de base de données est injecté explicitement, jamais ouvert en douce par le module.
2. Installation
Prérequis : activez le venv du projet
Quelle que soit la source, installez dans le venv du projet :
Lancé hors d'un venv, pip vise le Python système (Debian 12+, Ubuntu 23.04+),
protégé par PEP 668. Il refuse alors d'installer, pour ne pas écraser les paquets
gérés par apt, et affiche externally-managed-environment.
Le venv de projet créé par forge new n'a pas ce verrou.
Installer le paquet¶
B. Depuis Git (avant-garde)¶
Cœur puis opt-in depuis git, dans le venv du projet (l'opt-in trouve le cœur git déjà en place, sans version publiée sur PyPI) :
3. Mise en service
Installer le paquet ne suffit pas à le rendre opérationnel.
Voici les gestes propres à forge-mvc-settings, dans l'ordre.
Ils déclinent la procédure canonique, Rendre un opt-in opérationnel : les cinq
points.
1. L'épingler¶
Dans requirements.txt, à la même version ou au même commit que forge-mvc.
Sans cette ligne, l'opt-in n'existe que sur votre machine.
2. L'inscrire¶
L'opt-in est inscrit dans optins/registry.py (ADR-061), ce qui le rend visible du
projet.
--apply est obligatoire : sans lui, la commande simule et n'écrit rien.
3. Poser ce dont il a besoin¶
settings:init copie la migration embarquée dans mvc/migrations/ ;
migration:apply l'exécute et la trace (ADR-071).
Sans cette étape, le premier appel échoue sur une table absente.
4. Le brancher là où il agit¶
Il s'importe dans le code qui s'en sert. Il n'y a ni route à monter ni middleware
à poser.
5. Le prouver¶
Puis un premier usage réel.
Un opt-in installé, inscrit et provisionné qu'aucun code n'appelle n'est pas
opérationnel : il est seulement présent.
4. Désinstallation
opt-in:disable est l'inverse d'enable : il dé-inscrit du registre (le code n'était pas câblé), sans toucher au paquet.
forge opt-in:remove settings affiche la commande pip uninstall sans l'exécuter.
5. Commandes
forge-mvc-settings ajoute une commande :
| Commande | Rôle | Exemple |
|---|---|---|
settings:init |
Crée la table app_settings (DDL fournie). |
forge settings:init |
6. Vue d'ensemble rapide
| Élément | Valeur |
|---|---|
| Paquet | forge-mvc-settings |
| Module | forge_mvc_settings |
| Catégorie | Configuration (ADR-055) |
| Couche | opt-in (brique optionnelle) |
| Dépend de | forge-mvc et un backend BDD installé (ADR-054) |
| API publique | get_setting, set_setting, get_all_settings, delete_setting, parse_setting_value, describe_settings, set_user_setting, enable_settings_cache |
| Table SQL | app_settings (TABLE_NAME) |
| Types supportés | str, int, bool, float, json (SUPPORTED_TYPES) |
| Exception liée | SettingsError si la clé est invalide ou le type non supporté |
| Stratégie opt-in | ADR-052 |
| Installation | pip install --pre forge-mvc-settings |
7. Schémas UML
Les deux schémas suivants montrent deux vues complémentaires de l'opt-in.
Le diagramme de classe montre l'API, la table et l'exécuteur injecté.
Le diagramme de séquence montre l'écriture puis la lecture d'un paramètre.
5.1 Diagramme de classe¶
Le diagramme de classe montre que le module agit sur la table app_settings au travers d'un exécuteur de base de données fourni par l'application, et qu'il peut lever SettingsError.
classDiagram
direction LR
class settings {
<<module>>
+get_setting(key, default, db) SettingValue
+set_setting(key, value, db) None
+get_all_settings(db) dict
+delete_setting(key, db) bool
}
class app_settings {
<<table>>
+str setting_key
+str setting_value
+str value_type
}
class DBExecutor {
+execute(sql, params)
+fetch_one(sql, params)
+fetch_all(sql)
}
class SettingsError {
<<exception>>
}
settings --> DBExecutor : exécuteur injecté
DBExecutor --> app_settings : lit / écrit
settings ..> SettingsError : peut lever
À retenir :
- le module expose quatre fonctions, pas de classe à instancier ;
- les données vivent dans la table
app_settings; - le module n'ouvre jamais de connexion : il reçoit un exécuteur ;
- une clé invalide ou un type non supporté lève
SettingsError.
5.2 Diagramme de séquence¶
Le diagramme de séquence montre un set_setting (upsert) suivi d'un get_setting.
sequenceDiagram
participant App as Code applicatif
participant Settings as forge_mvc_settings
participant DB as Exécuteur BDD
participant Table as app_settings
App->>Settings: set_setting("maintenance", True)
Settings->>Settings: valide la clé, sérialise (value, "bool")
Settings->>DB: execute(UPSERT, params)
DB->>Table: insère ou met à jour la ligne
App->>Settings: get_setting("maintenance", default=False)
Settings->>DB: fetch_one(SELECT, ("maintenance",))
DB-->>Settings: ligne (setting_value, value_type)
Settings-->>App: True (recoercé selon value_type)
À retenir :
set_settingdéduit le type de la valeur et fait un upsert ;- la valeur est stockée sous forme de texte avec son type ;
get_settingrecoerce la valeur selon le type stocké ;get_settingrenvoiedefaultsi la clé est absente.
8. API publique
| Élément | Signature | Rôle |
|---|---|---|
set_setting |
set_setting(key, value, *, db=None) -> None |
crée ou met à jour un paramètre (upsert) |
get_setting |
get_setting(key, default=None, *, db=None) -> SettingValue \| None |
lit un paramètre, recoercé selon son type |
get_all_settings |
get_all_settings(*, db=None) -> dict[str, SettingValue] |
renvoie tous les paramètres, triés par clé |
delete_setting |
delete_setting(key, *, db=None) -> bool |
supprime un paramètre, True s'il existait |
SettingsError |
exception (ValueError) |
clé invalide ou type non supporté |
TABLE_NAME |
"app_settings" |
nom de la table |
SUPPORTED_TYPES |
("str", "int", "bool", "float") |
types de valeurs acceptés |
Le paramètre db est l'exécuteur de base de données.
S'il est omis, le module utilise le backend BDD actif du projet.
9. Contextes d'utilisation
| Besoin | Élément |
|---|---|
| Écrire un réglage | set_setting(key, value) |
| Lire un réglage avec repli | get_setting(key, default=...) |
| Lire tous les réglages | get_all_settings() |
| Supprimer un réglage | delete_setting(key) |
| Créer la table | forge settings:init puis forge migration:apply |
| Injecter un exécuteur de test | paramètre db=... |
10. Exemples d'utilisation
8.1 Écrire et lire un paramètre¶
from forge_mvc_settings import set_setting, get_setting
set_setting("school_name", "Collège Forge")
name = get_setting("school_name", default="Sans nom")
8.2 Valeurs typées¶
set_setting("maintenance", True) # bool
set_setting("max_upload_mb", 20) # int
if get_setting("maintenance", default=False):
...
Le type est déduit à l'écriture et restitué à la lecture : get_setting("maintenance") renvoie un vrai bool.
8.3 Lister et supprimer¶
from forge_mvc_settings import get_all_settings, delete_setting
reglages = get_all_settings() # dict trié par clé
existait = delete_setting("ancienne_option")
Aide-mémoire
Quatre fonctions, un seul objet de stockage :
set_setting/get_settingpour une clé ;get_all_settings/delete_settingpour gérer l'ensemble.
11. Clés, types et injection
Les clés sont des chaînes ; une clé vide ou non textuelle lève SettingsError.
Seuls str, int, bool, float sont stockables ; un autre type lève SettingsError.
Création de la table
Les fonctions supposent la table app_settings présente.
Créez-la avec forge settings:init puis forge migration:apply, avant le premier appel.
SQL visible et exécuteur injecté
Le module ne crée jamais de connexion : il reçoit un exécuteur (execute, fetch_one, fetch_all).
En production, c'est le backend BDD actif ; en test, vous injectez un faux exécuteur via db=....
Indépendance du cœur
Le cœur de Forge ne dépend pas de forge-mvc-settings.
La dépendance va de l'opt-in vers le cœur, jamais l'inverse.
Voir aussi¶
- Les paramètres (store.py) : détail des fonctions et du SQL.
- Initialisation (settings:init) : création de la table.
- Les erreurs (errors.py) : détail de
SettingsError. - Welcome-Settings : parcours d'apprentissage.
Déclaration de table¶
Le paquet ne livre plus de fichier SQL figé : il déclare sa table dans tables.py
(APP_SETTINGS, plus la liste MIGRATIONS).
Le DDL est rendu pour le backend installé par core.database.table_ddl, puis écrit
dans mvc/migrations/ par forge settings:init (chantier OPTIN-DDL-DIALECTAL).
Le SQL reste donc relisible avant forge migration:apply, mais il est correct pour
MariaDB, SQLite, PostgreSQL comme SQL Server.
Éditer les paramètres depuis un écran¶
Un paramètre porte une valeur et son type, et set_setting déduit le second de la première.
Une page web, elle, ne reçoit que du texte (ADMIN-SETTINGS-UI-001).
from forge_mvc_settings import describe_settings, parse_setting_value, set_setting
# Affichage
for ligne in describe_settings():
print(ligne.key, ligne.raw, ligne.value_type)
# Enregistrement d'une saisie
set_setting(cle, parse_setting_value(saisie, type_declare))
describe_settings ne rend que les paramètres globaux.
Les paramètres appartenant à un utilisateur en sont exclus, comme dans get_all_settings : un écran de réglages afficherait sinon les préférences de tous les comptes, adresses comprises, et les offrirait à l'édition.
Employez get_user_settings pour ceux d'un utilisateur.
Une valeur json se saisit telle qu'elle s'écrit, ["pdf", "odt"] ou {"lundi": "8h-17h"}.
Une saisie malformée produit un SettingsError, jamais une erreur cinq cents.
Un scalaire y est refusé : 42 a déjà son type, et l'accepter ferait changer le type déclaré au premier enregistrement.
Champ de SettingRow |
Rôle |
|---|---|
key |
la clé du paramètre |
value |
la valeur typée, pour l'affichage |
value_type |
le type déclaré, à renvoyer avec la saisie |
raw |
la forme texte, à mettre dans le champ de formulaire |
Ne branchez pas un CRUD générique sur la table
Il faudrait saisir value_type à la main et le tenir cohérent avec la valeur.
Une incohérence, value_type=int sur une valeur abc, casse toute lecture ultérieure du paramètre.
Taper « oui » dans un booléen enregistrait faux
La lecture interne compare à "1" : toute autre saisie valait faux, en silence.
L'exploitant croyait avoir activé une option, et rien ne le détrompait.
parse_setting_value accepte 1, true, vrai, oui, yes, on et leurs contraires, et refuse ce qu'elle ne reconnaît pas.
Un refus, jamais une erreur cinq cents
Convertir avec int(saisie) lève une ValueError nue.
parse_setting_value lève une SettingsError, que le contrôleur intercepte pour rendre un message de formulaire.
Ce que la page affiche, elle peut le renvoyer
raw est fait pour aller dans un champ que l'utilisateur renverra tel quel.
Afficher True produirait une saisie dépendante de la langue de Python plutôt que du contrat, d'où 1 et 0 pour un booléen.
Aucune dépendance au back-office
Ce module convertit et décrit ; la page appartient à l'application.
forge-mvc-admin n'est pas importé, et un projet sans back-office édite ses paramètres depuis sa propre interface avec les mêmes fonctions.
Une valeur textuelle est conservée telle quelle, espaces de bord comprises : contrairement à un entier, elles peuvent être voulues.
Paramètres par utilisateur¶
Un réglage personnel, thème ou langue, n'avait pas de place : la clé primaire porte la seule clé du paramètre.
Le ranger sous une clé composée marchait, mais rien n'empêchait la collision : une clé globale user.42.theme et la préférence de l'utilisateur 42 auraient désigné la même ligne, et l'une aurait écrasé l'autre en silence (SETTINGS-PER-USER-001).
from forge_mvc_settings import get_user_setting, get_user_settings, set_user_setting
set_user_setting(utilisateur.id, "theme", "sombre")
get_user_setting(utilisateur.id, "theme", "clair") # « clair » si absent
get_user_settings(utilisateur.id) # {"theme": "sombre"}
Le préfixe user. est réservé
set_setting("user.42.theme", ...) est refusé, et le message indique la bonne porte.
Sans cette réserve, une clé globale et une préférence personnelle pourraient désigner la même ligne.
Les deux espaces restent séparés
get_all_settings ne rend que les paramètres globaux.
Les mêler ferait grossir la configuration de l'application au rythme de ses comptes, et un écran de réglages afficherait les préférences de tout le monde.
Un réglage personnel ne retombe pas sur le global
get_user_setting rend le défaut que vous lui donnez, jamais le paramètre global de même nom.
Sinon « cet utilisateur n'a pas de préférence » et « sa préférence vaut le défaut de l'application » ne se distinguent plus, et l'appelant ne peut plus dire lequel il lit.
Le repli, s'il le veut, est une ligne de son code.
L'identifiant ne peut pas contenir de point, séparateur d'espace de noms : deux utilisateurs pourraient sinon viser la même clé.
Cache mémoire¶
Un paramètre est lu à chaque requête, parfois plusieurs fois, et change une fois par mois.
from forge_mvc_settings import clear_settings_cache, enable_settings_cache
enable_settings_cache() # au démarrage de l'application
clear_settings_cache() # après une écriture faite hors du paquet
Éteint par défaut
Un cache change ce qu'une lecture garantit : sans lui, get_setting rend toujours la valeur en base ; avec lui, la dernière valeur vue.
Le principe 3 refuse qu'un comportement change dans le dos de l'appelant : l'application l'active, et sait ce qu'elle achète.
L'invalidation est explicite, jamais par expiration
Une expiration ferait cohabiter deux valeurs pendant un délai que personne n'a choisi.
Écrire par ce paquet invalide l'entrée. Une écriture faite ailleurs, par une migration ou à la main, demande un clear_settings_cache() que l'exploitant décide.
Le cache vit dans le processus
Ce n'est pas un cache partagé : un déploiement à plusieurs travailleurs en a un par travailleur.
Deux d'entre eux peuvent donc voir des valeurs différentes entre l'écriture et l'invalidation.
Ce que les paramètres ne doivent pas contenir¶
Aucun secret. Ni mot de passe, ni jeton d'API, ni clé de chiffrement (DOC-SETTINGS-NO-SECRETS-001).
Un paramètre est en clair dans une table applicative, lisible par toute personne ayant accès à la base ou à une sauvegarde, et affiché tel quel par un écran d'administration.
| Ce qui va ici | Ce qui n'y va pas |
|---|---|
| nom de l'établissement, thème, langue par défaut | mot de passe SMTP |
| taille maximale d'un dépôt, nombre de places | jeton d'API d'un service tiers |
| adresse de contact affichée | clé de chiffrement MFA |
Les secrets vivent dans l'environnement, env/prod ignoré par git ou les variables du service.
forge deploy:check refuse d'ailleurs un secret laissé à sa valeur d'amorçage, contrôle qui ne regarde que l'environnement.
Pourquoi Forge ne chiffre pas cette table
Chiffrer déplacerait le problème sans le résoudre : la clé de déchiffrement devrait vivre quelque part, et cet endroit serait l'environnement.
Autant y mettre le secret directement, ce qui est plus simple et plus facile à auditer.