Recettes
Des morceaux de code à copier, testés contre un espace réel. Remplacez l’adresse, la base, l’identifiant et la clé : le reste fonctionne.
01Python
Rien à installer : xmlrpc.client est dans la bibliothèque
standard.
import os
import xmlrpc.client
class Madata:
"""Client minimal de l'API de donnees d'un espace MADATA."""
def __init__(self, espace, login, cle):
self.url = f"https://{espace}.madata.app"
self.db = f"prod_{espace}"
self.cle = cle
common = xmlrpc.client.ServerProxy(f"{self.url}/xmlrpc/2/common")
self.uid = common.authenticate(self.db, login, cle, {})
if not self.uid:
raise RuntimeError("identifiants refuses")
self.models = xmlrpc.client.ServerProxy(f"{self.url}/xmlrpc/2/object")
def appel(self, modele, methode, *args, **kw):
return self.models.execute_kw(
self.db, self.uid, self.cle, modele, methode, list(args), kw)
def chercher(self, modele, domaine, champs, **kw):
return self.appel(modele, 'search_read', domaine, fields=champs, **kw)
ma = Madata(
espace=os.environ["MADATA_ESPACE"],
login=os.environ["MADATA_LOGIN"],
cle=os.environ["MADATA_CLE"],
)
for c in ma.chercher('res.partner', [['customer_rank', '>', 0]],
['name', 'email'], limit=5):
print(c['id'], c['name'], c['email'] or '-')
02Node.js
En JSON-RPC, fetch suffit : aucune dépendance.
const ESPACE = process.env.MADATA_ESPACE;
const URL_BASE = `https://${ESPACE}.madata.app/jsonrpc`;
const DB = `prod_${ESPACE}`;
const CLE = process.env.MADATA_CLE;
async function rpc(service, method, args) {
const r = await fetch(URL_BASE, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0', method: 'call',
params: { service, method, args },
}),
});
const data = await r.json();
// Une erreur arrive en HTTP 200 : c'est la cle « error » qui compte.
if (data.error) {
const d = data.error.data || {};
throw new Error(`${d.name || 'erreur'} : ${d.message || data.error.message}`);
}
return data.result;
}
const uid = await rpc('common', 'authenticate',
[DB, process.env.MADATA_LOGIN, CLE, {}]);
const appel = (modele, methode, args = [], kw = {}) =>
rpc('object', 'execute_kw', [DB, uid, CLE, modele, methode, args, kw]);
const clients = await appel('res.partner', 'search_read',
[[['customer_rank', '>', 0]]],
{ fields: ['name', 'email'], limit: 5 });
console.table(clients);
03PHP
<?php
require_once 'ripcord.php';
$espace = getenv('MADATA_ESPACE');
$url = "https://{$espace}.madata.app";
$db = "prod_{$espace}";
$cle = getenv('MADATA_CLE');
$common = ripcord::client("$url/xmlrpc/2/common");
$uid = $common->authenticate($db, getenv('MADATA_LOGIN'), $cle, []);
$models = ripcord::client("$url/xmlrpc/2/object");
$clients = $models->execute_kw($db, $uid, $cle,
'res.partner', 'search_read',
[[['customer_rank', '>', 0]]],
['fields' => ['name', 'email'], 'limit' => 5]);
foreach ($clients as $c) {
echo $c['name'], ' ', ($c['email'] ?: '-'), PHP_EOL;
}
04curl
Pour un contrôle rapide, ou pour déboguer depuis un serveur où vous n’installerez rien.
ESPACE=kouassi CLE=votre-cle-d-api [email protected] # version du serveur curl -s https://$ESPACE.madata.app/jsonrpc -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"call","params":{ "service":"common","method":"version","args":[]}}' # uid curl -s https://$ESPACE.madata.app/jsonrpc -H "Content-Type: application/json" \ -d "{\"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":{ \"service\":\"common\",\"method\":\"authenticate\", \"args\":[\"prod_$ESPACE\",\"$LOGIN\",\"$CLE\",{}]}}" # les 5 derniers contacts (uid = 7 dans cet exemple) curl -s https://$ESPACE.madata.app/jsonrpc -H "Content-Type: application/json" \ -d "{\"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":{ \"service\":\"object\",\"method\":\"execute_kw\", \"args\":[\"prod_$ESPACE\",7,\"$CLE\",\"res.partner\", \"search_read\",[[]], {\"fields\":[\"name\"],\"limit\":5}]}}"
05Recette : les impayés du jour
from datetime import date
impayes = ma.chercher('account.move',
[['move_type', '=', 'out_invoice'],
['state', '=', 'posted'],
['payment_state', 'in', ['not_paid', 'partial']],
['invoice_date_due', '<', date.today().isoformat()]],
['name', 'partner_id', 'invoice_date_due', 'amount_residual'],
order='invoice_date_due')
total = sum(f['amount_residual'] for f in impayes)
print(f"{len(impayes)} factures echues, {total:,.0f} FCFA")
for f in impayes[:20]:
print(f['invoice_date_due'], f['partner_id'][1], f['amount_residual'])
06Recette : synchroniser un catalogue
Le motif « chercher, sinon créer » appliqué à un flux d’articles venu d’un autre système. Rejouable sans rien dupliquer.
def synchroniser(article):
"""article = {'ref': 'SKU-001', 'nom': '...', 'prix': 12000}"""
existant = ma.appel('product.template', 'search',
[['default_code', '=', article['ref']]], limit=1)
valeurs = {
'name': article['nom'],
'default_code': article['ref'],
'list_price': article['prix'],
}
if existant:
ma.appel('product.template', 'write', existant, valeurs)
return existant[0], 'mis a jour'
return ma.appel('product.template', 'create', valeurs), 'cree'
for a in flux_externe():
id_, action = synchroniser(a)
print(a['ref'], action, id_)
07Recette : joindre un document
import base64
with open('bon_de_livraison.pdf', 'rb') as f:
contenu = base64.b64encode(f.read()).decode()
ma.appel('ir.attachment', 'create', {
'name': 'bon_de_livraison.pdf',
'datas': contenu,
'res_model': 'account.move',
'res_id': facture_id,
'mimetype': 'application/pdf',
})
08Mettre en production
- Une clé dédiée
- Une par programme, nommée d’après lui, portée par un utilisateur dont les droits correspondent au travail à faire.
- Des secrets hors du code
- Variables d’environnement ou gestionnaire de secrets. Jamais dans un dépôt, même privé.
- Une reprise pensée
- Réessais espacés sur les erreurs passagères, arrêt franc sur les erreurs de droits, et une recherche avant chaque écriture rejouable.
- Un journal
- Modèle, méthode, arguments, réponse ou erreur complète. C’est ce qui transforme un incident en correction de dix minutes.
- Un test de fumée
- Un
search_countau démarrage : si la clé est révoquée, vous le saurez tout de suite, pas au milieu d’un import. - Des lots
- Regroupez lectures et écritures. Le réseau coûte plus cher que le calcul.
Un doute, un blocage
Passez par le support depuis votre espace, avec l’appel exact et la réponse reçue. Et si ce que vous voulez faire tient en une phrase plutôt qu’en un programme, le serveur MCP le fait peut-être déjà.