Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dev-dependencies = [
"ruff>=0.6.2",
"polars>=1.5.0",
"vega-datasets>=0.9.0",
"ibis-framework[duckdb]>=6; python_version >= '3.9'",
]

[tool.ruff.lint]
Expand Down
16 changes: 16 additions & 0 deletions src/quak/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import sys
import typing
import uuid

DataFrameObject = typing.Any

if typing.TYPE_CHECKING:
import duckdb
import ibis
import polars as pl
import pyarrow as pa

Expand Down Expand Up @@ -104,3 +106,17 @@ def is_polars(obj: object) -> typing.TypeGuard[pl.DataFrame]:
"""Check if an object is a Polars DataFrame."""
polars = sys.modules.get("polars")
return polars is not None and isinstance(obj, polars.DataFrame)


def is_ibis_duckdb(obj: object) -> typing.TypeGuard[ibis.Table]:
"""Check if an object is an ibis Table backed by DuckDB."""
ibis = sys.modules.get("ibis")
if ibis is None or not isinstance(obj, ibis.Table):
return False
backend = obj.get_backend()
return backend.name == "duckdb"


def gen_unique_name(prefix: str = "quak") -> str:
"""Generate a unique name with a given prefix."""
return f"{prefix}_{uuid.uuid4().hex[:8]}"
13 changes: 13 additions & 0 deletions src/quak/_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
from ._util import (
arrow_table_from_dataframe_protocol,
arrow_table_from_ipc,
gen_unique_name,
get_columns,
has_pycapsule_stream_interface,
is_arrow_ipc,
is_dataframe_api_obj,
is_ibis_duckdb,
is_polars,
table_to_ipc,
)
Expand All @@ -42,6 +44,17 @@ class Widget(anywidget.AnyWidget):
def __init__(self, data, *, table: str = "df"):
if isinstance(data, duckdb.DuckDBPyConnection):
conn = data
elif is_ibis_duckdb(data):
backend = data.get_backend()
conn = backend.con
table = gen_unique_name()
# if the given table is a memtable, need to register it so that the
# following backend.compile(data) references a table that actually exists.
backend._register_in_memory_tables(data)
# create a view, not a table, so that if you have an eg postgres
# database attached to duckdb, you can query it without
# materializing the entire table from postgres into duckdb.
backend.raw_sql(f"CREATE TEMP VIEW {table} AS ({backend.compile(data)})")
else:
conn = duckdb.connect(":memory:")
if is_polars(data):
Expand Down
Loading