Metadata-Version: 2.4
Name: urban-workflow
Version: 0.3.0
Summary: Workflow engine driven by a business DSL — Python port of Black-Beard-Sdk/Workflow, with JSON outputs normalized by urban-json (JSLT).
Author-email: Gael Beard <gaelgael5@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/gaelgael5/urban-workflow
Project-URL: Issues, https://github.com/gaelgael5/urban-workflow/issues
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: urban-json>=0.2.1
Provides-Extra: sql
Requires-Dist: sqlalchemy>=2.0; extra == "sql"
Provides-Extra: schemas
Requires-Dist: PyYAML>=6.0; extra == "schemas"
Requires-Dist: jsonschema>=4.18; extra == "schemas"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Requires-Dist: sqlalchemy>=2.0; extra == "dev"
Requires-Dist: fastapi>=0.110; extra == "dev"
Requires-Dist: uvicorn>=0.27; extra == "dev"
Requires-Dist: httpx>=0.27; extra == "dev"
Requires-Dist: PyYAML>=6.0; extra == "dev"
Requires-Dist: jsonschema>=4.18; extra == "dev"
Dynamic: license-file

# urban-workflow

*English · [Français](https://github.com/gaelgael5/urban-workflow/blob/v0.3.0/README.md)*

Workflow engine driven by a **domain-specific language** (DSL) — a Python
port of [Black-Beard-Sdk/Workflow](https://github.com/Black-Beard-Sdk/Workflow)
(.NET), whose JSON output normalization system is replaced by
[urban-json](https://pypi.org/project/urban-json/) (a JSLT engine): the
messages produced by the engine are described by declarative `.jslt`
templates that can be changed without redeploying.

## A workflow in a few lines

```sql
NAME ValidationAchat VERSION 1
MATCHING (Type = 'achat')

DEFINE EVENT  DemandeSoumise 'une demande est déposée';
DEFINE RULE   MontantInferieur (DECIMAL seuil) 'montant sous le seuil';
DEFINE ACTION NotifierValideur (TEXT dossier)  'demande d''approbation';
DEFINE CONST  seuil_auto 500 'approbation automatique sous ce montant';

INITIALIZE WORKFLOW
    ON EVENT DemandeSoumise RECURSIVE SWITCH Tri

DEFINE STATE Tri 'aiguillage'
    ON EVENT DemandeSoumise
        WHEN MontantInferieur(seuil = seuil_auto)
            SWITCH Approuvee
        EXECUTE NotifierValideur(dossier = @Workflow.ExternalId)
            SWITCH EnValidation
;
DEFINE STATE Approuvee    'approbation automatique' ;
DEFINE STATE EnValidation 'en attente d''un valideur'
    EXPIRE AFTER 3 DAY SWITCH Expiree
;
DEFINE STATE Expiree      'sans réponse' ;
```

```python
from urban_workflow import (
    FunctionRegistry, MemoryStorage, Workflow,
    WorkflowEngine, WorkflowFactory, WorkflowProcessor, WorkflowsConfig,
)
from urban_workflow.dsl import parse_config
from urban_workflow.outputs import default_memory_output

rules = FunctionRegistry("rule")
rules.add("MontantInferieur", lambda ctx, seuil: float(
    ctx.incoming_event.extended_datas.get("Montant", 0)) < seuil)

storage = MemoryStorage()
configs = WorkflowsConfig().add_document(parse_config(SCRIPT, rules=rules))
processor = WorkflowProcessor(configs, WorkflowFactory())
processor.load_existing_workflows_by_external_id = (
    lambda key: storage.get_by(Workflow, key, "external_id"))
processor.output_actions = default_memory_output(storage)
engine = WorkflowEngine(processor)

engine.evaluate_event({"Name": "DemandeSoumise", "ExternalId": "DA-42",
                       "Type": "achat", "Montant": 120})
```

## Documentation

| Guide | Contents |
|---|---|
| [docs/en/dsl-guide.md](https://github.com/gaelgael5/urban-workflow/blob/v0.3.0/docs/en/dsl-guide.md) | **Writing a workflow**: complete language reference — instance creation, `SWITCH` transitions, `WHEN` conditions, actions, expiration, event schemas, **deduplication keys** (`UNIQUE`, `DedupKey`, automatic fallback), fragments |
| [docs/en/rules-guide.md](https://github.com/gaelgael5/urban-workflow/blob/v0.3.0/docs/en/rules-guide.md) | **Rules and scripts**: `DEFINE RULE … AS @script`, multi-language `ScriptRegistry`, the `expression` language (operators, functions, `NULL` semantics), checks |
| [docs/en/artifacts-guide.md](https://github.com/gaelgael5/urban-workflow/blob/v0.3.0/docs/en/artifacts-guide.md) | **JSON artifacts**: `DEFINE ARTIFACT … AS @schema USING @script`, `jslt` scripts (urban-json), `@artifact.Name` in actions, schema validation |
| [docs/en/integration-guide.md](https://github.com/gaelgael5/urban-workflow/blob/v0.3.0/docs/en/integration-guide.md) | **Using it in code**: business rules, engine assembly, JSLT templates, output chain, technical events |
| [docs/en/testing-guide.md](https://github.com/gaelgael5/urban-workflow/blob/v0.3.0/docs/en/testing-guide.md) | **Testing it**: pure rules, configuration, templates, end-to-end scenarios |
| [docs/analyse-portage.md](https://github.com/gaelgael5/urban-workflow/blob/v0.3.0/docs/analyse-portage.md) (French) | Analysis of the source project and the key design choices of the port |

## Playground

**Standalone web components** (editor with intellisense, diagram,
schemas, scripts, simulation on the real engine…) that an application
mounts as needed, and a page that assembles them — a separate
distribution, [`urban-workflow-playground`](https://github.com/gaelgael5/urban-workflow/blob/v0.3.0/playground/README.en.md):

```bash
pip install urban-workflow-playground
urban-workflow-playground     # http://127.0.0.1:8777
```

## Installation and development

```bash
pip install -e .[dev]
python3 -m pytest -q
```

Single core dependency: `urban-json` (which brings the ANTLR runtime used
by the DSL parser). Python ≥ 3.10.

## Porting status

Foundation, DSL (ANTLR grammars taken from the source), engine, urban-json
templating and SQL persistence (`[sql]` extra, SQLAlchemy Core) are
functional and tested (end-to-end scenarios *DSL script → events → states
→ messages*, lifecycle persisted in SQL), with GitHub Actions CI (Python
3.10 → 3.13 tests, ruff lint). Known bugs of the source engine are not
reproduced — each one is covered by a dedicated test.

## License

See [LICENSE](https://github.com/gaelgael5/urban-workflow/blob/v0.3.0/LICENSE).
