Pythonic, typed wrappers for DuckDB relations and tables (Duck+).
Project description
Duck+ (duckplus)
Duck+ is a user-friendly companion to DuckDB for Python projects that want typed helpers, predictable joins, and safe table operations. It wraps DuckDB relations so you can compose analytics pipelines with readable Python while still generating explicit SQL under the hood.
What you get
- Typed relational wrappers –
DuckRelkeeps transformations immutable and chainable. - Safe table workflows –
DuckTableowns inserts, appends, and idempotent ingestion strategies. - Explicit joins and casing rules – column names stay intact, projections are deliberate, and collisions fail loudly unless you opt in to suffixes.
- Optional helpers – secrets management, a read-only CLI, and HTML previews stay in extras so the core package remains lightweight.
Install in seconds
Duck+ targets Python 3.12+ and DuckDB 1.3.0 or newer.
uv pip install duckplus
For development, clone the repository and run uv sync to create the managed
environment with test and typing dependencies. Build the documentation locally
with uv run sphinx-build -b html docs/source docs/_build/html, then open
docs/_build/html/index.html in your browser to preview the site.
Quickstart
from duckplus import DuckRel, connect
with connect() as conn:
rel = DuckRel(
conn.raw.sql(
"""
SELECT *
FROM (VALUES
(1, 'Alpha', 10),
(2, 'Beta', 5),
(3, 'Gamma', 8)
) AS t(id, name, score)
"""
)
)
top_scores = (
rel
.filter('"score" >= ?', 8)
.project_columns("id", "name", "score")
.order_by(score="desc")
)
print(top_scores.materialize().require_table().to_pylist())
This snippet opens an in-memory DuckDB connection, builds a relation, filters rows with positional parameters, and materializes results safely.
Core workflows
Connect and manage context
from duckplus import connect
with connect(path="analytics.duckdb") as conn:
rel = conn.relation("SELECT 42 AS answer")
print(rel.to_df())
Connections default to in-memory databases. Pass path for file-backed
workloads; Duck+ keeps them read-only by default.
Transform relations with DuckRel
deduped = (
rel
.distinct()
.project({"score": "AVG(score)"}, group_by=["name"])
.order_by(score="desc")
)
DuckRel methods always return new relations and validate column names with case-aware lookups.
Promote to tables with DuckTable
materialized = deduped.materialize().require_table()
table = materialized.to_table("scores")
table.insert_antijoin(deduped, keys=["name"])
Table wrappers provide append/insert helpers that guard against duplicates and respect column names.
Join with confidence
from duckplus import JoinProjection, JoinSpec
spec = JoinSpec(equal_keys=[("order_id", "id")])
projection = JoinProjection(allow_collisions=False)
joined = orders.natural_join(customers, project=projection)
suffixes = JoinProjection(allow_collisions=True)
safe = orders.left_outer(customers, spec, project=suffixes)
Join helpers project columns explicitly, drop duplicate right-side keys, and
raise when collisions would occur. Opt into suffixes through
JoinProjection(allow_collisions=True) when needed.
Extras worth knowing
Command line interface
uv run duckplus sql "SELECT 42 AS answer"
uv run duckplus schema "SELECT 1 AS id, 'alpha' AS label"
uv run duckplus --repl
The CLI provides read-only helpers for quick exploration. Point it at a DuckDB
file with --database path/to/file.duckdb when needed.
HTML previews
from duckplus import DuckRel, connect, to_html
with connect() as conn:
rel = DuckRel(conn.raw.sql("SELECT 1 AS id, 'Alice & Bob' AS name"))
html = to_html(rel, max_rows=10, null_display="∅", class_="preview")
to_html renders safe, escaped previews with optional styling hooks.
Documentation workflow
The documentation site is published automatically to GitHub Pages by the
Docs
workflow. Every push to main and each pull request runs uv sync, builds the
Sphinx project, and deploys the generated HTML to the gh-pages branch. The
latest deployment is always available at
https://isaacnfairplay.github.io/duck/,
and workflow summaries include preview links you can share for review.
If a deployment fails:
- Open the Actions → Docs run for the failing commit or pull request.
- Review the build logs, especially the
uv run sphinx-buildstep for Sphinx warnings promoted to errors. - Re-run the job from the Actions UI after fixing the problem to publish an updated preview.
For a local preview outside CI, run:
uv sync
uv run sphinx-build -b html docs/source docs/_build/html
python -m webbrowser docs/_build/html/index.html # optional helper to open the preview
Learn more
- Review the API reference for detailed method docs and typing information.
- Explore unit tests under
tests/to see edge cases and best practices.
If you run into questions or want to suggest improvements, open an issue or pull request. We welcome contributions that keep Duck+ reliable for the long haul.
License
Duck+ is available under the MIT License.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file duckplus-0.0.3.tar.gz.
File metadata
- Download URL: duckplus-0.0.3.tar.gz
- Upload date:
- Size: 60.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c98c7850d4c96f704f6f7560fe44e84594e317f39909073a6308245c60ccd07
|
|
| MD5 |
9214e9f4012e51af0037ec3d6d95eab7
|
|
| BLAKE2b-256 |
9d72258525437f6482990955d78b3bae3f4ab4d8d14ab6ba85cc0a18a2bbcdde
|
Provenance
The following attestation bundles were made for duckplus-0.0.3.tar.gz:
Publisher:
python-publish.yml on isaacnfairplay/duck
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
duckplus-0.0.3.tar.gz -
Subject digest:
8c98c7850d4c96f704f6f7560fe44e84594e317f39909073a6308245c60ccd07 - Sigstore transparency entry: 602256923
- Sigstore integration time:
-
Permalink:
isaacnfairplay/duck@349baff65a9a160197a60db4534a0e1b114d8ce5 -
Branch / Tag:
refs/tags/v0.0.3 - Owner: https://github.com/isaacnfairplay
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@349baff65a9a160197a60db4534a0e1b114d8ce5 -
Trigger Event:
release
-
Statement type:
File details
Details for the file duckplus-0.0.3-py3-none-any.whl.
File metadata
- Download URL: duckplus-0.0.3-py3-none-any.whl
- Upload date:
- Size: 47.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a58a20afd8848ece3631b6335e5f2efa2c6240e51eb6261a4a9119a7c86e4355
|
|
| MD5 |
1ed786433012e8c20264594366bb2b2b
|
|
| BLAKE2b-256 |
8e9b683d5b07c3001e5d28ec540df9f32b9528261704dd6a9f3fb3dac29b4375
|
Provenance
The following attestation bundles were made for duckplus-0.0.3-py3-none-any.whl:
Publisher:
python-publish.yml on isaacnfairplay/duck
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
duckplus-0.0.3-py3-none-any.whl -
Subject digest:
a58a20afd8848ece3631b6335e5f2efa2c6240e51eb6261a4a9119a7c86e4355 - Sigstore transparency entry: 602256924
- Sigstore integration time:
-
Permalink:
isaacnfairplay/duck@349baff65a9a160197a60db4534a0e1b114d8ce5 -
Branch / Tag:
refs/tags/v0.0.3 - Owner: https://github.com/isaacnfairplay
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@349baff65a9a160197a60db4534a0e1b114d8ce5 -
Trigger Event:
release
-
Statement type: