Skip to content

Le même module, en YAML

This content is not available in your language yet.

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": "library",
"depends": ["core"],
"version": "0.1.0",
"category": "assets"
}

YAML : manifest.yaml

name: library_lite
kind: declarative
version: 0.1.0
depends: [core]
category: assets

Une 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(
"library_lite_book",
module="library",
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/library_lite_item.yaml

model: library_lite_item
label: model.library_lite_item
fields:
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: reference

Notez 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="library_lite_book", nullable=False)

YAML

item_id: { type: many2one, target: library_lite_item, required: true }

Les permissions

Python : acl.json, un objet par modèle, une liste de groupes par verbe.

{
"library_lite_book": {
"read": ["library_user", "library_manager", "kws_admin"],
"create": ["library_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: library_lite_user, label: group.library_lite_user }
- { id: library_lite_manager, label: group.library_lite_manager }
access:
library_lite_item:
library_lite_user: [read]
library_lite_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: library_lite_item, field: daily_rate,
read: [library_lite_manager], write: [library_lite_manager] }
record_rules:
- { model: library_lite_loan, groups: [library_lite_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: library_lite_item
views:
list: { columns: [name, reference, condition] }
form:
sections:
- { title: section.identity, fields: [name, reference, condition, daily_rate] }

Les traductions

i18n/fr.yaml
model.library_lite_item: "Matériel"
model.library_lite_loan: "Emprunt"
section.identity: "Identification"
group.library_lite_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: library_lite_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: library_lite_loan, label: action.remind,
do: [{ emit: library_lite.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 library : ['library_lite_book', 'library_lite_loan']
modeles library_lite : ['library_lite_item', 'library_lite_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 id
item_id kws_x_extra state taken_on updated_at updated_by_id version

Une seule commande crée les tables des deux :

Fenêtre de terminal
.venv/bin/kewos reconcile --confirm --modules-root ./modules

Et 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 siPrenez 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.