Source code for openbasket.datasets
"""Kedro datasets for tables too large for pandas.
N-PORT holds millions of rows per quarter. Nodes that work on it pass
:class:`duckdb.DuckDBPyRelation` objects, and this dataset persists them as
Parquet, so the data stays inside DuckDB and Kedro still tracks lineage.
"""
from __future__ import annotations
from pathlib import Path
from typing import Any
import duckdb
from kedro.io import AbstractDataset
_CONNECTION: duckdb.DuckDBPyConnection | None = None
[docs]
def connection() -> duckdb.DuckDBPyConnection:
"""Return the DuckDB connection shared by all nodes in a run."""
global _CONNECTION
if _CONNECTION is None:
_CONNECTION = duckdb.connect()
return _CONNECTION
[docs]
class DuckDBParquetDataset(AbstractDataset[duckdb.DuckDBPyRelation, duckdb.DuckDBPyRelation]):
"""Save a DuckDB relation to Parquet and load it back as a relation.
Example catalog entry::
nport_holdings:
type: openbasket.datasets.DuckDBParquetDataset
filepath: data/03_primary/nport_holdings.parquet
order_by: SERIES_ID
"""
def __init__(self, filepath: str, order_by: str | None = None, metadata: dict[str, Any] | None = None):
self._filepath = Path(filepath)
self._order_by = order_by
self.metadata = metadata
[docs]
def load(self) -> duckdb.DuckDBPyRelation:
return connection().read_parquet(str(self._filepath))
[docs]
def save(self, data: duckdb.DuckDBPyRelation) -> None:
self._filepath.parent.mkdir(parents=True, exist_ok=True)
if self._order_by:
data = data.order(self._order_by)
tmp = self._filepath.with_suffix(".tmp.parquet")
data.write_parquet(str(tmp), compression="zstd")
tmp.replace(self._filepath)
def _exists(self) -> bool:
return self._filepath.exists()
def _describe(self) -> dict[str, Any]:
return {"filepath": str(self._filepath), "order_by": self._order_by}