Repository navigation
State the current reasons for the Polars chunk reader factories - #1271
Conversation
The cross-thread close() that the split was made for is unsupported now. Describe what creating the readers during execute() does instead: the requests that prepare the reads raise their errors there, and the Parquet schema is read once. Closes #1249 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| The chunk readers take the state that they need, such as the output location, | ||
| the column names, and the connection, when they are created. The credentials | ||
| are frozen at the first read. | ||
| The result set creates the iterator during ``execute()``. The requests that |
There was a problem hiding this comment.
Self-review round 1 (implementation behavior): CLEAN
Base e086eac7bb2120e434c08c2eababccd9d0167fb0, head 0174353d12172a03995a4420da46a5c9d5caeb83. Scope: docstrings of _get_parquet_storage_options() and _create_dataframe_iterator(); no code change.
- The only remaining text that gave the cross-thread
close()reason from Give the chunk readers the state that close() clears when they are created #1190 was in these two docstrings (git log -Son c78e418's wording, andgrepfor "when they are created" / "releases the result set"). - The new text matches the code:
__init__calls_create_dataframe_iterator()only on the chunked path with an output location, so_iter_csv_chunks()always calls_is_csv_readable()→_get_content_length()(HeadObject,OperationalErroron failure,pyathena/result_set.py:853-869); the UNLOAD path calls_prepare_parquet_location()(data manifest,pyathena/result_set.py:871-885) and_read_parquet_schema(), whose result sets_metadataand goes to_iter_parquet_chunks(schema). - The removed sentence about credentials being frozen at the first read stays documented where it applies, in the
connectionarguments of_scan_csv_chunks()and_scan_parquet_chunks(). - No behavior change, so no regression test is added.
| connection: The connection whose session and region the reads use. The | ||
| chunk readers give the connection that they took when they were | ||
| created, as ``close()`` releases the result set's connection. | ||
| connection: The connection whose session and region the reads use. |
There was a problem hiding this comment.
Self-review round 2 (claims, callers): CLEAN
Base e086eac7bb2120e434c08c2eababccd9d0167fb0, head 0174353d12172a03995a4420da46a5c9d5caeb83.
- "
execute()raises their errors": the result set is constructed insideexecute()forPolarsCursorandAioPolarsCursor(in its thread), and inside the future ofAsyncPolarsCursor.execute(), whoseresult()raises them. In all three the errors surface before the first fetch, which is the distinction Decide whether the Polars chunk readers keep the state they take at creation #1249 is about. - PR body claims: Break result sets after failed or cancelled fetches and drop the cross-thread guards #1238 reverted the pandas Parquet reader change and kept the Polars split (pandas
_iter_parquet_chunks()now takes no state arguments,pyathena/pandas/result_set.py:1564); merging the CSV pair would move_is_csv_readable()into the generator body and so to the first fetch. - The
connectionparameter is still passed by the readers; this PR keeps that structure as option 1 decided.
AsyncPolarsCursor raises the preparation errors from its future, not from execute(), and the pyarrow chunk reader does not use the Parquet schema. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| The chunk readers take the state that they need, such as the output location, | ||
| the column names, and the connection, when they are created. The credentials | ||
| are frozen at the first read. | ||
| The result set creates the iterator during ``execute()``. The requests that |
There was a problem hiding this comment.
Independent review (relayed): Codex CLI 0.163.0 (codex exec -s read-only, session 01a14cf6-50bd-7fd0-ba94-3f8ef42e3226), static review on a detached snapshot of 0174353d12172a03995a4420da46a5c9d5caeb83, base e086eac7bb2120e434c08c2eababccd9d0167fb0. The prompt contained only the diff, the threadsafety contract, and the review request (no PR text or prior findings). The snapshot and the PR worktree were unchanged afterwards.
Result: FINDINGS (2). Covered: the diff, all docstrings/comments in result_set.py, credential handling, CSV/UNLOAD preparation, both chunk-reader branches, close behavior, and construction/error propagation through all three cursors. No remaining docstring or comment keeps the obsolete close() rationale.
- [P2]
execute()does not raise the preparation errors forAsyncPolarsCursor. Scenario: a chunked SELECT succeeds, but the CSV size check fails.AsyncPolarsCursor.execute()returns(query_id, future)and builds the result set in its executor (pyathena/polars/async_cursor.py:272), so theOperationalErrorsurfaces fromfuture.result(), notexecute().
Verified against the code: valid. Self-review round 2 had noted the future path but accepted the wording; the reviewer is right that it is inaccurate.
There was a problem hiding this comment.
Repaired in cf186d9: the docstring now says the result set calls this method when it is created, "in execute() or in the future that AsyncPolarsCursor.execute() returns", and that the requests raise their errors there, before the first fetch. The PR body was updated to match.
Repair self-review (rounds 1 and 2 on the changed hunk): CLEAN. __init__ calls _create_dataframe_iterator() (pyathena/polars/result_set.py:378); PolarsCursor and AioPolarsCursor build the result set inside execute() (the latter awaited in a thread), and AsyncPolarsCursor inside its future. just lint passed. An independent follow-up on cf186d9 follows.
There was a problem hiding this comment.
Independent follow-up (relayed): Codex CLI 0.163.0 (codex exec -s read-only, session 01a14cfb-8ade-7b50-a4d1-2d19fcbc2f8b), static review of cf186d9 against 0174353 on a detached snapshot (unchanged afterwards).
Result: FINDINGS (1). Both earlier findings are resolved: the preparation errors surface during result-set construction through PolarsCursor.execute(), future.result() of AsyncPolarsCursor, and await AioPolarsCursor.execute(), and the pyarrow reader is no longer claimed to use the schema.
- [P3] "the Polars scan of the chunks also uses it" needs a qualification: with
schema=given toexecute(),_int96_read_options()returns{}(pyathena/polars/result_set.py:870),_output_dtypes()returns{}, and the scan reads with the caller's schema, so the discovered schema only sets the metadata (covered bytests/pyathena/polars/test_cursor.py:257).
Verified: valid. Repaired in 088b79e: "the Polars scan of the chunks uses it to read timestamps at microseconds unless execute() was given a schema", matching _int96_read_options() and _output_dtypes() (Polars 2.x read schema, Polars 1.x casts). Repair self-review (both perspectives): CLEAN; PR body updated; just lint passed. A second follow-up on 088b79e follows.
There was a problem hiding this comment.
Independent follow-up 2 (relayed): Codex CLI 0.163.0 (codex exec -s read-only, session 01a14cfe-6dbf-7513-a7dc-013785c0a45b), static review of 088b79e against cf186d9 on a detached snapshot (unchanged afterwards).
Result: FINDINGS (1). The explicit-schema finding is resolved.
- [P2] "uses it to read timestamps at microseconds" does not hold on Polars 1.x: with
unload=True,chunksize, a read option such aslow_memory=True(which disables the pyarrow path) and noschema,_int96_read_options()returnsNoneand_scan_parquet_chunks()reads at nanoseconds before casting to microseconds (pyathena/polars/result_set.py:1211).
Verified: valid. Repaired in df1d35b by not restating the scan's use of the schema here: the docstring now says the schema "is also passed to _iter_parquet_chunks()", whose existing docstring describes the Polars 1.x/2.x and pyarrow behavior. The duplicate "sets the metadata" clause was dropped, since the first paragraph already says so. Repair self-review (both perspectives): CLEAN; PR body updated; just lint passed. A third follow-up on df1d35b follows.
There was a problem hiding this comment.
Independent follow-up 3 (relayed): Codex CLI 0.163.0 (codex exec -s read-only, session 01a14d01-5a73-75e3-9688-4d811812633e), static review of df1d35b against 088b79e on a detached snapshot (unchanged afterwards).
Result: CLEAN. Covered: the exact diff and the complete docstring; constructor timing, CSV size checks, UNLOAD manifest/schema reads, metadata replacement, empty results, and both chunk-reader paths; construction and error propagation through PolarsCursor, AsyncPolarsCursor, and AioPolarsCursor; relevant test source without execution. The previous finding is resolved, and the docstring accurately describes eager preparation, where its errors surface, and lazy chunk reads across all three cursors.
There was a problem hiding this comment.
Correction to the follow-up 2 record: the amend that produced df1d35b did not include the edit that dropped the duplicate "sets the metadata" clause (it was left uncommitted). The merged docstring therefore reads "The schema sets the metadata and is passed to _iter_parquet_chunks()", which is what the follow-up 3 review checked and found accurate. The remaining repair (no longer restating how the scan uses the schema) is in the merged commit; only the duplicate clause, already stated in the first paragraph, remains.
| The result set creates the iterator during ``execute()``. The requests that | ||
| prepare the reads are made then, so ``execute()`` raises their errors: the size | ||
| check of a CSV result file, and the data manifest and the Parquet schema of an | ||
| UNLOAD result. The schema is read once, for the metadata and for the chunk |
There was a problem hiding this comment.
Independent review (relayed), finding 2 [P3]: the schema is not shared with the pyarrow chunk reader. Scenario: unload=True, chunksize=1000, pyarrow installed, and no reader kwargs. The schema sets the metadata, but _iter_parquet_chunks() ignores it on the _reads_with_pyarrow() branch and builds ParquetChunkReader without it (pyathena/polars/result_set.py:1152), so "for the chunk reader" holds only for the Polars scan.
Verified against the code: valid.
There was a problem hiding this comment.
Repaired in cf186d9: "The schema is read once, for the metadata and for the chunk reader" became "The schema sets the metadata, and the Polars scan of the chunks also uses it", which holds only on the _scan_parquet_chunks(location, connection, schema) branch (pyathena/polars/result_set.py:1157); the pyarrow branch is not claimed to use it. The PR body was updated to match.
With a schema given to execute(), the scan reads with that schema and does not use the one read from the files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
How the chunk scan uses the schema depends on the Polars version and on the arguments given to execute(), which _iter_parquet_chunks() describes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
38ee56f to
df1d35b
Compare
WHAT
Docstring-only change in
pyathena/polars/result_set.py, following option 1 of #1249 (keep the chunk reader split):_get_parquet_storage_options(): remove the reason that the chunk readers pass the connection they took at creation becauseclose()releases the result set's connection._create_dataframe_iterator(): replace "the chunk readers take the state that they need when they are created" with the current reasons. The result set creates the iterator when it is created, inexecute()or in the future thatAsyncPolarsCursor.execute()returns, so the requests that prepare the reads (theHeadObjectsize check of a CSV result file, and the data manifest and Parquet schema of an UNLOAD result) are made then and raise their errors before the first fetch; the schema, which sets the metadata as the first paragraph says, is also passed to_iter_parquet_chunks(), whose docstring describes how the chunk readers use it.No behavior changes.
WHY
Closes #1249. The split came from #1190 for a
close()from another thread before the first read. #1206 (PR #1238) made that cross-thread use unsupported, so that reason no longer applies. Merging the readers back into single generators would move the CSV size check and its errors fromexecute()to the first fetch, a user-visible change; the maintainer chose to keep the split and only correct the docstrings.TEST
just lint: passed (ruff, format check, mypy, CloudFormation, license headers).🤖 Generated with Claude Code