Skip to content

lythonic.frame

Library-agnostic tabular data, with one conversion facade per DataFrame library.

Library-agnostic tabular data model.

FrameData is a Pydantic model that stores columnar data in a simple row-oriented format (columns + data). It serializes to/from JSON without any DataFrame library dependency.

Conversions live behind one facade per library, named for the library's conventional import alias. Class access gives the inbound constructors and instance access the outbound conversions:

fd = FrameData.pd.from_frame(df)    # pandas DataFrame in
df = fd.pd.frame()                  # pandas DataFrame out

fd = FrameData.pl.from_frame(df)    # polars DataFrame in
df = fd.pl.frame()                  # polars DataFrame out

fd = FrameData.pa.from_table(tbl)   # pyarrow Table in
tbl = fd.pa.table()                 # pyarrow Table out

Each facade lazily imports its library on first access, so importing this module costs nothing when no optional dependency is installed. Reaching for a library that is not installed raises ImportError naming the package.

>>> fd = FrameData(columns=["a", "b"], data=[[1, 2], [3, 4]])
>>> fd.columns
['a', 'b']
>>> fd.data
[[1, 2], [3, 4]]

FrameData

Bases: BaseModel

Serializable tabular data container. Library-agnostic wire format for passing table data between components.

One facade per DataFrame library: pd, pl, pa. Class access gives the inbound constructors (FrameData.pd.from_frame(df)), instance access the outbound conversions (fd.pd.frame()). Each facade lazily imports its library and raises ImportError if it is not installed.

Source code in src/lythonic/frame.py
class FrameData(BaseModel):
    """
    Serializable tabular data container. Library-agnostic wire format
    for passing table data between components.

    One facade per DataFrame library: `pd`, `pl`, `pa`. Class access gives
    the inbound constructors (`FrameData.pd.from_frame(df)`), instance access
    the outbound conversions (`fd.pd.frame()`). Each facade lazily imports its
    library and raises `ImportError` if it is not installed.
    """

    columns: list[str]
    data: list[list[Any]]

    pd: ClassVar[LibAccess[FrameData, PdIn, PdOut]] = LibAccess(PdIn, PdOut)
    """Pandas facade. Class access gives constructors, instance access conversions."""

    pl: ClassVar[LibAccess[FrameData, PlIn, PlOut]] = LibAccess(PlIn, PlOut)
    """Polars facade. Class access gives constructors, instance access conversions."""

    pa: ClassVar[LibAccess[FrameData, PaIn, PaOut]] = LibAccess(PaIn, PaOut)
    """Pyarrow facade. Class access gives constructors, instance access conversions."""

pd = LibAccess(PdIn, PdOut) class-attribute

Pandas facade. Class access gives constructors, instance access conversions.

pl = LibAccess(PlIn, PlOut) class-attribute

Polars facade. Class access gives constructors, instance access conversions.

pa = LibAccess(PaIn, PaOut) class-attribute

Pyarrow facade. Class access gives constructors, instance access conversions.

PdIn

Class-access pandas facade: constructors that take a pandas DataFrame.

Source code in src/lythonic/frame.py
class PdIn:
    """Class-access pandas facade: constructors that take a pandas DataFrame."""

    _owner: type[FrameData]
    _pandas: ModuleType

    def __init__(self, owner: type[FrameData]) -> None:
        self._owner = owner
        self._pandas = require("pandas")

    def from_frame(self, df: pd.DataFrame) -> FrameData:
        """Build `FrameData` from a pandas DataFrame."""
        if not isinstance(df, self._pandas.DataFrame):
            raise TypeError(f"Expected pandas.DataFrame, got {type(df).__name__}")
        t: dict[str, Any] = df.to_dict(orient="tight", index=False)
        return self._owner(columns=t["columns"], data=t["data"])

from_frame(df)

Build FrameData from a pandas DataFrame.

Source code in src/lythonic/frame.py
def from_frame(self, df: pd.DataFrame) -> FrameData:
    """Build `FrameData` from a pandas DataFrame."""
    if not isinstance(df, self._pandas.DataFrame):
        raise TypeError(f"Expected pandas.DataFrame, got {type(df).__name__}")
    t: dict[str, Any] = df.to_dict(orient="tight", index=False)
    return self._owner(columns=t["columns"], data=t["data"])

PdOut

Instance-access pandas facade: pandas views of the data.

Source code in src/lythonic/frame.py
class PdOut:
    """Instance-access pandas facade: pandas views of the data."""

    _fd: FrameData
    _pandas: ModuleType

    def __init__(self, owner: FrameData) -> None:
        self._fd = owner
        self._pandas = require("pandas")

    def frame(self) -> pd.DataFrame:
        """The data as a pandas DataFrame."""
        fd = self._fd
        return self._pandas.DataFrame.from_dict(
            {
                **fd.model_dump(mode="json"),
                "index": list(range(len(fd.data))),
                "index_names": [""],
                "column_names": [None],
            },
            orient="tight",
        )

