Skip to main content

Funmill

Funmill provides one stable task API while execution is delegated to a replaceable backend. The first backend is self-hosted Windmill, pinned to v1.808.0.

client -> Funmill /v1 -> TaskBackend -> Windmill
                                  -> another backend later

Funmill owns the public request and response models. Backend job IDs remain opaque strings, and no Windmill routes or payloads are exposed to clients.

Start

Install the project and the Windmill binary:

uv sync
uv run funmill install windmill

Start Windmill against an existing PostgreSQL database:

DATABASE_URL='postgres://windmill:password@127.0.0.1:5432/windmill' \
uv run funmill start windmill

Open http://localhost:8001, log in with admin@windmill.dev / changeme, change the password, and create an API token in the admins workspace. Then start Funmill in another terminal:

FUNMILL_API_KEY='replace-me' \
WINDMILL_URL='http://127.0.0.1:8001' \
WINDMILL_WORKSPACE=admins \
WINDMILL_TOKEN='replace-me' \
uv run funmill start

The API is at http://localhost:8000; OpenAPI docs are at http://localhost:8000/docs. All /v1 routes require X-API-Key. See the Windmill deployment guide for PostgreSQL setup and additional workers.

Run the end-to-end task and DAG checks with:

FUNMILL_API_KEY=the-value-from-env ./scripts/smoke.sh

Set FUNMILL_CALLBACK_URL to test callbacks. The URL must be reachable from the Windmill workers.

API

Operation Route
Submit Python/Bash POST /v1/tasks
Submit a DAG POST /v1/workflows
Status GET /v1/tasks/{task_id}
Logs GET /v1/tasks/{task_id}/logs
Progress GET /v1/tasks/{task_id}/progress
Result GET /v1/tasks/{task_id}/result
Cancel POST /v1/tasks/{task_id}/cancel
Rerun POST /v1/tasks/{task_id}/rerun

Submit one task, optionally waiting for existing task IDs:

{
  "language": "python",
  "source": "def main(value: int):\n    return value * 2\n",
  "args": {"value": 21},
  "depends_on": ["EXISTING_TASK_ID"],
  "dependency_timeout_seconds": 3600,
  "retry": {"attempts": 2, "delay_seconds": 5},
  "timeout_seconds": 300,
  "callback_url": "https://example.internal/task-callback"
}

Submit A -> [B, C] as one workflow:

{
  "tasks": [
    {"key": "a", "language": "python", "source": "def main(): return 1"},
    {"key": "b", "language": "python", "source": "def main(): return 2", "depends_on": ["a"]},
    {"key": "c", "language": "bash", "source": "main() { echo 3; }", "depends_on": ["a"]}
  ]
}

Dependencies inside a workflow are task keys. Top-level depends_on values are IDs returned by earlier Funmill submissions. The Windmill backend checks those dependencies in short jobs separated by Flow sleeps, so waiting does not hold a worker process. Failure or cancellation of a dependency fails the waiting task. Workflow results and callback payloads are objects keyed by every workflow task key. Each topological layer is a synchronization barrier; tasks in the same layer run in parallel.

Callbacks contain task_id, status, and payload; success and failure delivery retry three times. Delivery is at least once, so callback receivers must be idempotent.

Backends

The public contract is TaskBackend in src/funmill/backends/base.py. Backend selection uses FUNMILL_BACKEND; registration lives in src/funmill/backends/__init__.py, following the same driver pattern as fundrive. Each third-party adapter lives in its own directory, such as src/funmill/backends/windmill/.

Changing the backend does not change /v1, but it does not migrate old jobs or their IDs. Add a Funmill-owned ID mapping database only when jobs must remain queryable after a live backend migration.

Operations

funmill services
funmill install windmill
funmill start windmill
funmill start

Both start commands run in the foreground and stop normally with Ctrl+C. Use systemd or an existing process manager for long-running deployment. Add TLS, PostgreSQL backups, callback egress restrictions, and a secrets manager before network exposure.

Metadata

Release files for funmill 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for funmill 0.1.1
File Interpreter ABI Platform
funmill-0.1.1-py3-none-any.whl Python 3 none any Details

Release files / funmill-0.1.1-py3-none-any.whl

Download URL funmill-0.1.1-py3-none-any.whl
Size 14.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
329d13053a6f756c8f9976783e7b5aaf9ab83b5f22e7969cc3cddf7e63a462a2
BLAKE2b-256 checksum
How to use checksums
ab932664a9fea5c42ff173598bf09eea65f76523bd2a419a85f0b572dda0d4a5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","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":null}

Security for funmill 0.1.1

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.

Release history Release notifications | RSS feed

This release

0.1.1 This release

1 release file No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page