DuckDB Python Client

repository·v2.0-cyanoptera·Indexed 41 days ago

https://github.com/duckdb/duckdb-python

A high-performance, in-process analytical database system for Python. It provides a columnar engine for running complex SQL queries on data formats like Parquet, CSV, and JSON, with native integrations for Pandas and Apache Arrow. Features include a DB API 2.0 compliant interface, support for larger-than-memory workloads, and an extensible system for third-party functions and data types.

Tokens
7.4K
Snippets
26
Records
35
Agent score
51%

What's inside DuckDB

  1. Overview of DuckDB Python

    v2.0-cyanoptera

    DuckDB is an in-process, portable, and open-source analytical database system designed for high-performance SQL queries.

    Key characteristics include:

    • In-process: Runs within your host application with zero external dependencies.
    • Columnar Engine: Optimized for analytical queries, supporting parallel execution and larger-than-memory workloads.
    • File Support: Native ability to read and write formats like CSV, Parquet, and JSON from local filesystems or remote endpoints like S3.
    • Extensible: Supports third-party extensions for new data types, functions, and file formats.
    • Portable: Compatible with Linux, macOS, Windows, Android, and iOS.
  2. Benchmark marker types: gate vs informational

    v2.0-cyanoptera

    Benchmarks in the suite are categorized by markers to determine how regressions are handled:

    • gate: These benchmarks are binding-dominated, hold the GIL, and are deterministic under Callgrind. A threshold breach in a gate benchmark is considered a binding regression.
    • informational: These benchmarks are diluted by engine, library, or streaming performance. They are reported for visibility but are never used to gate builds, as engine updates would cause frequent false positives.
  3. Run local A/B walltime benchmarks

    v2.0-cyanoptera

    To perform local A/B testing using walltime (useful when instruction-count gating is unavailable, such as on macOS arm64), run the pytest module with the --codspeed and --codspeed-mode=walltime flags.

    Important: To ensure the delta measured is purely the binding performance, you must pin the data libraries identically across both builds being compared. Use the following loop to run benchmarks against two different Python environments (e.g., a release environment and a local development environment):

    for P in ../main/.venv-release/bin/python .venv-release/bin/python; do
      $P -m pytest benchmarks/<module>.py --codspeed --codspeed-mode=walltime -o addopts= -p no:cacheprovider
    done
  4. Use DuckDB with Pyodide

    v2.0-cyanoptera

    DuckDB provides binary WASM wheels to allow the DuckDB Python bindings to run within Pyodide, a WASM-based Python interpreter in the browser. To use it, you must load Pyodide, use micropip to install the specific DuckDB WASM wheel, and then you can import duckdb within your Python environment.

    <html
      <head>
        <meta charset="utf-8" />
      </head>
      <body
        <script type="text/javascript" src="https://cdn.jsdelivr.net/pyodide/v0.25.1/full/pyodide.js"></script>
        <script type="text/javascript">
          async function main() {
            let pyodide = await loadPyodide();
            await pyodide.loadPackage("micropip");
            const micropip = pyodide.pyimport("micropip");
            // Install the DuckDB WASM wheel via micropip
            await micropip.install(["https://pyodide.duckdb.org/duckdb-0.10.2.dev479-cp311-cp311-emscripten_3_1_46_wasm32.whl"]);
            await pyodide.runPython(`
    import duckdb
    
    data = """\\\n
    a,b,c
    1,2,3
    4,5,6
    7,8,9"""
    
    with open('data.csv', mode="w") as f:
        f.write(data)
    
    print(duckdb.sql("SELECT COUNT(*) FROM 'data.csv'"))
            `);
          }
          main();
        </script>
      </body>
    </html>
  5. Handle Polars dynamic predicates during conversion

    v2.0-cyanoptera

    Polars uses "dynamic predicates" as internal optimizer hints (e.g., when a slice or TOP-N sits above a scan). These nodes serialize as a Display node with a fmt_str starting with dynamic_pred: <uuid>.

    Feeding these nodes back into DataFrame.filter() causes Polars to panic. When integrating with DuckDB, these dynamic predicates must be stripped from the expression tree. The _strip_dynamic_predicates function removes these conjuncts from a serialized tree. If a dynamic predicate is found anywhere other than a top-level And conjunct, a NotImplementedError is raised because the hint cannot be safely dropped or applied.

  6. Handle DuckDB Python exceptions

    v2.0-cyanoptera

    DuckDB's Python interface follows the Python DBAPI 2.0 specification for exceptions. Errors are organized in a hierarchy starting from Error (mapped from PyError) and Warning. You can catch specific error types to handle different failure scenarios, such as syntax errors, connection issues, or data integrity violations.

    Common exception categories include:

    • DatabaseError: The base class for most database-related errors.
    • DataError: Errors related to data processing (e.g., ConversionException, TypeMismatchException).
    • OperationalError: Errors related to the database operation itself (e.g., ConnectionException, IOException, OutOfMemoryException).
    • IntegrityError: Errors related to data integrity (e.g., ConstraintException).
    • InternalError: Errors occurring within the DuckDB engine (e.g., InternalException).
    • ProgrammingError: Errors in the user's code or SQL (e.g., SyntaxException, ParserException, CatalogException).
    • NotSupportedError: Errors when a feature is not implemented (e.g., NotImplementedException).
  7. Integrate fsspec filesystems with DuckDB

    v2.0-cyanoptera
    DuckDB can use Python's fsspec library to access files via various protocols (e.g., S3, GCS, HTTP) as if they were local files. This is achieved by passing an instance of an fsspec.AbstractFileSystem to DuckDB. When using fsspec, DuckDB utilizes the PythonFilesystem implementation to bridge Python-side file handles with DuckDB's internal filesystem requirements.
  8. Check column types using DB API 2.0 type objects

    v2.0-cyanoptera

    DuckDB provides DB API 2.0 compliant type objects to allow applications to categorize and check column types returned by queries. Instead of using isinstance(), you should use the equality operator (==) to compare a DuckDB type (from cursor.description) against these standard type objects.

    Commonly used type objects include:

    • duckdb.STRING: For text-based columns (e.g., VARCHAR).
    • duckdb.NUMBER: For numeric columns (integers, decimals, floats).
    • duckdb.DATETIME: For temporal data (dates, times, timestamps).
    • duckdb.BINARY: For binary data (e.g., BLOB).
    import duckdb
    
    conn = duckdb.connect()
    cursor = conn.cursor()
    cursor.execute("SELECT 'hello' as text_col, 42 as num_col, CURRENT_DATE as date_col")
    
    # Check column types using DB API type objects
    for i, desc in enumerate(cursor.description):
        col_name, col_type = desc[0], desc[1]
        if col_type == duckdb.STRING:
            print(f"{col_name} is a string type")
        elif col_type == duckdb.NUMBER:
            print(f"{col_name} is a numeric type")
        elif col_type == duckdb.DATETIME:
            print(f"{col_name} is a date/time type")
  9. Use fsspec.AbstractFileSystem for remote data access

    v2.0-cyanoptera

    To allow DuckDB to read from or write to remote filesystems using Python-based protocols, you can provide an object that inherits from fsspec.AbstractFileSystem. DuckDB will wrap this in a PythonFilesystem to perform operations like Read, Write, Seek, and Glob.

    import duckdb
    import s3fs  # Example: using s3fs which implements fsspec
    
    # Create an fsspec filesystem instance
    fs = s3fs.S3FileSystem(key='...', secret='...')
    
    # DuckDB can then use this filesystem to access remote files
    # Note: The specific mechanism for registering the filesystem 
    # depends on the high-level DuckDB Python API usage.
    con = duckdb.connect()
    con.execute("SELECT * FROM 's3://my-bucket/data.parquet'")
  10. Caveats and limitations of DuckDB in Pyodide

    v2.0-cyanoptera

    When using DuckDB with Pyodide, be aware of the following constraints:

    • Python Version Support: Currently, only Python 3.10 and 3.11 are supported (3.12 support is planned).
    • Wheel Compatibility: DuckDB wheels are tied to specific Pyodide versions. For example, Pyodide 0.25.1 requires a cp311-based wheel.
    • Extension Limitations: Extension downloading is not supported in the Pyodide runtime.
      • The default extensions and the httpfs extension that ship with the DuckDB Python package work without needing to be INSTALLed.
      • Other extensions, such as spatial, will not work because they cannot be downloaded.
  11. Benchmark implementation conventions and pitfalls

    v2.0-cyanoptera

    When implementing or analyzing benchmarks, follow these conventions to ensure accuracy:

    Conventions

    • READ operations: Aggregate real columns (e.g., using sum or length) instead of count(*), as count(*) can be answered from metadata without scanning data.
    • WRITE operations: Fully materialize the result or drain the lazy reader to ensure work is actually performed.
    • Warm-up: Always warm the system once before starting measurements.
    • Threading: The con fixture pins threads=1.

    Critical Pitfalls (Avoid these to prevent measuring the wrong thing)

    • Null Column Benches: For benchmarks testing OUT-col nulls, use real nulls (e.g., CASE WHEN ... THEN NULL). Using other methods may trigger a cheap std::move path instead of the intended logic.
    • Numpy String Benches: For IN-numpy string benchmarks, use a mix of ASCII, non-ASCII, and a null sentinel. Failing to do so may cause the benchmark to skip the transcode/null ladder.