frame()

The data as a pandas DataFrame.

Source code in src/lythonic/frame.py
def frame(self) -> pd.DataFrame:
    """The data as a pandas DataFrame."""
    fd = self._fd
    return self._pandas.DataFrame.from_dict(
        {
            **fd.model_dump(mode="json"),
            "index": list(range(len(fd.data))),
            "index_names": [""],
            "column_names": [None],
        },
        orient="tight",
    )

PlIn

Class-access polars facade: constructors that take a polars DataFrame.

Source code in src/lythonic/frame.py
class PlIn:
    """Class-access polars facade: constructors that take a polars DataFrame."""

    _owner: type[FrameData]
    _polars: ModuleType

    def __init__(self, owner: type[FrameData]) -> None:
        self._owner = owner
        self._polars = require("polars")

    def from_frame(self, df: pl.DataFrame) -> FrameData:
        """Build `FrameData` from a polars DataFrame."""
        if not isinstance(df, self._polars.DataFrame):
            raise TypeError(f"Expected polars.DataFrame, got {type(df).__name__}")
        return self._owner(columns=df.columns, data=[list(row) for row in df.rows()])

from_frame(df)

Build FrameData from a polars DataFrame.

Source code in src/lythonic/frame.py
def from_frame(self, df: pl.DataFrame) -> FrameData:
    """Build `FrameData` from a polars DataFrame."""
    if not isinstance(df, self._polars.DataFrame):
        raise TypeError(f"Expected polars.DataFrame, got {type(df).__name__}")
    return self._owner(columns=df.columns, data=[list(row) for row in df.rows()])

PlOut

Instance-access polars facade: polars views of the data.

Source code in src/lythonic/frame.py
class PlOut:
    """Instance-access polars facade: polars views of the data."""

    _fd: FrameData
    _polars: ModuleType

    def __init__(self, owner: FrameData) -> None:
        self._fd = owner
        self._polars = require("polars")

    def frame(self) -> pl.DataFrame:
        """The data as a polars DataFrame."""
        fd = self._fd
        return self._polars.DataFrame(fd.data, schema=fd.columns, orient="row")

frame()

The data as a polars DataFrame.

Source code in src/lythonic/frame.py
def frame(self) -> pl.DataFrame:
    """The data as a polars DataFrame."""
    fd = self._fd
    return self._polars.DataFrame(fd.data, schema=fd.columns, orient="row")

PaIn

Class-access pyarrow facade: constructors that take a pyarrow Table.

Source code in src/lythonic/frame.py
class PaIn:
    """Class-access pyarrow facade: constructors that take a pyarrow Table."""

    _owner: type[FrameData]
    _pyarrow: ModuleType

    def __init__(self, owner: type[FrameData]) -> None:
        self._owner = owner
        self._pyarrow = require("pyarrow")

    def from_table(self, table: pa.Table) -> FrameData:
        """Build `FrameData` from a pyarrow Table."""
        if not isinstance(table, self._pyarrow.Table):
            raise TypeError(f"Expected pyarrow.Table, got {type(table).__name__}")
        col_dict = table.to_pydict()
        columns = table.column_names
        data = [[col_dict[c][i] for c in columns] for i in range(table.num_rows)]
        return self._owner(columns=columns, data=data)

from_table(table)

Build FrameData from a pyarrow Table.

Source code in src/lythonic/frame.py
def from_table(self, table: pa.Table) -> FrameData:
    """Build `FrameData` from a pyarrow Table."""
    if not isinstance(table, self._pyarrow.Table):
        raise TypeError(f"Expected pyarrow.Table, got {type(table).__name__}")
    col_dict = table.to_pydict()
    columns = table.column_names
    data = [[col_dict[c][i] for c in columns] for i in range(table.num_rows)]
    return self._owner(columns=columns, data=data)

PaOut

Instance-access pyarrow facade: Arrow views of the data.

Source code in src/lythonic/frame.py
class PaOut:
    """Instance-access pyarrow facade: Arrow views of the data."""

    _fd: FrameData
    _pyarrow: ModuleType

    def __init__(self, owner: FrameData) -> None:
        self._fd = owner
        self._pyarrow = require("pyarrow")

    def table(self) -> pa.Table:
        """The data as a pyarrow Table."""
        fd = self._fd
        col_dict = {c: [row[i] for row in fd.data] for i, c in enumerate(fd.columns)}
        return self._pyarrow.table(col_dict)

table()

The data as a pyarrow Table.

Source code in src/lythonic/frame.py
def table(self) -> pa.Table:
    """The data as a pyarrow Table."""
    fd = self._fd
    col_dict = {c: [row[i] for row in fd.data] for i, c in enumerate(fd.columns)}
    return self._pyarrow.table(col_dict)