Skip to main content

Local-first ETL/ELT pipelines that run on DuckDB. CLI, Python API, and MCP server for AI agents.

Project description

Duckle

Local-first ETL/ELT pipelines that run on DuckDB. Python builds the plan, DuckDB moves the rows. Your data never leaves the machine.

PyPI License GitHub

pip install duckle
duckle quickstart
Terminal: pip install duckle then duckle quickstart, which scaffolds sample data and a pipeline, runs it, and prints the resulting rows

quickstart scaffolds sample data and a pipeline, runs it, and shows you the rows. One command from nothing to a result, because the engine ships in the install: a ~20 MB native binary plus the DuckDB CLI. No JVM, no Docker, no server, no account.

No install at all, if you have uv:

uvx duckle@latest quickstart

No data passes through Python

Every method appends a node to a pipeline graph. Nothing executes until .run(), and then the whole graph is handed to the engine, compiled to SQL, and executed inside DuckDB.

That is the difference from a dataframe library: a billion-row job costs what the SQL costs, because it is SQL. There is no interpreter in the data path, and no to_pandas() escape hatch quietly pulling rows into memory.

import duckle
from duckle import col

(duckle.read_csv("orders.csv")
    .where(col.amount >= 20)
    .derive(total="round(amount * 1.2, 2)", tag="f'{region}-{id}'")
    .write_parquet("out.parquet")
    .run())

Python expressions, compiled to SQL

amount * 1.2, f'{a}-{b}', x if c else y, name.strip().upper() and email is None are translated to vectorized DuckDB SQL at plan time:

.derive(band="'high' if amount > 50 else 'low'")
# CASE WHEN ("amount" > 50) THEN 'high' ELSE 'low' END

An expression with no exact SQL equivalent is rejected with the construct named, never quietly executed somewhere slower. Comprehensions, lambda, indexing and eval are refused by name.

Prefer editor completion over strings? col builds the same tree with real operators, and fragments are reusable:

eu_only = col.region.isin(["EU", "UK"])
p.where(eu_only & (col.amount >= 20))

See exactly what will run, before it runs:

p.explain()   # prints the compiled SQL, one block per stage

359 components, not 10 file formats

Component ids map onto attribute paths, so anything in the catalog is reachable:

duckle.src.salesforce(object="Account", authMode="clientCredentials")
duckle.xf.geo.reproject(geomColumn="geom", targetCrs="EPSG:3857")
duckle.snk.salesforce.bulk(operation="upsert", externalIdField="Ext__c")

104 sources, 66 sinks and 138 transforms: Postgres, MySQL, Oracle, SQL Server, Snowflake, Databricks, Teradata, SAP OData, Salesforce (including Bulk API 2.0), Kafka, WebSocket, S3, SFTP, IMAP, LanceDB and more.

duckle.component_ids(contains="salesforce")
duckle.describe("snk.salesforce.bulk")   # settings, and which ones the engine ignores

A CI gate that needs no engine

Terminal: duckle validate reports one failing and one passing pipeline, then exits 1

duckle validate compiles every pipeline without opening a source or writing a sink, so it needs no DuckDB, no credentials and no network. Exit codes are stable and safe to gate on:

code meaning
0 clean
1 a real finding: a pipeline failed, or did not compile
2 the runner could not start: bad usage, missing engine
duckle validate --json          # machine-readable, for a build step
duckle --pipeline my.json       # run one

Note: validate does not yet catch every missing required property, so a clean validate is not proof that a run will succeed.

Agent-ready, with nothing installed

Terminal: adding duckle as an MCP server via uvx, then an agent discovering Salesforce connectors, reading a component schema, creating a pipeline that compiles, and running it

The same package is an MCP server, so an AI agent gets a governed way to work with data instead of a shell. Point Claude Code, Claude Desktop or Cursor at it:

{ "mcpServers": { "duckle": { "command": "uvx", "args": ["duckle", "mcp"] } } }

Or in Claude Code:

claude mcp add duckle -- uvx duckle mcp

That is the whole setup. No pip install, no PATH, no engine to configure: uv fetches the package and the DuckDB engine into a throwaway environment and the server finds it there. If you did pip install duckle, "command": "duckle", "args": ["mcp"] works the same way.

19 tools, including list_components, get_component_schema, create_pipeline, validate_pipeline, run_pipeline, pipeline_lineage, trust_report and schema_drift.

What that buys over letting an agent write a script: it can discover a real connector rather than guess one, compile-check a pipeline before anything executes, run it, and get column-level lineage back. validate_pipeline opens no source and writes no sink, so an agent can check its work without touching your data. The pipeline it produces is the same JSON your desktop canvas opens, so you can inspect what it built.

Code and canvas are the same file

Pipelines are the same JSON the Duckle desktop studio reads. A pipeline written in Python opens on the canvas, and one drawn on the canvas runs from Python:

p.save("pipelines/orders.json")          # opens in the studio
duckle.from_json("pipelines/orders.json").run()

Install notes

duckle depends on duckdb-cli, published by the DuckDB Foundation, so the engine arrives with the install and works offline. To pin your own build instead, set DUCKLE_DUCKDB_BIN.

