Skip to main content

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 wrappersDuckRel keeps transformations immutable and chainable.
  • Safe table workflowsDuckTable owns 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:

  1. Open the Actions → Docs run for the failing commit or pull request.
  2. Review the build logs, especially the uv run sphinx-build step for Sphinx warnings promoted to errors.
  3. 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


Download files

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

Source Distribution

duckplus-0.0.3.tar.gz (60.5 kB view details)

Uploaded Source

Built Distribution

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

duckplus-0.0.3-py3-none-any.whl (47.5 kB view details)

Uploaded Python 3

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

Hashes for duckplus-0.0.3.tar.gz
Algorithm Hash digest
SHA256 8c98c7850d4c96f704f6f7560fe44e84594e317f39909073a6308245c60ccd07
MD5 9214e9f4012e51af0037ec3d6d95eab7
BLAKE2b-256 9d72258525437f6482990955d78b3bae3f4ab4d8d14ab6ba85cc0a18a2bbcdde

See more details on using hashes here.

Provenance

The following attestation bundles were made for duckplus-0.0.3.tar.gz:

Publisher: python-publish.yml on isaacnfairplay/duck

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

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

Hashes for duckplus-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 a58a20afd8848ece3631b6335e5f2efa2c6240e51eb6261a4a9119a7c86e4355
MD5 1ed786433012e8c20264594366bb2b2b
BLAKE2b-256 8e9b683d5b07c3001e5d28ec540df9f32b9528261704dd6a9f3fb3dac29b4375

See more details on using hashes here.

Provenance

The following attestation bundles were made for duckplus-0.0.3-py3-none-any.whl:

Publisher: python-publish.yml on isaacnfairplay/duck

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