Le même module, en YAML
Le module du parcours s’écrit aussi entièrement en YAML, sans une ligne de code. Ce n’est pas un jouet ni un générateur : le chargeur lit le YAML et appelle les mêmes fonctions du noyau que le module Python. Mêmes tables, même API, mêmes écrans, mêmes permissions.
Pourquoi ce format existe
Pour les intégrateurs et les entreprises qui veulent garder la main sur leurs modules sans dépendre d’un cycle de livraison logiciel. Le format ne remplace pas Python : il coexiste avec lui, et les deux se croisent dans la même instance.
Le principe est simple à énoncer : traduire, pas réinventer. Le YAML ne dispose d’aucun moteur à lui. Il n’y a donc pas de second système à sécuriser.
Côte à côte
Le manifeste
Python : manifest.json
{ "name": "gear", "depends": ["core"], "version": "0.1.0", "category": "assets"}YAML : manifest.yaml
name: gearlitekind: declarativeversion: 0.1.0depends: [core]category: assetsUne seule différence de fond : kind: declarative. C’est ce mot qui dit au noyau de lire le
dossier au lieu de l’importer.
Un modèle
Python : models/item.py
registry.register_model( "gear_item", module="gear", fields=[ CharField(name="name", nullable=False, max_length=128), CharField(name="reference", nullable=True, max_length=32), SelectionField( name="condition", choices=[("new", "New"), ("good", "Good"), ("worn", "Worn")], default="good", ), DecimalField(name="daily_rate", precision=12, scale=2), ],)YAML : models/gearlite_item.yaml
model: gearlite_itemlabel: model.gearlite_itemfields: name: { type: char, required: true, max_length: 128 } reference: { type: char, max_length: 32 } condition: type: selection choices: [[new, "New"], [good, "Good"], [worn, "Worn"]] default: good daily_rate: { type: decimal, precision: 12, scale: 2 }search: title: name subtitle: referenceNotez required: true au lieu de nullable=False : le YAML dit ce que veut l’auteur, le
chargeur traduit vers ce qu’attend le noyau.
Une relation
Python
Many2one(name="item_id", target_model="gear_item", nullable=False)YAML
item_id: { type: many2one, target: gearlite_item, required: true }Les permissions
Python : acl.json, un objet par modèle, une liste de groupes par verbe.
{ "gear_item": { "read": ["gear_user", "gear_manager", "kws_admin"], "create": ["gear_manager", "kws_admin"] }}YAML : security.yaml, qui va plus loin. Il déclare aussi les groupes, ce que le module
Python fait ailleurs.
groups: - { id: gearlite_user, label: group.gearlite_user } - { id: gearlite_manager, label: group.gearlite_manager }
access: gearlite_item: gearlite_user: [read] gearlite_manager: [read, create, write, unlink]Le même fichier accueille les règles plus fines, qui n’ont pas d’équivalent plus court en Python :
field_rules: - { model: gearlite_item, field: daily_rate, read: [gearlite_manager], write: [gearlite_manager] }
record_rules: - { model: gearlite_loan, groups: [gearlite_user], domain: "borrower == user.name" }Une règle de champ cache une colonne à qui n’y a pas droit ; une règle d’enregistrement filtre les lignes. Les deux s’appliquent dans la couche d’accès aux données, donc l’API et les écrans en héritent sans rien faire.
Les écrans
Python : un fichier JSON par vue.
YAML : un fichier par modèle, toutes ses vues dedans.
model: gearlite_itemviews: list: { columns: [name, reference, condition] } form: sections: - { title: section.identity, fields: [name, reference, condition, daily_rate] }Les traductions
model.gearlite_item: "Matériel"model.gearlite_loan: "Emprunt"section.identity: "Identification"group.gearlite_user: "Emprunteur"Ce que le YAML sait faire en plus, sans code
Une machine à états, avec ses gardes et ses effets :
workflows: - id: loan_lifecycle model: gearlite_loan initial: out states: [out, returned] transitions: - { trigger: give_back, from: out, to: returned, guard: "taken_on != null" }Un bouton qui déclenche quelque chose :
actions: - { id: remind, model: gearlite_loan, label: action.remind, do: [{ emit: gearlite.loan.reminder }] }Les expressions (taken_on != null, concat('RET-', name)) sont écrites dans un langage
restreint et sur liste blanche. Il n’y a ni eval, ni exec, ni échappatoire vers du code
arbitraire, et ce n’est pas une omission : un module qu’un client dépose sur un serveur ne doit
pas pouvoir exécuter ce qu’il veut.
La preuve : les deux se comportent pareil
Chargement des deux modules dans la même instance :
modeles gear : ['gear_item', 'gear_loan']modeles gearlite : ['gearlite_item', 'gearlite_loan']Les champs systèmes sont ajoutés des deux côtés, à l’identique :
borrower created_at created_by_id deleted_at deleted_by_id due_on iditem_id kws_x_extra state taken_on updated_at updated_by_id versionUne seule commande crée les tables des deux :
.venv/bin/kewos reconcile --confirm --modules-root ./modulesEt l’API traite le module YAML exactement comme l’autre :
{"id":1,"name":"Echelle 3m","reference":"ECH-003","condition":"new", "daily_rate":"8.00","created_at":"2026-09-03T12:05:53.675666+00:00", "created_by_id":1,"version":1,"deleted_at":null}Lequel choisir
| Prenez le YAML si | Prenez Python si |
|---|---|
| Vous décrivez des données, des écrans, des droits, des états. | Vous avez besoin d’un calcul que le langage restreint n’exprime pas. |
| Vous voulez livrer sans cycle de release. | Vous appelez un service externe, un format de fichier, une bibliothèque. |
| Plusieurs personnes non développeuses doivent relire le module. | Vous écrivez des tests fins sur des règles métier. |
Et rien n’oblige à choisir une fois pour toutes : un module YAML et un module Python cohabitent dans la même instance, se voient par les événements et le registre, et se déploient de la même façon.