Skip to content
Merged
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
13 changes: 11 additions & 2 deletions README-PYPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ Convert [Polars LazyFrames](https://docs.pola.rs/api/python/stable/reference/laz

Polars and Ibis have similar APIs, but while Polars supports computation in-memory and on [Polars Cloud](https://cloud.pola.rs/), Ibis by itself does not handle computation: Instead it translates the dataframe expression into idiomatic SQL for a particular database.

The public interface of `polars_to_ibis` consists of exactly one function: `convert_polars_to_ibis`.

## Example

```python
Expand Down Expand Up @@ -55,6 +53,17 @@ Finally, we can execute in SQLite the query which we constructed in Polars and t

```

In this example we somewhat artificially started with a Polars LazyFrame.
In the real world, you more likely would start with a database.
To read a database table's schema and create from that a LazyFrame, use `scan_database`:

```python
>>> from polars_to_ibis import scan_database
>>> dict(scan_database(connection, table_name).collect_schema())
{'ints': Int64}

```


## Limitations

Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ Convert [Polars LazyFrames](https://docs.pola.rs/api/python/stable/reference/laz

Polars and Ibis have similar APIs, but while Polars supports computation in-memory and on [Polars Cloud](https://cloud.pola.rs/), Ibis by itself does not handle computation: Instead it translates the dataframe expression into idiomatic SQL for a particular database.

The public interface of `polars_to_ibis` consists of exactly one function: `convert_polars_to_ibis`.

## Example

```python
Expand Down Expand Up @@ -55,6 +53,17 @@ Finally, we can execute in SQLite the query which we constructed in Polars and t

```

In this example we somewhat artificially started with a Polars LazyFrame.
In the real world, you more likely would start with a database.
To read a database table's schema and create from that a LazyFrame, use `scan_database`:

```python
>>> from polars_to_ibis import scan_database
>>> dict(scan_database(connection, table_name).collect_schema())
{'ints': Int64}

```


## Limitations

Expand Down
8 changes: 8 additions & 0 deletions polars_to_ibis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ def _check_version():
)


def scan_database(connection: Any, table_name: str):
"""
Get the schema from a database table and convert it to Polars.
"""
ibis_schema = connection.get_schema(table_name)
return pl.LazyFrame(schema=ibis_schema.to_polars())


def convert_polars_to_ibis(lf: pl.LazyFrame, table_name: str) -> ibis.Table:
"""
Convert a Polars LazyFrame to an Ibis unbound table.
Expand Down
31 changes: 19 additions & 12 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import polars as pl
import pytest

from polars_to_ibis import convert_polars_to_ibis
from polars_to_ibis import convert_polars_to_ibis, scan_database
from polars_to_ibis._parse.table_handlers import update_polars_to_ibis

from .fixtures import Fixture, fixtures, input_data
Expand Down Expand Up @@ -41,7 +41,8 @@ def get_connection(df: pl.DataFrame, table_name: str, backend: str):
# Test fixtures:

backends = [
"polars",
# Polars could be tested, but there's an error getting the schema,
# and since it's not a realistic target for us, drop it from coverage.
"sqlite",
"duckdb",
pytest.param("postgres", marks=pytest.mark.extra_install),
Expand Down Expand Up @@ -73,22 +74,23 @@ def assert_error_or_none(
return result


@pytest.mark.parametrize(
"fixture", fixtures, ids=lambda fixture: f"{fixture.category}-{fixture.expression}"
)
def test_fixture_consistency(fixture: Fixture):
# Does the polars expression have the expected result?
globals = {"lf": pl.LazyFrame(input_data[fixture.category]), "pl": pl}
polars_output = eval(fixture.expression, globals).collect().to_dict(as_series=False)
assert polars_output == fixture.expected_output, "Typo in fixture?"


@pytest.mark.parametrize(
"fixture", fixtures, ids=lambda fixture: f"{fixture.category}-{fixture.expression}"
)
@pytest.mark.parametrize("backend", backends)
@pytest.mark.parametrize("exporter_key", exporters.keys()) # type: ignore
def test_translate_table(fixture: Fixture, backend: str, exporter_key: str):
# Sanity check: Does the polars expression have the expected result?
lf = pl.LazyFrame(input_data[fixture.category])
polars_output = eval(fixture.expression).collect().to_dict(as_series=False)
assert polars_output == fixture.expected_output, "Typo in test?"

# Convert polars to ibis, but without any data:
lf = pl.LazyFrame(schema=lf.collect_schema())
lf = eval(fixture.expression)
def test_translate_table_new(fixture: Fixture, backend: str, exporter_key: str):
table_name = "default_table"
ibis_table = convert_polars_to_ibis(lf, table_name)

# Set up target database, with data:
input_df = pl.DataFrame(input_data[fixture.category])
Expand All @@ -98,6 +100,11 @@ def test_translate_table(fixture: Fixture, backend: str, exporter_key: str):
lambda: get_connection(input_df, table_name=table_name, backend=backend),
)

globals = {"lf": scan_database(connection, table_name), "pl": pl}
lf = eval(fixture.expression, globals)

ibis_table = convert_polars_to_ibis(lf, table_name)

# Run query on target database:
export = exporters[exporter_key] # type: ignore
expected_backend_error = fixture.backend_errors.get(
Expand Down
Loading