Wheels ship for Linux, macOS and Windows on x86-64 and arm64. The wheel carries a compiled Rust binary and is Python-version independent (py3-none-<platform>).


Apache-2.0  ·  GitHub  ·  duckle.org  ·  Issues

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

duckle-0.5.8-py3-none-win_arm64.whl (21.8 MB view details)

Uploaded Python 3Windows ARM64

duckle-0.5.8-py3-none-win_amd64.whl (23.0 MB view details)

Uploaded Python 3Windows x86-64

duckle-0.5.8-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (23.2 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

duckle-0.5.8-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl (22.3 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

duckle-0.5.8-py3-none-macosx_11_0_x86_64.whl (21.3 MB view details)

Uploaded Python 3macOS 11.0+ x86-64

duckle-0.5.8-py3-none-macosx_11_0_arm64.whl (20.5 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

File details

Details for the file duckle-0.5.8-py3-none-win_arm64.whl.

File metadata

  • Download URL: duckle-0.5.8-py3-none-win_arm64.whl
  • Upload date:
  • Size: 21.8 MB
  • Tags: Python 3, Windows ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for duckle-0.5.8-py3-none-win_arm64.whl
Algorithm Hash digest
SHA256 4e8d6b1b46cef6dc5d84dbc4b77daa7e87bdf9dd39e4bd197f9469757195625d
MD5 cbbed48af05c08ac7d64bfc528e2d656
BLAKE2b-256 8477b6d34d4b339838bb424776b7703554256316c7b9a89cef81c90961582694

See more details on using hashes here.

Provenance

The following attestation bundles were made for duckle-0.5.8-py3-none-win_arm64.whl:

Publisher: pypi.yml on slothflowlabs/duckle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file duckle-0.5.8-py3-none-win_amd64.whl.

File metadata

  • Download URL: duckle-0.5.8-py3-none-win_amd64.whl
  • Upload date:
  • Size: 23.0 MB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for duckle-0.5.8-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 dfdcc13f93a4fb1e3054cd9febe7521fa5e3db974bf99bee832719ac49a3f78b
MD5 48167e51b9934a8ce7f2fe5995d5e78d
BLAKE2b-256 6b006f80617328723f2d0b9e72efca02bf7acc481c00870957a111658bbb0423

See more details on using hashes here.

Provenance

The following attestation bundles were made for duckle-0.5.8-py3-none-win_amd64.whl:

Publisher: pypi.yml on slothflowlabs/duckle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file duckle-0.5.8-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.

File metadata

File hashes

Hashes for duckle-0.5.8-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 0a919d764f867a78eb51db225b2e49a5d4b3ab5b59705cf36e7976b6bd0eb4f4
MD5 85be6407d37d12c96d4119a61fbe55ff
BLAKE2b-256 15558cb113a272f20b1a5d6ba7bc69ad1d34bf89a2b916a58473dd00cce5d3ef

See more details on using hashes here.

Provenance

The following attestation bundles were made for duckle-0.5.8-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl:

Publisher: pypi.yml on slothflowlabs/duckle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file duckle-0.5.8-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl.

File metadata

File hashes

Hashes for duckle-0.5.8-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
Algorithm Hash digest
SHA256 3d7f6a3686f294c3fe3c0dc34e051f104aebe01544b257d60f172a6567f8624f
MD5 b8279444b26d10eb6232c94f180fcef5
BLAKE2b-256 ab2456de6a8434ff14c51e9a6c2c68ee47fae5f557276f1fe309dfddd94f3e43

See more details on using hashes here.

Provenance

The following attestation bundles were made for duckle-0.5.8-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl:

Publisher: pypi.yml on slothflowlabs/duckle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file duckle-0.5.8-py3-none-macosx_11_0_x86_64.whl.

File metadata

File hashes

Hashes for duckle-0.5.8-py3-none-macosx_11_0_x86_64.whl
Algorithm Hash digest
SHA256 4d289c45e2eec12664c5c31a162a0a4ffc821b872a96d57e58013f63ce10e39e
MD5 5f917a9391d154f70454c2200aa9931d
BLAKE2b-256 08cf56b7ba2eb21edba0d5292d1c5ef667ddc6418c9da987ba06c0ef29872669

See more details on using hashes here.

Provenance

The following attestation bundles were made for duckle-0.5.8-py3-none-macosx_11_0_x86_64.whl:

Publisher: pypi.yml on slothflowlabs/duckle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file duckle-0.5.8-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for duckle-0.5.8-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 f440afbc26c3c178d8133b2ff81767fa437b17f1f93f714dc5d4a9c22bd6afb0
MD5 b715192ab0a65d8d7efbe48303ed3b28
BLAKE2b-256 966b16b45226b7082b22df80df9c0b264fe9b4401621b41759fea8e66937f9bb

See more details on using hashes here.

Provenance

The following attestation bundles were made for duckle-0.5.8-py3-none-macosx_11_0_arm64.whl:

Publisher: pypi.yml on slothflowlabs/duckle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page