[FLINK-40190][python] Add PyFlink DataFrame creation and conversion APIs - #28934
[FLINK-40190][python] Add PyFlink DataFrame creation and conversion APIs#28934auroflow wants to merge 7 commits into
Conversation
Add pandas, Arrow, Table, and range creators with strict schema and watermark validation. Add DataFrame conversion wrappers and schema-aware in-memory and Arrow source paths. Generated-by: Codex (GPT-5)
Align timezone-aware Arrow timestamps with existing Table API semantics, delegate pandas creation to the Arrow path, and add split-aware Arrow IPC serialization. Refine watermark and row helpers with focused tests. Generated-by: Codex (GPT-5)
Combine inferred-schema row conversion with DataFrame creation and let range use its known BIGINT schema directly. Generated-by: Codex (GPT-5)
Reject ranges whose emitted values exceed signed BIGINT bounds before creating the underlying table. Cover valid boundary values and ascending and descending overflow cases. Generated-by: Codex (GPT-5)
f53446b to
654b9ca
Compare
Clarify that to_table does not execute a job, simplify the DataFrame creation and results reference pages, and exercise from_pandas through filtering, projection, and conversion in the existing pandas round-trip integration smoke test. Generated-by: Codex (GPT-5)
66e5281 to
e693442
Compare
Inline the single-use Arrow IPC writer into TableEnvironment._from_arrow and remove its helper-specific tests. This also avoids the NamedTemporaryFile wrapper type mismatch reported by mypy. Generated-by: Codex (GPT-5)
| return column_names | ||
|
|
||
|
|
||
| def _parse_watermark( |
There was a problem hiding this comment.
Can we move this under the _WaterMarkSpec class? Like:
class Watermark:
@classmethod
def parse(cls, ....):
....
return cls(....)There was a problem hiding this comment.
Agreed. I moved it under _WaterMarkSpec.
| return _WatermarkSpec(*watermark) | ||
|
|
||
|
|
||
| def _normalize_watermark_row_type(row_type: RowType, watermark: _WatermarkSpec) -> RowType: |
There was a problem hiding this comment.
Can we move this under the _WaterMarkSpec class? Like:
class Watermark:
def normalize_row_type(self, row_type: RowType) -> RowType:
...There was a problem hiding this comment.
Moved under _WaterMarkSpec.
| table_schema = ( | ||
| Schema.new_builder() | ||
| .from_row_data_type(row_type) | ||
| .watermark(watermark.column, watermark.expression) |
There was a problem hiding this comment.
since the watermark is a NamedTuple, you can simplify the call by unpacking it like:
schema.watermark(*watermark)I feel like this both reads better and is more concise.
There was a problem hiding this comment.
Agreed, I changed this to unpacking.
| return RowType(fields, row_type._nullable) | ||
|
|
||
|
|
||
| def _resolve_watermark_schema( |
There was a problem hiding this comment.
This helper function feels shallow and with only 2 uses a little unecessary. Thoughts on inlining the logic at the call sites like:
if watermark:
row_type = watermark.normalize_row_type(row_type)
table_schema = ...
else:
table_schema = Nonethis feels a easier to follow to me
There was a problem hiding this comment.
I inlined this helper function.
There was a problem hiding this comment.
since _WatermarkSpec is a NamedTuple you should add a quick test to verify unpacking order. This would help guard against errors caused by someone changing the internal field order or values. In particular, this would enable us to be confident that:
schema.watermark(*watermark)won't quietly break in the future.
There was a problem hiding this comment.
I added a test here to verify unpacking order.
Encapsulate watermark parsing and row-type normalization, inline schema construction, and verify watermark unpacking order. Generated-by: Codex (GPT-5)
| names = _resolve_column_names(table.column_names, schema) | ||
| row_type = RowType( | ||
| [ | ||
| RowField(name, from_arrow_type(field.type, field.nullable)) |
There was a problem hiding this comment.
from_arrow_type() maps timezone-bearing Arrow timestamps to TIMESTAMP, and _from_arrow then casts away the timezone. With Asia/Shanghai, 2026-01-01 00:00+08:00 round-trips as naïve 2025-12-31 16:00, which also changes downstream event-time behavior.
| import pyarrow as pa | ||
|
|
||
| return from_arrow( | ||
| pa.Table.from_pandas(pdf, preserve_index=False), |
There was a problem hiding this comment.
pa.Table.from_pandas rejects duplicate pandas column names before from_arrow can apply the supplied positional schema. Consequently a DataFrame with columns ['x', 'x'] cannot be converted even with schema=['left', 'right'], contrary to the positional-renaming contract. Assign temporary unique names or convert columns by position before constructing the Arrow table.
| .watermark(*watermark_spec) | ||
| .build() | ||
| ) | ||
| result = get_or_create_table_environment()._from_arrow( |
There was a problem hiding this comment.
Arrow null fields become Flink NullType here, producing a DataFrame whose schema resolves but whose execution always fails with FlinkTypeFactory's 'null type is reserved' ValidationException. This occurs for common inputs such as pd.DataFrame({'x': [None]}), and the name-only schema offers no escape hatch. Reject these fields during creation or support an explicit typed schema.
| raise TypeError("data must be a mapping") | ||
| if not data: | ||
| raise ValueError("data must not be empty") | ||
| watermark_spec = _WatermarkSpec.parse(watermark) |
There was a problem hiding this comment.
Add an example about watermark usage.
What is the purpose of the change
This pull request completes the data creation and conversion APIs for the PyFlink DataFrame API described in FLINK-40190.
It adds creation from pandas DataFrames, PyArrow tables, PyFlink Tables, dictionaries, records, and integer ranges. It also supports conversion back to Table and pandas, explicit column names, and event-time watermarks where applicable.
Brief change log
from_pandas(),from_arrow(),from_table(), andrange(), and complete the existingfrom_dict()andfrom_records()creators.DataFrame.to_table()andDataFrame.to_pandas().Verifying this change
This change added tests and can be verified as follows:
to_table()andto_pandas().from_arrow()→ DataFrame transformation →to_pandas().Does this pull request potentially affect one of the following parts:
@Public(Evolving): yesDocumentation
Was generative AI tooling used to co-author this PR?
Generated-by: Codex (GPT-5)