urban-workflow-playground
Composants web pour écrire, visualiser et tester des workflows urban-workflow, et le serveur qui leur fournit l'analyse et la simulation réelles (parseur ANTLR, visiteur, moteur en mémoire).
Le paquet sert deux usages :
- le playground : une page prête à l'emploi (
urban-workflow-playground, port 8777) qui assemble tous les composants ; - une application cliente : elle monte les composants qu'elle veut, où elle veut (l'éditeur seul, le diagramme à côté d'un formulaire, des onglets de son choix…), et sert l'API dans sa propre application FastAPI.
Lancement du playground
pip install urban-workflow-playground
urban-workflow-playground # http://127.0.0.1:8777
ou python -m urban_workflow_playground. Variables : PLAYGROUND_HOST
(défaut 127.0.0.1), PLAYGROUND_PORT (défaut 8777),
PLAYGROUND_LAYOUT_DIR (répertoire des mises en page, voir plus bas).
Depuis un clone du dépôt : pip install -e ./playground.
Les composants
Chaque composant s'affiche dans l'élément qu'on lui donne et ne communique avec les autres que par un espace de travail partagé. Aucun n'est obligatoire, aucun ne suppose la présence d'un autre.
| Nom | Ce qu'il affiche |
|---|---|
editor |
texte du workflow : coloration, intellisense contextuelle (Ctrl+Espace ou automatique), erreurs soulignées et listées dessous (option diagnostics: false pour ne pas les lister) |
status |
badge du résultat de la dernière analyse (« script valide », « 2 erreur(s) ») |
diagram |
vue graphique éditable (états, transitions, initialiseurs ⚡, expirations ⏰), avec sa barre d'outils ; « Générer le DSL » réécrit le script canonique dans l'espace |
events |
liste des événements ; pour chacun, le socle commun (lecture seule) et l'éditeur visuel de son schéma métier |
states |
même chose pour les états |
scripts |
scripts des règles (AS @nom, langage expression) et des artefacts (USING @nom, langage jslt), avec complétion, erreurs dans le script et aperçu du JSON produit ; fonctions Python du serveur en lecture seule |
rules |
règles métier simulées (les règles de code n'existent pas dans le navigateur : nom, paramètres typés, résultat) |
schemas |
schémas JSON (YAML) des événements, états et artefacts, avec qui les utilise |
structure |
configuration compilée : initialiseurs, états, transitions |
simulation |
événements JSON rejoués sur le vrai moteur en mémoire : états atteints, actions et leurs messages, journal |
help |
aide-mémoire du langage |
examples |
liste déroulante des exemples, qui charge l'exemple choisi dans l'espace |
tabs |
conteneur à onglets de composants (facultatif) |
UrbanWorkflow.components() liste les composants chargés, avec leur
libellé et leur description.
Monter des composants
Une page hôte charge les dépendances (CodeMirror, X6, l'éditeur de schémas)
puis un seul fichier de composants, static/urban-workflow.js, et sa
feuille de style :
<link rel="stylesheet" href="/workflow/static/vendor/codemirror.css">
<link rel="stylesheet" href="/workflow/static/vendor/show-hint.css">
<link rel="stylesheet" href="/workflow/static/vendor/lint.css">
<link rel="stylesheet" href="/workflow/static/vendor/schema-editor.css"> <!-- events, states -->
<link rel="stylesheet" href="/workflow/static/urban-workflow.css">
<div id="texte"></div>
<div id="schema"></div>
<span id="etat"></span>
<script src="/workflow/static/vendor/codemirror.js"></script>
<script src="/workflow/static/vendor/simple.js"></script>
<script src="/workflow/static/vendor/show-hint.js"></script>
<script src="/workflow/static/vendor/lint.js"></script>
<script src="/workflow/static/vendor/x6.min.js"></script> <!-- diagram -->
<script src="/workflow/static/vendor/schema-editor.js"></script> <!-- events, states -->
<script src="/workflow/static/urban-workflow.js"></script>
<script>
const ws = UrbanWorkflow.createWorkspace({
storage: false, // l'application fournit les données
script: monWorkflow,
schemas: [{name: "schema_Demande", yaml: "type: object\n…"}],
});
UrbanWorkflow.mount("editor", "#texte", {workspace: ws});
const diagramme = UrbanWorkflow.mount("diagram", "#schema", {workspace: ws});
diagramme.activate(); // premier affichage : importe le script
UrbanWorkflow.mount("status", "#etat", {workspace: ws});
ws.on("change", (c) => { if (c.what === "script") enregistrer(ws.getScript()); });
</script>
Les dépendances ne sont nécessaires que pour les composants qui s'en
servent : X6 pour diagram, l'éditeur de schémas pour events et
states, CodeMirror pour editor et scripts.
UrbanWorkflow.mount(nom, élément | sélecteur, options) retourne
l'instance du composant. Toutes ont activate() (à appeler quand le
composant redevient visible après avoir été caché : CodeMirror et X6
remesurent, le diagramme importe le script à son premier affichage) et
destroy(). Quelques-unes ont davantage :
| Composant | Méthodes |
|---|---|
editor |
codemirror, focus(), refresh() ; option readOnly |
diagram |
reload(), generate(), getModel(), getPositions(), setLayoutStore(store) ; option layoutStore |
simulation |
run() (promesse du résultat) |
scripts |
select(nom), runPreview(), editor() |
events, states |
select(nom) |
tabs |
show(index), current(), component(nom) |
Plusieurs instances d'un même composant peuvent coexister dans une page,
sur le même espace de travail ou sur des espaces différents : aucun
composant n'utilise d'id fixe.
L'espace de travail
const ws = UrbanWorkflow.createWorkspace({
apiBase: "https://exemple.fr/workflow/", // défaut : déduit de l'URL de urban-workflow.js
storage: "local", // défaut ; false = rien n'est gardé ;
// ou {load(nom), save(nom, valeur)}
storageKey: "uw-playground", // préfixe des clés du localStorage
script: "", rules: [], schemas: [], scripts: [], events: [],
analyzeDelay: 450, // ms entre une modification et l'analyse
});
| Donnée | Lire | Écrire |
|---|---|---|
| script du workflow | getScript() |
setScript(texte), insertText(index, texte) |
règles simulées {name, params: [{name, type}], result} |
getRules() |
setRules(liste), setRule(règle), removeRule(nom) |
schémas {name, yaml} |
getSchemas(), findSchema(nom) |
setSchemas(liste), setSchema(nom, yaml), removeSchema(nom) |
scripts {name, language, source} |
getScripts(), findScript(nom) |
setScripts(liste), setScriptSource(nom, source, langage), removeScript(nom) |
| événements d'exemple | getEvents() |
setEvents(liste) |
| dernière analyse | getAnalysis(), getErrors(), getSymbols() |
analyze() (immédiate, promesse) |
Toute modification du script, des règles, des schémas ou des scripts
relance l'analyse après analyzeDelay. Les règles, schémas et scripts sont
persistés par storage ; le script et les événements ne le sont pas
(c'est à l'application de garder le workflow là où elle le range).
Événements (ws.on(nom, fn) retourne la fonction de désabonnement) :
| Événement | Détail |
|---|---|
change |
{what: "script" | "rules" | "schemas" | "scripts" | "events", origin} |
analyze |
la nouvelle analyse {ok, errors, symbols, summary} |
analyze-error |
le serveur n'a pas répondu |
reveal |
{component, detail} : demande d'affichage (voir plus bas) |
Autres méthodes : simulate(events?) (promesse du résultat de
/api/simulate), loadExample(exemple), api(chemin, corps?, méthode?)
(appel brut à l'API), payload() (le corps commun des requêtes).
Demandes d'affichage. Un composant ne sait pas où sont les autres. Quand
il a besoin d'en montrer un (clic sur une erreur située dans un script,
« Générer le DSL » qui produit un nouveau texte), il appelle
ws.reveal("scripts", {script, line, column}) ou ws.reveal("editor"). Le
composant visé réagit au détail, et le conteneur tabs active l'onglet qui
le porte. Une application qui range les composants autrement écoute
ws.on("reveal", …) pour faire de même.
Onglets
const onglets = UrbanWorkflow.mount("tabs", "#zone", {
workspace: ws,
tabs: [
{label: "Texte", component: "editor"},
{label: "Diagramme", component: "diagram", options: {layoutStore: monStockage}},
{label: "Paramétrage", components: ["rules", "schemas"]},
],
active: 0, // index, ou nom d'un composant
onChange: (index, onglet) => {},
});
onglets.component("diagram").reload();
La page du playground n'est qu'une composition de ce genre
(static/page.js).
Apparence
Les styles sont rangés sous la classe .uw (posée par mount sur chaque
élément) et ne débordent pas sur la page hôte. Les couleurs sont des
variables CSS à redéfinir : --uw-bg, --uw-panel, --uw-border,
--uw-text, --uw-muted, --uw-accent, --uw-ok, --uw-ko, --uw-mono.
:root { --uw-accent: #e0a030; }
Le serveur dans une application cliente
create_app fabrique l'application FastAPI (API, page et fichiers
statiques). On la monte sous le préfixe de son choix :
from fastapi import FastAPI
from urban_workflow_playground.app import create_app
from mon_appli.regles import plafond_ok, client_connu
app = FastAPI()
app.mount("/workflow", create_app(
python_functions={"plafond_ok": plafond_ok, "client_connu": client_connu},
layout_dir="/var/lib/mon-appli/layouts",
))
python_functions: fonctions(ctx, **params)que les workflows référencent parDEFINE RULE … AS @nom; leurs noms sont réservés, et le composantscriptsles montre en lecture seule. Par défaut, les exemples du playground ;{}pour n'en publier aucune. Le navigateur, lui, ne peut écrire que des scriptsexpressionetjslt: aucun code Python venu du navigateur n'est exécuté ;layout_dir: répertoire des fichiers de mise en page du diagramme (par défautPLAYGROUND_LAYOUT_DIR, sinon les positions restent dans le navigateur).
La page et les composants n'utilisent que des URL relatives : monté
sous /workflow, le playground s'ouvre sur /workflow/ (avec la barre
finale) et les composants chargés depuis /workflow/static/… parlent à
/workflow/api/… sans configuration. Une page hôte servie ailleurs passe
apiBase à createWorkspace.
API HTTP
| Route | Rôle |
|---|---|
POST /api/analyze |
{script, rules, schemas, scripts} → {ok, is_fragment, errors, symbols, summary} ; erreurs positionnées (line, column, script si l'erreur est dans un script) |
POST /api/simulate |
idem + events → étapes (workflows impactés, états, actions et messages, journal) et état final du stockage |
POST /api/diagram |
{script} → modèle de surface du diagramme |
POST /api/generate |
{model, rules, schemas, scripts} → script canonique + analyse |
POST /api/artifact-preview |
{script, …, artifact, event, state} → JSON produit par l'artefact, ou les violations de son schéma |
GET /api/base-event-schema, GET /api/base-state-schema |
socles communs (JSON Schema) |
GET/PUT /api/layout/{workflow} |
mise en page du diagramme (ci-dessous) |
GET /healthz |
sonde de vie |
GET /static/urban-workflow.js, /static/urban-workflow.css |
composants et styles en un fichier |
Mise en page du diagramme
Comme les schémas et les scripts, la position des états n'est pas dans
le document de workflow : elle vit dans un fichier à côté,
<workflow>.layout.json, indexé par nom d'état :
{"version": 1, "workflow": "Reclamation",
"states": {"Ouvert": {"x": 280, "y": 60}, "Clos": {"x": 600, "y": 100}}}
Un état absent du fichier (ajouté dans l'éditeur de texte, par exemple) est placé automatiquement, seul : les autres gardent leur place. « Réorganiser » replace tout.
Trois manières de l'enregistrer :
-
fichiers sur le serveur — avec
layout_dir(ouPLAYGROUND_LAYOUT_DIR=/chemin), le serveur lit et écrit/chemin/<workflow>.layout.jsonviaGET /api/layout/{workflow}({"enabled": true, "layout": …|null}) etPUT /api/layout/{workflow}(le document ci-dessus, ou la forme courte{"Etat": {"x": …, "y": …}}). Nom de workflow et positions contrôlés, écriture atomique.urban_workflow_playground.layout.LayoutDirectoryfait la même chose depuis Python ; -
navigateur — sans répertoire (
enabled: false), les positions restent dans lelocalStorage; -
stockage de l'application — elle branche le sien sur le composant :
const diagramme = UrbanWorkflow.mount("diagram", "#schema", { workspace: ws, layoutStore: { load: (workflow) => fetch(`/mon-api/${workflow}/layout`).then(r => r.json()), save: (workflow, positions) => fetch(`/mon-api/${workflow}/layout`, {method: "PUT", body: JSON.stringify(positions)}), }, }); // ou plus tard : diagramme.setLayoutStore(store); diagramme.reload();
loadrend{Etat: {x, y}}(ou une promesse, ounull) ;setLayoutStore(null)revient au stockage par défaut.
Notes
- Outil de développement : le serveur n'a aucune authentification. Une application qui le monte le place derrière la sienne ; ne pas l'exposer publiquement tel quel.
- Les règles simulées retournent le booléen configuré (le moteur et la validation sont réels ; seule la logique interne des règles est remplacée).
- Le visiteur s'arrête à la première erreur sémantique : les erreurs se corrigent donc une par une (la syntaxe, elle, est remontée en bloc).
- Le diagramme travaille sur le modèle de surface du script
(
urban_workflow.dsl.surface) : gardes et blocs d'exécution y restent du texte DSL brut ; la génération réécrit le script en forme canonique (commentaires--non conservés). Les scriptsFRAGMENTou àINCLUDEne sont pas représentables en diagramme. urban_workflow_playground/static/vendor/: CodeMirror 5.65.18 et AntV X6 3.1.8 (licences MIT), embarqués pour fonctionner hors ligne ; éditeur de schémasschema-editor.jsconstruit depuisplayground/schema-editor/.
Metadata
Release files for urban-workflow-playground 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| urban_workflow_playground-0.2.0.tar.gz | 528.5 kB | With provenance | Details for urban_workflow_playground-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| urban_workflow_playground-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for urban_workflow_playground-0.2.0-py3-none-any.whl |
Total release size: 1.1 MB
Release files / urban_workflow_playground-0.2.0.tar.gz
| Download URL | urban_workflow_playground-0.2.0.tar.gz |
|---|---|
| Size | 528.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
36b43e1c2273312fca6831450333350bd5692e74d5267ecd6a27cb7fcc26a50d
|
|
BLAKE2b-256 checksum How to use checksums |
c583582cf1b81ad4d0b116276f45abfc48d268bc0deb856cc0c9ffc94fbf3b68
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logRelease files / urban_workflow_playground-0.2.0-py3-none-any.whl
| Download URL | urban_workflow_playground-0.2.0-py3-none-any.whl |
|---|---|
| Size | 535.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9e1bdf569bd626923a4d13b80d91adbd01c3ccef081c6d184b238ef291be0af9
|
|
BLAKE2b-256 checksum How to use checksums |
5ae5b1df532be2458ed8a13e58e00954416d84799550e7cbf2e4395b0a689485
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logSecurity for urban-workflow-playground 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for urban_workflow_playground-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.