Winslow
A state and workflow management framework with a terminal UI.
Winslow lets you describe work as a set of small, dependency-aware tasks and then run them from a live terminal dashboard — or headless in CI. Its guiding idea is simple: every task knows both how to do its work and how to tell whether that work is already done. Winslow checks the latter before doing the former, so re-running a workflow only does what still needs doing.
If you've ever written a pile of scripts held together with "did this step already run?" checks, Winslow is that pattern, made first-class.
Demo
https://github.com/user-attachments/assets/23426158-0447-421b-ab16-caf4c0d9103e
Install
uv add "winslow[tui]" # or: pip install 'winslow[tui]'
Requires Python 3.12+. The tui extra pulls in the terminal UI; for headless
runs only (cron, CI), the bare winslow package is enough.
Quick start
A workflow is a directory containing a Workflow class and the Task classes
that belong to it. The workflow.py filename marks the directory as a
workflow package — every .py file next to it belongs to that workflow.
Drop this in workflows/etl/workflow.py:
import os
from winslow import Workflow, Task
class Etl(Workflow):
pass # name defaults to "etl" (kebab-cased class name)
class DownloadData(Task):
is_premier = True # foundational step, runs first
def run(self):
# ... fetch the file ...
download("s3://bucket/raw.csv", "/data/raw.csv")
def check(self):
return os.path.exists("/data/raw.csv")
class TransformData(Task):
dependencies = DownloadData
def run(self):
transform("/data/raw.csv", "/data/clean.csv")
def check(self):
return os.path.exists("/data/clean.csv")
Then launch the terminal UI from that directory:
winslow run
Or run it headless (handy for cron and CI):
winslow run --mode headless --workflow etl
The core idea: run + check
These two methods are the whole contract:
run(self)makes a change to the world.check(self)reports whether the desired end state already holds.
Before running a task, Winslow calls check(). If it's already true, the
task is skipped; otherwise run() executes and Winslow calls check()
again to confirm success. That makes workflows idempotent and resumable by
construction — interrupt one halfway and re-run it, and it picks up where it
left off. (Need to force a redo? winslow run --force-run skips the pre-check.)
A task that only verifies state can omit run() entirely — it becomes a
read-only check.
A few things you'll probably reach for
Each of these is optional — start with run/check and add the rest as
you need it.
-
Dependencies & ordering. Declare
dependencies = OtherTask(or a tuple / task-group name). Winslow builds the dependency graph, detects cycles, and runs things in the right order. Mark foundational stepsis_premierand cleanup stepsis_terminal. -
Eligibility & guards. Override
is_eligible()to skip a task in some environments, orcan_run()to block it until a precondition holds. For reusable rules, attach composable constraints:class DeployTask(Task): runnability_constraints = [BusinessHoursOnly]
-
Parameterized tasks. One task class can fan out into many instances via
Parameterdeclarations — e.g. oneProcessRegiontask per region — each tracked and run independently. -
A task filter language — that you can extend. Narrow the view (or a run) with expressions like
build,test,!g deploy, or~lint & !group nightly, in the UI's search box or via--filteron the CLI. Add your own commands by subclassingTaskFilter: define a!commandand what it matches, and it's available everywhere filters are. -
Live terminal UI. Watch tasks change state in real time, stream per-task logs, browse execution history, and run or re-check individual tasks by hand — all from the dashboard. Everything also works headless in
--mode headless. -
A pluggable UI. The dashboard and workflow views are assembled from plugins that fill named slots. Add your own tab or panel — or replace a built-in one — by subclassing
UIPlugin, no fork required. Both UI plugins and custom filters can be autodiscovered or shipped as separate packages via entry points (winslow.tui_plugins,winslow.filter_plugins).
CLI at a glance
winslow run # interactive terminal UI
winslow run --workflow etl # UI, pre-selecting a workflow
winslow run --mode headless --workflow etl # headless run
winslow run --mode headless --check ... # check completion without running
winslow run --dry-run ... # call dry_run() instead of run()
winslow show # list workflows
winslow show --initialize --workflow etl # initialize one workflow, list its tasks
winslow show --initialize --workflow etl --with-deps # ...with each task's dependencies
Winslow discovers your workflows from the current directory; defining an
Orchestrator subclass to customize global options is optional.
Trust model
Treat a workflow directory like a Makefile or a conftest.py: running
winslow in a directory runs that directory's code. On startup Winslow
imports each workflow's workflow.py (and every .py beside it), plus the
top-level modules used for orchestrator discovery — at import time, before any
prompt. Only run winslow in directories you trust.
Plugin and filter autodiscovery is opt-out, not opt-in: any installed
package exposing winslow.tui_plugins / winslow.filter_plugins entry points is imported
and its autoload = True classes registered on startup. Constrain this from
your pyproject.toml:
[tool.winslow]
disabled_tui_plugins = ["some-plugin"] # or enabled_tui_plugins to allowlist
disabled_filter_plugins = ["some-filter"] # likewise enabled_filter_plugins
Status
Winslow is early (0.x) — the API may still shift between minor versions. Full documentation lives at winslow-workflow.org.
License
Metadata
Release files for winslow 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 | |
|---|---|---|---|---|
| winslow-0.2.0.tar.gz | 105.6 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| winslow-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 250.6 kB
Release files / winslow-0.2.0.tar.gz
| Download URL | winslow-0.2.0.tar.gz |
|---|---|
| Size | 105.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0bc9919f85c9ec69f942e898907a2b68a4edbc91e0a33a523f422cb3b476d9ef
|
|
BLAKE2b-256 checksum How to use checksums |
4c50d7f6b7fd70029e7c665d0ee74da6abdea2bf19daa2747e0ccbfdc825c661
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / winslow-0.2.0-py3-none-any.whl
| Download URL | winslow-0.2.0-py3-none-any.whl |
|---|---|
| Size | 145.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
092d8b29c44c9c2d09c540fce7dacd764920e285b1373bf92f07548b4008e667
|
|
BLAKE2b-256 checksum How to use checksums |
f320b96cb8028a2a20f4eecd15a486a69935e9e940072dc1d552e5000938a991
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for winslow 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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A 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.