flowlit 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flowlit-0.1.0/.gitignore +12 -0
- flowlit-0.1.0/LICENSE +21 -0
- flowlit-0.1.0/PKG-INFO +198 -0
- flowlit-0.1.0/README.md +168 -0
- flowlit-0.1.0/doc/api-reference.md +357 -0
- flowlit-0.1.0/doc/architecture.md +256 -0
- flowlit-0.1.0/doc/concurrency.md +276 -0
- flowlit-0.1.0/doc/configuration.md +104 -0
- flowlit-0.1.0/doc/examples.md +99 -0
- flowlit-0.1.0/doc/executors.md +396 -0
- flowlit-0.1.0/doc/getting-started.md +140 -0
- flowlit-0.1.0/doc/observability.md +160 -0
- flowlit-0.1.0/doc/plan-format.md +573 -0
- flowlit-0.1.0/doc/testing.md +110 -0
- flowlit-0.1.0/examples/hello_workflow.yaml +26 -0
- flowlit-0.1.0/examples/http_shell_noop_pipeline.yaml +33 -0
- flowlit-0.1.0/examples/optional_step_plan.yaml +46 -0
- flowlit-0.1.0/examples/sleep_sequential_workflow.yaml +34 -0
- flowlit-0.1.0/examples/sleep_workflow.yaml +34 -0
- flowlit-0.1.0/examples/with_bash_plan.yaml +49 -0
- flowlit-0.1.0/examples/with_http_plan.yaml +29 -0
- flowlit-0.1.0/pyproject.toml +73 -0
- flowlit-0.1.0/src/flowlit/__init__.py +30 -0
- flowlit-0.1.0/src/flowlit/application/__init__.py +7 -0
- flowlit-0.1.0/src/flowlit/application/dto.py +100 -0
- flowlit-0.1.0/src/flowlit/application/errors.py +119 -0
- flowlit-0.1.0/src/flowlit/application/events.py +201 -0
- flowlit-0.1.0/src/flowlit/application/executor_dispatcher.py +112 -0
- flowlit-0.1.0/src/flowlit/application/executor_registry.py +26 -0
- flowlit-0.1.0/src/flowlit/application/orchestrator.py +393 -0
- flowlit-0.1.0/src/flowlit/application/ports/__init__.py +6 -0
- flowlit-0.1.0/src/flowlit/application/ports/event_bus.py +31 -0
- flowlit-0.1.0/src/flowlit/application/ports/execution_coordinator.py +45 -0
- flowlit-0.1.0/src/flowlit/application/ports/executor.py +153 -0
- flowlit-0.1.0/src/flowlit/application/ports/plan_parser.py +28 -0
- flowlit-0.1.0/src/flowlit/application/ports/workflow_repository.py +24 -0
- flowlit-0.1.0/src/flowlit/application/workflow_service.py +342 -0
- flowlit-0.1.0/src/flowlit/domain/__init__.py +7 -0
- flowlit-0.1.0/src/flowlit/domain/dag.py +99 -0
- flowlit-0.1.0/src/flowlit/domain/errors.py +245 -0
- flowlit-0.1.0/src/flowlit/domain/spec_resolution.py +232 -0
- flowlit-0.1.0/src/flowlit/domain/step.py +333 -0
- flowlit-0.1.0/src/flowlit/domain/workflow.py +746 -0
- flowlit-0.1.0/src/flowlit/infrastructure/__init__.py +7 -0
- flowlit-0.1.0/src/flowlit/infrastructure/event_bus/__init__.py +1 -0
- flowlit-0.1.0/src/flowlit/infrastructure/event_bus/asyncio_event_bus.py +72 -0
- flowlit-0.1.0/src/flowlit/infrastructure/execution/__init__.py +3 -0
- flowlit-0.1.0/src/flowlit/infrastructure/execution/asyncio_execution_coordinator.py +140 -0
- flowlit-0.1.0/src/flowlit/infrastructure/executors/__init__.py +11 -0
- flowlit-0.1.0/src/flowlit/infrastructure/executors/base_executor.py +102 -0
- flowlit-0.1.0/src/flowlit/infrastructure/executors/http_executor.py +191 -0
- flowlit-0.1.0/src/flowlit/infrastructure/executors/noop_executor.py +40 -0
- flowlit-0.1.0/src/flowlit/infrastructure/executors/shell_executor.py +133 -0
- flowlit-0.1.0/src/flowlit/infrastructure/executors/sleep_executor.py +56 -0
- flowlit-0.1.0/src/flowlit/infrastructure/repositories/__init__.py +1 -0
- flowlit-0.1.0/src/flowlit/infrastructure/repositories/in_memory_repository.py +36 -0
- flowlit-0.1.0/src/flowlit/infrastructure/yaml_loader/__init__.py +1 -0
- flowlit-0.1.0/src/flowlit/infrastructure/yaml_loader/loader.py +103 -0
- flowlit-0.1.0/src/flowlit/infrastructure/yaml_loader/schema.py +61 -0
- flowlit-0.1.0/src/flowlit/interfaces/__init__.py +6 -0
- flowlit-0.1.0/src/flowlit/interfaces/cli/__init__.py +1 -0
- flowlit-0.1.0/src/flowlit/interfaces/cli/main.py +170 -0
- flowlit-0.1.0/src/flowlit/interfaces/composition_root.py +92 -0
- flowlit-0.1.0/src/flowlit/plugins.py +91 -0
- flowlit-0.1.0/src/flowlit/py.typed +0 -0
- flowlit-0.1.0/tests/__init__.py +0 -0
- flowlit-0.1.0/tests/fixtures/concurrent_sleep_plan.yaml +14 -0
- flowlit-0.1.0/tests/fixtures/custom_executor_plan.yaml +8 -0
- flowlit-0.1.0/tests/fixtures/cyclic_plan.yaml +12 -0
- flowlit-0.1.0/tests/fixtures/else_cascade_taint_plan.yaml +25 -0
- flowlit-0.1.0/tests/fixtures/else_fallback_plan.yaml +22 -0
- flowlit-0.1.0/tests/fixtures/else_not_fired_plan.yaml +22 -0
- flowlit-0.1.0/tests/fixtures/four_independent_sleeps_plan.yaml +26 -0
- flowlit-0.1.0/tests/fixtures/generic_timeout_optional_plan.yaml +16 -0
- flowlit-0.1.0/tests/fixtures/generic_timeout_plan.yaml +11 -0
- flowlit-0.1.0/tests/fixtures/idempotency_key_plan.yaml +8 -0
- flowlit-0.1.0/tests/fixtures/invalid_spec_plan.yaml +10 -0
- flowlit-0.1.0/tests/fixtures/long_sleep_plan.yaml +8 -0
- flowlit-0.1.0/tests/fixtures/malformed_when_plan.yaml +15 -0
- flowlit-0.1.0/tests/fixtures/optional_blocking_plan.yaml +22 -0
- flowlit-0.1.0/tests/fixtures/optional_non_blocking_plan.yaml +16 -0
- flowlit-0.1.0/tests/fixtures/output_passing_plan.yaml +14 -0
- flowlit-0.1.0/tests/fixtures/per_edge_blocking_plan.yaml +26 -0
- flowlit-0.1.0/tests/fixtures/placeholder_resolves_to_invalid_spec_plan.yaml +20 -0
- flowlit-0.1.0/tests/fixtures/placeholder_typed_spec_plan.yaml +22 -0
- flowlit-0.1.0/tests/fixtures/required_failure_cancels_sibling_plan.yaml +14 -0
- flowlit-0.1.0/tests/fixtures/retry_plan.yaml +10 -0
- flowlit-0.1.0/tests/fixtures/sample_plan.yaml +20 -0
- flowlit-0.1.0/tests/fixtures/silent_optional_failure_plan.yaml +10 -0
- flowlit-0.1.0/tests/fixtures/singleton_plan.yaml +10 -0
- flowlit-0.1.0/tests/fixtures/singleton_sleep_plan.yaml +10 -0
- flowlit-0.1.0/tests/fixtures/sleep_plan.yaml +8 -0
- flowlit-0.1.0/tests/fixtures/sleep_then_reference_plan.yaml +14 -0
- flowlit-0.1.0/tests/fixtures/status_condition_plan.yaml +23 -0
- flowlit-0.1.0/tests/fixtures/undeclared_output_reference_plan.yaml +17 -0
- flowlit-0.1.0/tests/fixtures/unknown_blocking_override_plan.yaml +18 -0
- flowlit-0.1.0/tests/fixtures/unknown_step_type_plan.yaml +14 -0
- flowlit-0.1.0/tests/fixtures/unresolvable_output_reference_plan.yaml +20 -0
- flowlit-0.1.0/tests/fixtures/unresolved_dep_plan.yaml +7 -0
- flowlit-0.1.0/tests/fixtures/when_false_non_blocking_plan.yaml +23 -0
- flowlit-0.1.0/tests/fixtures/when_false_plan.yaml +21 -0
- flowlit-0.1.0/tests/fixtures/when_not_plan.yaml +15 -0
- flowlit-0.1.0/tests/fixtures/when_references_optional_step_plan.yaml +16 -0
- flowlit-0.1.0/tests/fixtures/when_true_plan.yaml +15 -0
- flowlit-0.1.0/tests/fixtures/when_true_step_fails_plan.yaml +21 -0
- flowlit-0.1.0/tests/integration/__init__.py +0 -0
- flowlit-0.1.0/tests/integration/test_end_to_end.py +988 -0
- flowlit-0.1.0/tests/unit/__init__.py +0 -0
- flowlit-0.1.0/tests/unit/application/__init__.py +0 -0
- flowlit-0.1.0/tests/unit/application/test_executor_dispatcher.py +363 -0
- flowlit-0.1.0/tests/unit/application/test_executor_registry.py +31 -0
- flowlit-0.1.0/tests/unit/application/test_orchestrator.py +484 -0
- flowlit-0.1.0/tests/unit/application/test_workflow_service.py +196 -0
- flowlit-0.1.0/tests/unit/domain/__init__.py +0 -0
- flowlit-0.1.0/tests/unit/domain/test_dag.py +143 -0
- flowlit-0.1.0/tests/unit/domain/test_spec_resolution.py +257 -0
- flowlit-0.1.0/tests/unit/domain/test_step_state.py +213 -0
- flowlit-0.1.0/tests/unit/domain/test_workflow_state.py +1348 -0
- flowlit-0.1.0/tests/unit/infrastructure/__init__.py +0 -0
- flowlit-0.1.0/tests/unit/infrastructure/test_asyncio_event_bus.py +136 -0
- flowlit-0.1.0/tests/unit/infrastructure/test_asyncio_execution_coordinator.py +289 -0
- flowlit-0.1.0/tests/unit/infrastructure/test_base_executor.py +93 -0
- flowlit-0.1.0/tests/unit/infrastructure/test_http_executor.py +283 -0
- flowlit-0.1.0/tests/unit/infrastructure/test_in_memory_repository.py +75 -0
- flowlit-0.1.0/tests/unit/infrastructure/test_shell_executor.py +138 -0
- flowlit-0.1.0/tests/unit/infrastructure/test_sleep_executor.py +76 -0
- flowlit-0.1.0/tests/unit/infrastructure/test_yaml_loader.py +411 -0
- flowlit-0.1.0/tests/unit/interfaces/__init__.py +0 -0
- flowlit-0.1.0/tests/unit/interfaces/cli/__init__.py +0 -0
- flowlit-0.1.0/tests/unit/interfaces/cli/test_main.py +241 -0
- flowlit-0.1.0/tests/unit/interfaces/test_composition_root.py +36 -0
- flowlit-0.1.0/tests/unit/test_plugins.py +77 -0
flowlit-0.1.0/.gitignore
ADDED
flowlit-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 shadiwazir
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
flowlit-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: flowlit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight, event-driven workflow execution engine.
|
|
5
|
+
Project-URL: Homepage, https://github.com/shadiwazir/flowlit
|
|
6
|
+
Project-URL: Repository, https://github.com/shadiwazir/flowlit
|
|
7
|
+
Project-URL: Issues, https://github.com/shadiwazir/flowlit/issues
|
|
8
|
+
Author: shadiwazir
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: aiohttp>=3.9
|
|
21
|
+
Requires-Dist: pydantic>=2.0
|
|
22
|
+
Requires-Dist: pyyaml>=6.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
28
|
+
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# Flowlit
|
|
32
|
+
|
|
33
|
+
A lightweight, event-driven workflow execution engine. Hand it a YAML
|
|
34
|
+
plan — a set of steps, their dependencies, and per-step instructions —
|
|
35
|
+
and Flowlit validates it as a DAG, runs it, and hands each step to
|
|
36
|
+
whichever **executor** is registered for that step's type, running every
|
|
37
|
+
step the DAG allows to run at once *genuinely concurrently*, not one at a
|
|
38
|
+
time.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install -e ".[dev]"
|
|
42
|
+
flowlit examples/hello_workflow.yaml
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
-> greet (noop): submitted
|
|
47
|
+
step greet (noop): Hello from Flowlit!
|
|
48
|
+
<- greet: completed
|
|
49
|
+
-> build (noop): submitted
|
|
50
|
+
step build (noop): Pretending to build the project...
|
|
51
|
+
<- build: completed
|
|
52
|
+
-> test (noop): submitted
|
|
53
|
+
step test (noop): Pretending to run tests...
|
|
54
|
+
<- test: completed
|
|
55
|
+
-> report (noop): submitted
|
|
56
|
+
step report (noop): Build and test both finished.
|
|
57
|
+
<- report: completed
|
|
58
|
+
Workflow hello-workflow: completed
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Why Flowlit
|
|
62
|
+
|
|
63
|
+
- **The DAG alone decides concurrency.** Two steps that share nothing but
|
|
64
|
+
a parent run genuinely at once, as independent `asyncio.Task`s — not
|
|
65
|
+
serialized because they happen to share a step type, and not
|
|
66
|
+
artificially parallelized either. See
|
|
67
|
+
[Concurrency model](doc/concurrency.md).
|
|
68
|
+
- **Data flow is explicit and auditable.** A step reads a prior step's
|
|
69
|
+
output only via a small, closed
|
|
70
|
+
`${{ steps.<id>.output.<path> }}` placeholder syntax — no expressions,
|
|
71
|
+
no filters, nothing a semi-trusted plan author (an AI agent proposing a
|
|
72
|
+
workflow, say) could use to smuggle in arbitrary logic. See
|
|
73
|
+
[Plan format](doc/plan-format.md).
|
|
74
|
+
- **Failure handling is a first-class, per-step decision.** `optional`,
|
|
75
|
+
`blocking`, `silent_failures`, and per-edge `blocking_overrides` let
|
|
76
|
+
two different dependents of the same step disagree about whether
|
|
77
|
+
they're blocked by its failure — expressive enough for "best-effort
|
|
78
|
+
logging that should never stop a required deploy step." See
|
|
79
|
+
[Plan format](doc/plan-format.md#failure-handling-optional-blocking-silent_failures).
|
|
80
|
+
- **Branching without an expression language.** `when`/`when_not`/`else`
|
|
81
|
+
gate a step on a prior step's output or outcome, reusing the same
|
|
82
|
+
closed placeholder syntax — no new DSL to audit. A branch step is
|
|
83
|
+
implicitly optional, so picking one path over another can't itself
|
|
84
|
+
abort the workflow. See
|
|
85
|
+
[Branching](doc/plan-format.md#branching-when-when_not-else).
|
|
86
|
+
- **Clean, layered architecture, built to extend.** Four layers
|
|
87
|
+
(`domain` → `application` → `infrastructure` → `interfaces`), each only
|
|
88
|
+
depending on the ones below it, with ports (`Protocol`s) separating
|
|
89
|
+
contracts from implementations. Add a step type without forking the
|
|
90
|
+
repo via one call to `register_executor()`. See
|
|
91
|
+
[Architecture](doc/architecture.md).
|
|
92
|
+
- **Built for both a blocking CLI and a future async server.**
|
|
93
|
+
`run_to_completion()` blocks until done (right for a CLI); `start_workflow()`
|
|
94
|
+
+ polling `get_status()` doesn't (right for an MCP server or any
|
|
95
|
+
poll-driven client) — same DAG, same engine, no separate code path. See
|
|
96
|
+
[API reference](doc/api-reference.md).
|
|
97
|
+
|
|
98
|
+
Everything runs in memory — nothing survives a process restart. That's a
|
|
99
|
+
deliberate v1 choice, not an oversight: the goal is to get the
|
|
100
|
+
architecture right first, with the `WorkflowRepository`/`EventBus` ports
|
|
101
|
+
already shaped so a persistent implementation can be dropped in later
|
|
102
|
+
without touching orchestration logic. The eventual goal is to run this as
|
|
103
|
+
an MCP server so agent tools (e.g. VS Code GitHub Copilot) can submit and
|
|
104
|
+
drive plans.
|
|
105
|
+
|
|
106
|
+
## Key features
|
|
107
|
+
|
|
108
|
+
| | |
|
|
109
|
+
|---|---|
|
|
110
|
+
| **Real concurrency** | Independent steps run as genuinely parallel `asyncio.Task`s, driven purely by the dependency graph. |
|
|
111
|
+
| **Four built-in executors** | `noop`, `sleep`, `shell` (with timeout), `http` (with retry/backoff) — see [Executors](doc/executors.md). |
|
|
112
|
+
| **Typed, defaulted specs** | Any executor can declare a pydantic `spec_model` and get validation for free, at the earliest honest point. |
|
|
113
|
+
| **Pluggable executors** | `register_executor("my_type", MyExecutor())` — no fork required. |
|
|
114
|
+
| **Rich failure semantics** | Required vs. optional steps, blocking vs. non-blocking dependents (with per-edge overrides), silent vs. visible failures, cascading skips. |
|
|
115
|
+
| **Clean cancellation** | A required failure — or an explicit `cancel_workflow()` — stops every in-flight step promptly and deterministically. |
|
|
116
|
+
| **Output passing** | `${{ steps.<id>.output.<path> }}` placeholders, resolved just before dispatch. |
|
|
117
|
+
| **Two execution modes** | `run_to_completion()` (blocking) and `start_workflow()` + poll (non-blocking), from the exact same engine. |
|
|
118
|
+
| **JSON-ready DTOs** | `WorkflowService` returns primitives-only dataclasses — no domain leakage across the API boundary. |
|
|
119
|
+
|
|
120
|
+
## Quickstart
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
python -m venv .venv
|
|
124
|
+
source .venv/bin/activate # or `.venv\Scripts\activate` on Windows
|
|
125
|
+
pip install -e ".[dev]"
|
|
126
|
+
|
|
127
|
+
flowlit examples/hello_workflow.yaml
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pytest # run the test suite
|
|
132
|
+
ruff check src tests # lint
|
|
133
|
+
mypy src # type-check
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
See [Getting started](doc/getting-started.md) for a full walkthrough,
|
|
137
|
+
including using flowlit as a library and reading its CLI output.
|
|
138
|
+
|
|
139
|
+
## Documentation
|
|
140
|
+
|
|
141
|
+
Detailed, modular guides live under [`doc/`](doc/):
|
|
142
|
+
|
|
143
|
+
| Guide | Covers |
|
|
144
|
+
|---|---|
|
|
145
|
+
| [Getting started](doc/getting-started.md) | Install, quickstart, CLI flags, using flowlit as a library. |
|
|
146
|
+
| [Plan format](doc/plan-format.md) | The YAML plan schema: steps, dependencies, output-passing placeholders, failure-handling fields. |
|
|
147
|
+
| [Architecture](doc/architecture.md) | The four-layer design, execution flow, event bus, composition root — with diagrams. |
|
|
148
|
+
| [Concurrency model](doc/concurrency.md) | How the DAG drives real concurrency, cancellation semantics, `max_concurrent_steps`. |
|
|
149
|
+
| [Executors](doc/executors.md) | The `noop`/`sleep`/`shell`/`http` executors, adding your own, registering one from outside the repo. |
|
|
150
|
+
| [API reference](doc/api-reference.md) | `WorkflowService`, ports, DTOs, events, and the full exception hierarchy. |
|
|
151
|
+
| [Configuration](doc/configuration.md) | CLI flags, `build_app_context()` parameters, per-step configuration. |
|
|
152
|
+
| [Observability](doc/observability.md) | Logging setup and live progress via the event bus. |
|
|
153
|
+
| [Testing](doc/testing.md) | Test suite layout and what each part covers. |
|
|
154
|
+
| [Examples](doc/examples.md) | A guided tour of every plan in [`examples/`](examples/). |
|
|
155
|
+
|
|
156
|
+
## A minimal plan
|
|
157
|
+
|
|
158
|
+
```yaml
|
|
159
|
+
id: build-and-deploy
|
|
160
|
+
name: Build and Deploy
|
|
161
|
+
steps:
|
|
162
|
+
- id: fetch_source
|
|
163
|
+
type: shell
|
|
164
|
+
spec:
|
|
165
|
+
command: "echo '{\"commit_sha\": \"abc123\"}'"
|
|
166
|
+
depends_on: []
|
|
167
|
+
|
|
168
|
+
- id: notify
|
|
169
|
+
type: noop
|
|
170
|
+
spec:
|
|
171
|
+
message: "deployed commit ${{ steps.fetch_source.output.commit_sha }}"
|
|
172
|
+
depends_on: [fetch_source]
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`type` selects the executor; `spec` is whatever that executor needs;
|
|
176
|
+
`depends_on` must form a DAG. See [Plan format](doc/plan-format.md) for
|
|
177
|
+
the complete schema, including `optional`/`blocking`/`silent_failures`
|
|
178
|
+
and `blocking_overrides`.
|
|
179
|
+
|
|
180
|
+
## Project layout
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
src/flowlit/
|
|
184
|
+
├── domain/ Step/Workflow entities, DAG validation -- stdlib only
|
|
185
|
+
├── application/ use cases, ports (Protocols), orchestration
|
|
186
|
+
├── infrastructure/ YAML loading, in-memory event bus/repository, executors
|
|
187
|
+
├── interfaces/ CLI today; a future MCP server; the composition root
|
|
188
|
+
└── plugins.py register_executor() -- the public extension seam
|
|
189
|
+
examples/ runnable YAML plans
|
|
190
|
+
tests/ unit + integration test suite
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
See [Architecture](doc/architecture.md) for the reasoning behind this
|
|
194
|
+
layering.
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT — see [`pyproject.toml`](pyproject.toml).
|
flowlit-0.1.0/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Flowlit
|
|
2
|
+
|
|
3
|
+
A lightweight, event-driven workflow execution engine. Hand it a YAML
|
|
4
|
+
plan — a set of steps, their dependencies, and per-step instructions —
|
|
5
|
+
and Flowlit validates it as a DAG, runs it, and hands each step to
|
|
6
|
+
whichever **executor** is registered for that step's type, running every
|
|
7
|
+
step the DAG allows to run at once *genuinely concurrently*, not one at a
|
|
8
|
+
time.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install -e ".[dev]"
|
|
12
|
+
flowlit examples/hello_workflow.yaml
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
-> greet (noop): submitted
|
|
17
|
+
step greet (noop): Hello from Flowlit!
|
|
18
|
+
<- greet: completed
|
|
19
|
+
-> build (noop): submitted
|
|
20
|
+
step build (noop): Pretending to build the project...
|
|
21
|
+
<- build: completed
|
|
22
|
+
-> test (noop): submitted
|
|
23
|
+
step test (noop): Pretending to run tests...
|
|
24
|
+
<- test: completed
|
|
25
|
+
-> report (noop): submitted
|
|
26
|
+
step report (noop): Build and test both finished.
|
|
27
|
+
<- report: completed
|
|
28
|
+
Workflow hello-workflow: completed
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Why Flowlit
|
|
32
|
+
|
|
33
|
+
- **The DAG alone decides concurrency.** Two steps that share nothing but
|
|
34
|
+
a parent run genuinely at once, as independent `asyncio.Task`s — not
|
|
35
|
+
serialized because they happen to share a step type, and not
|
|
36
|
+
artificially parallelized either. See
|
|
37
|
+
[Concurrency model](doc/concurrency.md).
|
|
38
|
+
- **Data flow is explicit and auditable.** A step reads a prior step's
|
|
39
|
+
output only via a small, closed
|
|
40
|
+
`${{ steps.<id>.output.<path> }}` placeholder syntax — no expressions,
|
|
41
|
+
no filters, nothing a semi-trusted plan author (an AI agent proposing a
|
|
42
|
+
workflow, say) could use to smuggle in arbitrary logic. See
|
|
43
|
+
[Plan format](doc/plan-format.md).
|
|
44
|
+
- **Failure handling is a first-class, per-step decision.** `optional`,
|
|
45
|
+
`blocking`, `silent_failures`, and per-edge `blocking_overrides` let
|
|
46
|
+
two different dependents of the same step disagree about whether
|
|
47
|
+
they're blocked by its failure — expressive enough for "best-effort
|
|
48
|
+
logging that should never stop a required deploy step." See
|
|
49
|
+
[Plan format](doc/plan-format.md#failure-handling-optional-blocking-silent_failures).
|
|
50
|
+
- **Branching without an expression language.** `when`/`when_not`/`else`
|
|
51
|
+
gate a step on a prior step's output or outcome, reusing the same
|
|
52
|
+
closed placeholder syntax — no new DSL to audit. A branch step is
|
|
53
|
+
implicitly optional, so picking one path over another can't itself
|
|
54
|
+
abort the workflow. See
|
|
55
|
+
[Branching](doc/plan-format.md#branching-when-when_not-else).
|
|
56
|
+
- **Clean, layered architecture, built to extend.** Four layers
|
|
57
|
+
(`domain` → `application` → `infrastructure` → `interfaces`), each only
|
|
58
|
+
depending on the ones below it, with ports (`Protocol`s) separating
|
|
59
|
+
contracts from implementations. Add a step type without forking the
|
|
60
|
+
repo via one call to `register_executor()`. See
|
|
61
|
+
[Architecture](doc/architecture.md).
|
|
62
|
+
- **Built for both a blocking CLI and a future async server.**
|
|
63
|
+
`run_to_completion()` blocks until done (right for a CLI); `start_workflow()`
|
|
64
|
+
+ polling `get_status()` doesn't (right for an MCP server or any
|
|
65
|
+
poll-driven client) — same DAG, same engine, no separate code path. See
|
|
66
|
+
[API reference](doc/api-reference.md).
|
|
67
|
+
|
|
68
|
+
Everything runs in memory — nothing survives a process restart. That's a
|
|
69
|
+
deliberate v1 choice, not an oversight: the goal is to get the
|
|
70
|
+
architecture right first, with the `WorkflowRepository`/`EventBus` ports
|
|
71
|
+
already shaped so a persistent implementation can be dropped in later
|
|
72
|
+
without touching orchestration logic. The eventual goal is to run this as
|
|
73
|
+
an MCP server so agent tools (e.g. VS Code GitHub Copilot) can submit and
|
|
74
|
+
drive plans.
|
|
75
|
+
|
|
76
|
+
## Key features
|
|
77
|
+
|
|
78
|
+
| | |
|
|
79
|
+
|---|---|
|
|
80
|
+
| **Real concurrency** | Independent steps run as genuinely parallel `asyncio.Task`s, driven purely by the dependency graph. |
|
|
81
|
+
| **Four built-in executors** | `noop`, `sleep`, `shell` (with timeout), `http` (with retry/backoff) — see [Executors](doc/executors.md). |
|
|
82
|
+
| **Typed, defaulted specs** | Any executor can declare a pydantic `spec_model` and get validation for free, at the earliest honest point. |
|
|
83
|
+
| **Pluggable executors** | `register_executor("my_type", MyExecutor())` — no fork required. |
|
|
84
|
+
| **Rich failure semantics** | Required vs. optional steps, blocking vs. non-blocking dependents (with per-edge overrides), silent vs. visible failures, cascading skips. |
|
|
85
|
+
| **Clean cancellation** | A required failure — or an explicit `cancel_workflow()` — stops every in-flight step promptly and deterministically. |
|
|
86
|
+
| **Output passing** | `${{ steps.<id>.output.<path> }}` placeholders, resolved just before dispatch. |
|
|
87
|
+
| **Two execution modes** | `run_to_completion()` (blocking) and `start_workflow()` + poll (non-blocking), from the exact same engine. |
|
|
88
|
+
| **JSON-ready DTOs** | `WorkflowService` returns primitives-only dataclasses — no domain leakage across the API boundary. |
|
|
89
|
+
|
|
90
|
+
## Quickstart
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
python -m venv .venv
|
|
94
|
+
source .venv/bin/activate # or `.venv\Scripts\activate` on Windows
|
|
95
|
+
pip install -e ".[dev]"
|
|
96
|
+
|
|
97
|
+
flowlit examples/hello_workflow.yaml
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pytest # run the test suite
|
|
102
|
+
ruff check src tests # lint
|
|
103
|
+
mypy src # type-check
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
See [Getting started](doc/getting-started.md) for a full walkthrough,
|
|
107
|
+
including using flowlit as a library and reading its CLI output.
|
|
108
|
+
|
|
109
|
+
## Documentation
|
|
110
|
+
|
|
111
|
+
Detailed, modular guides live under [`doc/`](doc/):
|
|
112
|
+
|
|
113
|
+
| Guide | Covers |
|
|
114
|
+
|---|---|
|
|
115
|
+
| [Getting started](doc/getting-started.md) | Install, quickstart, CLI flags, using flowlit as a library. |
|
|
116
|
+
| [Plan format](doc/plan-format.md) | The YAML plan schema: steps, dependencies, output-passing placeholders, failure-handling fields. |
|
|
117
|
+
| [Architecture](doc/architecture.md) | The four-layer design, execution flow, event bus, composition root — with diagrams. |
|
|
118
|
+
| [Concurrency model](doc/concurrency.md) | How the DAG drives real concurrency, cancellation semantics, `max_concurrent_steps`. |
|
|
119
|
+
| [Executors](doc/executors.md) | The `noop`/`sleep`/`shell`/`http` executors, adding your own, registering one from outside the repo. |
|
|
120
|
+
| [API reference](doc/api-reference.md) | `WorkflowService`, ports, DTOs, events, and the full exception hierarchy. |
|
|
121
|
+
| [Configuration](doc/configuration.md) | CLI flags, `build_app_context()` parameters, per-step configuration. |
|
|
122
|
+
| [Observability](doc/observability.md) | Logging setup and live progress via the event bus. |
|
|
123
|
+
| [Testing](doc/testing.md) | Test suite layout and what each part covers. |
|
|
124
|
+
| [Examples](doc/examples.md) | A guided tour of every plan in [`examples/`](examples/). |
|
|
125
|
+
|
|
126
|
+
## A minimal plan
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
id: build-and-deploy
|
|
130
|
+
name: Build and Deploy
|
|
131
|
+
steps:
|
|
132
|
+
- id: fetch_source
|
|
133
|
+
type: shell
|
|
134
|
+
spec:
|
|
135
|
+
command: "echo '{\"commit_sha\": \"abc123\"}'"
|
|
136
|
+
depends_on: []
|
|
137
|
+
|
|
138
|
+
- id: notify
|
|
139
|
+
type: noop
|
|
140
|
+
spec:
|
|
141
|
+
message: "deployed commit ${{ steps.fetch_source.output.commit_sha }}"
|
|
142
|
+
depends_on: [fetch_source]
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`type` selects the executor; `spec` is whatever that executor needs;
|
|
146
|
+
`depends_on` must form a DAG. See [Plan format](doc/plan-format.md) for
|
|
147
|
+
the complete schema, including `optional`/`blocking`/`silent_failures`
|
|
148
|
+
and `blocking_overrides`.
|
|
149
|
+
|
|
150
|
+
## Project layout
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
src/flowlit/
|
|
154
|
+
├── domain/ Step/Workflow entities, DAG validation -- stdlib only
|
|
155
|
+
├── application/ use cases, ports (Protocols), orchestration
|
|
156
|
+
├── infrastructure/ YAML loading, in-memory event bus/repository, executors
|
|
157
|
+
├── interfaces/ CLI today; a future MCP server; the composition root
|
|
158
|
+
└── plugins.py register_executor() -- the public extension seam
|
|
159
|
+
examples/ runnable YAML plans
|
|
160
|
+
tests/ unit + integration test suite
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
See [Architecture](doc/architecture.md) for the reasoning behind this
|
|
164
|
+
layering.
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT — see [`pyproject.toml`](pyproject.toml).
|