Skip to content

Fix issue 791: add LazyFrame sink classes for parquet, csv, ipc, ndjson#1653

Open
sonalishintre wants to merge 3 commits into
apache:mainfrom
sonalishintre:fix-issue-791
Open

Fix issue 791: add LazyFrame sink classes for parquet, csv, ipc, ndjson#1653
sonalishintre wants to merge 3 commits into
apache:mainfrom
sonalishintre:fix-issue-791

Conversation

@sonalishintre

Copy link
Copy Markdown

Summary

Closes #791

Adds data sink support for Polars LazyFrames, allowing users to write
LazyFrames directly to disk without calling lf.collect() first, which
is more performant.

Changes

  • Added PolarsLazyFrameSinkParquet class
  • Added PolarsLazyFrameSinkCSV class
  • Added PolarsLazyFrameSinkIPC class
  • Added PolarsLazyFrameSinkNDJSON class
  • Registered all new sinks in register_data_loaders()

How I tested this

Added 4 new tests in tests/plugins/test_polars_lazyframe_extensions.py:

  • test_polars_lazyframe_sink_parquet
  • test_polars_lazyframe_sink_csv
  • test_polars_lazyframe_sink_ipc
  • test_polars_lazyframe_sink_ndjson

Each test creates a LazyFrame, sinks it to a temp file, reads it back,
and verifies the data matches.

Notes

  • sink_ndjson has no corresponding loader yet as noted in the issue

Checklist

  • PR has an informative and human-readable title
  • Changes are limited to a single goal (no scope creep)
  • Any change in functionality is tested
  • New functions are documented (with a description and docstring)

@sonalishintre

Copy link
Copy Markdown
Author

Hi @skrawcz,
this is my first contribution to this project.
I've implemented the LazyFrame sink classes requested in #791
(sink_parquet, sink_csv, sink_ipc, sink_ndjson) with tests for each.
Would appreciate a review when you have time!

@jernejfrank jernejfrank left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @sonalishintre and welcome. Thanks for taking the time to contribute. In general looks good just a couple of remarks to keep it consistent with the rest of the existing implementations (see below).

If you look at the https://github.com/apache/hamilton/blob/main/hamilton/plugins/polars_post_1_0_0_extensions.py you will see the same implementations for the eager frame and can see the class naming conventions and order in which the classes appear. Could you try to mirror that?

Otherwise looking good and great job!

return [DATAFRAME_TYPE]

def save_data(self, data: pl.LazyFrame) -> dict[str, Any]:
data.sink_parquet(self.path)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add the ability to add the lazyframe.sink_parquet kwargs? https://docs.pola.rs/api/python/stable/reference/api/polars.LazyFrame.sink_parquet.html

return "feather"

@dataclasses.dataclass
class PolarsLazyFrameSinkParquet(DataSaver):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we name this PolarsSinkParquetWriter?

return [DATAFRAME_TYPE]

def save_data(self, data: pl.LazyFrame) -> dict[str, Any]:
data.sink_ipc(self.path)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.



@dataclasses.dataclass
class PolarsLazyFrameSinkIPC(DataSaver):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same rename style: PolarsSinkFeatherWriter



@dataclasses.dataclass
class PolarsLazyFrameSinkNDJSON(DataSaver):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same rename: PolarsSinkNDJSONWriter

return [DATAFRAME_TYPE]

def save_data(self, data: pl.LazyFrame) -> dict[str, Any]:
data.sink_ndjson(self.path)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.



@dataclasses.dataclass
class PolarsLazyFrameSinkCSV(DataSaver):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same rename: PolarsSinkCSVWriter

return [DATAFRAME_TYPE]

def save_data(self, data: pl.LazyFrame) -> dict[str, Any]:
data.sink_csv(self.path)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sonalishintre

Copy link
Copy Markdown
Author

Hi @jernejfrank — I've made the requested changes:

  • Renamed classes to match existing naming convention
    (PolarsSinkParquetWriter, PolarsSinkCSVWriter,
    PolarsSinkFeatherWriter, PolarsSinkNDJSONWriter)
  • Added kwargs support with _get_saving_kwargs() for all 4 sink classes
  • All 4 tests passing

Please let me know if any further changes are needed!
Thank You

@jernejfrank

Copy link
Copy Markdown
Contributor

Thanks for the update. I took another pass through the latest revision and found a few things that should be addressed before merging:

  1. Feather saver is registered under the wrong adapter name

    PolarsSinkFeatherWriter.name() currently returns "ipc", whereas PolarsScanFeatherReader and the existing PolarsFeatherWriter both use "feather". As a result, to.feather(...) will not select this new streaming saver and can continue selecting the eager writer, which collects the LazyFrame. Could this return "feather", with an integration test confirming that a LazyFrame passed to to.feather selects PolarsSinkFeatherWriter?

  2. LazyFrame saver selection is ambiguous and registration-order dependent

    The existing eager CSV, Parquet, Feather, and NDJSON writers already advertise support for pl.LazyFrame. The new sink writers advertise the same type under the same adapter names, so Hamilton reports more than one applicable adapter and chooses based on registration order. A different manual import order could therefore select the eager writer and call collect() again. Could we make LazyFrame selection unambiguous and test adapter resolution through the registry/materializer API?

  3. The kwargs support is incomplete

    The dataclass fields plus _get_saving_kwargs() pattern is consistent with the other implementations, but not all intended sink options are represented. Most clearly, LazyFrame.sink_csv() supports include_header=True, and the existing PolarsCSVWriter exposes it too, but PolarsSinkCSVWriter does not. IPC and NDJSON also expose only a small subset of their documented options. Could we cover the compatible options we intend to support, or document why specific ones are intentionally omitted? (lazy=True may need to remain excluded or receive special handling because it conflicts with save_data() expecting the file to exist immediately.)

  4. The tests do not verify custom kwargs or adapter integration

    The tests currently inspect default values such as separator == ",", but do not instantiate savers with non-default kwargs and verify their effect. They also instantiate the classes directly, which misses the "ipc"/"feather" registration issue and duplicate saver selection. Could we add at least one non-default kwarg test per relevant saver and a registry/materializer resolution test?

  5. The superseded implementation and tests are commented out rather than removed

    The old four saver classes and duplicate registration function remain commented out near the end of polars_lazyframe_extensions.py, and the old four tests remain commented out near the end of the test file. Could these dead blocks be deleted before merging?

  6. The surrounding-code ordering was not fully mirrored

    The established Polars module uses CSV, Parquet, Feather, etc., generally placing each reader and writer together. This revision puts Parquet before CSV and groups all new writers after the readers. Could we mirror the surrounding module's ordering as requested in the previous review?

  7. Formatting checks currently fail

    Ruff reports trailing whitespace in polars_lazyframe_extensions.py, an unsorted import block in the test file, and both changed files require formatting. git diff --check also reports an extra blank line at EOF. There are also temporary-looking #### separators. Please run the configured formatting/lint hooks before the next review.

For reference, the functional test file passes locally (11 passed, 1 skipped), but the current tests do not cover the registry and kwargs issues above.

@jernejfrank

Copy link
Copy Markdown
Contributor

@sonalishintre let me know if any of the above are unclear or you need help with any of the points. Happy to sketch it out / point to the right places so you can get this over the line

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add data source sinks for Polars Lazyframe implementation

2 participants