Repository navigation
Backport #1185 and #1169 to 3.x - #1198
Conversation
…r accept a cursor class by name The shared dialect now asks for the API cursor class positionally, which the async adapted connection did not accept, and it could not drive a synchronous Cursor anyway. Accept the argument and map it to the async counterpart. (cherry picked from commit c61c361) 3.x resolution: by maintainer decision, only this commit of #798 is ported, as the prerequisite of #1185, whose do_ping() asks the async adapted connection for an API cursor. The rest of #798 (reflection through an API cursor and the information_schema fallback) builds on metadata work that 3.x does not have. - pyathena/aio/sqlalchemy/base.py: AsyncAdapt_pyathena_connection.cursor() takes the optional cursor class. It takes no **kwargs, because the keyword passthrough came with #777, which 3.x does not have; the cursor_kwargs and retry_config properties of the conflict hunk come from the same excluded work. - pyathena/sqlalchemy/base.py: unchanged. This commit only retyped AthenaDialect._internal_cursor(), which 3.x does not have. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This fixes the issue of running a ping when unload=true is on which results in a MISSING_COLUMN_NAME error. Closes #1189 (cherry picked from commit 119cc70) 3.x resolution: 3.x has neither Connection._internal_cursor() (#1102, a 4.0.0 breaking change) nor pyathena.util.override, so: - pyathena/sqlalchemy/base.py: do_ping() opens the API cursor with dbapi_connection.cursor(Cursor), which the async adapted connection maps to AioCursor since the #798 commit ported before this one. 3.x Connection.cursor() passes every cursor_kwargs entry to the cursor, and Cursor ignores DataFrame-cursor options such as unload and chunksize, so the ping runs SELECT 1 without UNLOAD; it fetches no rows, so the connection-level converter does not matter. The method has no @OverRide decorator, and do_ping() sits after _get_table(), since 3.x has no _internal_cursor() to follow. Cursor is imported, as #798 did on master. - tests/pyathena/sqlalchemy/test_base.py: the unit test uses the 3.x adapter names AsyncAdapt_pyathena_connection and AsyncAdapt_pyathena_cursor and imports MagicMock and the connection and cursor classes it needs; the other master tests in the conflict hunk are not part of this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Polars 2.0 reads a path in read_csv() with its native object store and rejects the fsspec storage options that PyAthena passed, so every CSV result read without chunksize failed. The result set now opens the file with fsspec.open() and the same options, and passes its contents to Polars, which keeps PyAthena's S3 filesystem and its read settings with Polars 1.x and 2.0. Polars 2.0 also names headerless columns from column_0 and requires new_columns to name every column, so the tests that pass those options through no longer depend on the Polars version. The dev lock tests Polars 2 on Python 3.14, which pull requests run, and keeps the newest Polars 1.x release on older versions. Closes #1168 (cherry picked from commit 0c6a0b2) 3.x resolution: 3.x does not have the master changes that this commit builds on (execute()-level storage_options replacement, the timestamp re-read and the GetQueryResults CSV path of _read_csv(), and the TestAthenaPolarsResultSet unit tests), so only this commit's own delta is applied: - pyathena/polars/result_set.py: _read_csv() opens output_location with fsspec.open(..., **self._csv_storage_options) and passes the bytes to pl.read_csv() with 3.x's existing arguments, instead of passing the path and storage_options to Polars. - pyproject.toml: the dev group keeps 3.x's "polars>=1.0.0" floor and adds the Polars 2 marker pair for Python 3.14 and older versions. - uv.lock: relocked; only polars and polars-runtime-32 change. Python 3.14 resolves Polars 2.0.0, and the older versions keep 3.x's locked 1.36.1 rather than master's newest 1.x release. - tests/pyathena/polars/test_cursor.py, test_result_set.py: unchanged. The tests this commit adjusts (new_columns with schema_overrides, headerless column names, and the storage_options replacement cases) do not exist on 3.x; the existing PolarsCursor integration tests cover _read_csv() with both Polars majors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Polars 2.0 reads a Parquet map as a Map column, which get_athena_type() did not know, so UNLOAD results described map columns as string. Nested types are now matched by their outer type, which also describes a struct containing a list as row instead of array. The docs now say that Polars reads with fsspec when use_pyarrow=True is given. (cherry picked from commit a473e9c) 3.x resolution: - docs/polars.md: unchanged. The sentence this commit edits belongs to the "S3 read options" section, which 3.x does not have; the preceding #1169 commit 6ec3e00, which added that sentence and reworded two test comments of tests 3.x does not have, was skipped as empty. - tests/pyathena/polars/test_cursor.py: test_nested_type_description_unload runs without chunksize only. On 3.x, a chunked UNLOAD result describes its columns as the UNLOAD statement's own "rows" column, because the Parquet schema is not read on that path; master fixed that in #1012, which 3.x does not have. - pyathena/polars/util.py and tests/pyathena/polars/test_util.py apply unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…polars() Polars 2.0 converts an Arrow map to a Map column whose values are dicts; Polars 1.x converts it to a list of key-value structs. (cherry picked from commit 22de454) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| raise exc.NoSuchTableError(table_name) from e | ||
| raise | ||
|
|
||
| def do_ping(self, dbapi_connection: DBAPIConnection) -> bool: |
There was a problem hiding this comment.
Self-review round one (implementation behavior): CLEAN.
Scope: base 5970145, head a2e6899; every changed file (dialect do_ping(), the async adapter's cursor(), AthenaPolarsResultSet._read_csv(), get_athena_type(), the dev group and uv.lock, and the new and changed tests).
- Behavior and failure paths:
do_ping()returnsTrueor raises, like SQLAlchemy's default on 2.x. On 1.x the default also catchesdbapi.Errorforis_disconnect(), which this dialect does not override, so the error propagates there too. Hypothesis checked and rejected: a DataFrame cursor'sarraysizeabove 1000 incursor_kwargsmaking the API cursor raise. On 3.x,Connection.cursor()andAioConnection.cursor()ignorearraysizefromcursor_kwargsfor every cursor class (measuredarraysize == 1000forPandasCursor,Cursor,AioPandasCursor, andAioCursorwith 5000 and 0), so the ping cannot fail where the default cursor succeeded.unloadandchunksizeare accepted and ignored byCursor/AioCursor. - The async adapter's
cursor()with no argument passesNonetoAioConnection.cursor(), which picks the default cursor class as before; SQLAlchemy never passed arguments to it on 3.x, since it accepted none. - Data and resource boundaries:
_read_csv()keeps theoutput_locationcheck and wrapsfsspec.open()failures inOperationalErrorwith the same log line. The bytes are read whole, as Polars 1.x did for an fsspec path. Astorage_optionskey in the execute kwargs raisedTypeError(duplicate keyword) before and now reachespl.read_csv(); not a regression, and 3.x never supported it. get_athena_type(): Polars 1.x still describes an UNLOAD map (list of structs) asarray; a struct containing a list changes fromarraytorow, which is release-noted.- Tests:
test_do_ping_uses_api_cursor_without_unloadfails on the base (the defaultdo_ping()opensPandasCursor/AioPandasCursor, whoseexecute()is not patched). The live reproduction on the base and the branch is recorded in the PR body.
Out of scope, recorded only: with Polars 1.36.1, _iter_csv_chunks() can yield no chunks when Polars' file cache panics in a collect_batches() worker (see the PR body). That is pre-existing on 3.x and unaffected by this diff.
| dtype_str = str(dtype).lower() | ||
| if "list" in dtype_str: | ||
| # Nested types are matched by their outer type, not by the types they contain. | ||
| if base_dtype == pl.List: |
There was a problem hiding this comment.
Self-review round two (claims, callers, operations): FINDINGS, repaired in the PR body only; no code change.
Scope: base 5970145, head a2e6899; the PR body, the five commit messages, the changed comments and docstring, and docs/polars.md and docs/sqlalchemy.md on 3.x.
Finding (repaired): the release note said an UNLOAD map was described as string before and map now. With Polars 2.0 on the base, a map whose values are lists or structs was described as array or row, because str(dtype) is Map(String, List(Int32)) or Map(String, Struct({'a': Int32})) and the old substring checks matched it. The note now states all three previous results. The TEST section's account of the Polars 1.36.1 file-cache panic is narrowed to what was observed. The eviction explanation is marked as inferred from the Polars source (EvictionCandidate::update_ttl()), not traced in the run.
Claims checked and held:
**kwargsand the adapter'scursor_kwargs/retry_configproperties come from Fix metadata reflection throttling and reuse listed metadata #777 (commits 7932642, 922b295, and 9d68b59 all belong to Fix metadata reflection throttling and reuse listed metadata #777).Connection._internal_cursor()comes from BREAKING: Reject unknown cursor keyword arguments #1102 (09f7fb3). 3.x has nopyathena.util.override.uv.lock: only thepolarsandpolars-runtime-32entries change. Polars 2.0.0 requires Python >=3.10, so every Python version 3.x supports resolves it for a freshPyAthena[polars]<4install.- The tests 0c6a0b2 adjusts do not exist on 3.x (
TestAthenaPolarsResultSet,test_new_columns_with_schema_overrides,test_csv_read_kwargs_replace_defaults,test_txt_new_columns_with_schema_overrides,test_storage_options_replace_defaults: 0 matches). - The chunked UNLOAD path (
_create_dataframe_iterator()→_iter_parquet_chunks()) never calls_read_parquet_schema(), so it keeps the UNLOAD statement'srowsmetadata; Describe chunked Polars UNLOAD results with the Parquet schema #1012 ("Describe chunked Polars UNLOAD results with the Parquet schema") is the master fix. - Existing callers: Apache Superset's connection test calls
engine.dialect.do_ping(engine.raw_connection())(superset/commands/database/utils.py). That passes a_ConnectionFairy, whosecursor(*args, **kwargs)forwards the cursor class to the DBAPI connection (SQLAlchemy 2.0.46, pool/base.py). SQLAlchemy 1.4's defaultdo_ping()re-raises a non-disconnect error, and this dialect does not overrideis_disconnect(), so error propagation is unchanged. - Operations: the 3.x release gate runs
just tox→just test pyathena(pytest -n 8) on Python 3.10–3.14, so Python 3.10–3.13 keep Polars 1.36.1 as before. - Docs: docs/polars.md still holds (PolarsCursor uses
pl.read_csv/pl.read_parquet, and PyAthena's S3FileSystem for S3 access); docs/sqlalchemy.md does not mention pings. - Evidence: every result in the TEST section was produced at a2e6899, including the base comparisons, which ran on 5970145.
| # When unload is enabled in the default cursor, SELECT 1 is wrapped in UNLOAD | ||
| # which Athena rejects with MISSING_COLUMN_NAME because the column is unnamed. | ||
| # Using the plain cursor here performs the SELECT without the UNLOAD. | ||
| with dbapi_connection.cursor(Cursor) as cursor: # type: ignore[attr-defined] |
There was a problem hiding this comment.
Independent review (relayed): Codex CLI 0.162.1, model gpt-6-astra, reasoning effort max, codex exec -s read-only, session 01a14263-33c1-7631-afb9-db0693f4b040. Static review only: no edits, builds, tests, GitHub, or network access. Snapshot: detached worktree at head a2e6899, base 5970145. The prompt gave the intended behavior without the PR number, description, commit messages, or prior findings. The snapshot and the PR worktree were unchanged afterwards.
Coverage reported: the full 11-file diff (sync/async ping, pool-proxy forwarding, cursor options, CSV error handling/resource release/memory/parser arguments, nested/map metadata, tests and fixtures, dependency/lock consistency), compared against cached SQLAlchemy 1.4.54, 2.0.0, and 2.0.46, and Polars 1.36.1 and 2.0.0.
Result: FINDINGS (1).
P2 — Preserve older SQLAlchemy disconnect handling — pyathena/sqlalchemy/base.py:293. Introduced by this range.
SQLAlchemy 1.4.54 and 2.0.0 handle recognized disconnects insideDefaultDialect.do_ping(). This override replaces that handling with an unconditional execute-and-propagate path.
Concrete scenario: SQLAlchemy 2.0.0,pool_pre_ping=True, a pooled connection with expired assumed-role credentials, and ahandle_errorlistener marking the resulting DBAPI error as a disconnect. Previously, ping returnedFalse, allowing the pool to reconnect and obtain fresh credentials. Now the exception escapes ping and checkout fails. SQLAlchemy 1.4 applications customizingis_disconnect()have the same regression.
The added success-path tests and locked SQLAlchemy 2.0.46 would miss this because that version handles errors outsidedo_ping().
No additional introduced defect identified in the Polars or lock changes. Compatibility at the earliest SQLAlchemy/Polars floors was not fully verified.
Author verification: confirmed. DefaultDialect.do_ping() in SQLAlchemy 1.4.54 and 2.0.0 catches dbapi.Error, consults is_disconnect() and (2.0.0) the handle_error events, and returns False for a disconnect. From 2.0.5 (rel_2_0_5; absent in 2.0.0–2.0.4), the pool calls _do_ping_w_event(), which wraps do_ping() and keeps that handling for an overriding dialect. This dialect does not override is_disconnect(), so the affected case is SQLAlchemy < 2.0.5 with a handle_error listener (or subclass) that marks the ping's error as a disconnect. The same override is on master (#1185, extra floor sqlalchemy>=2.0.0), so master is affected on 2.0.0–2.0.4. Repair pending a maintainer decision; this PR stays Draft.
There was a problem hiding this comment.
Repair: master fixed this finding in #1200 (issue #1199, merged as 1f6864a), and its three commits are ported here as cb34998, e5c4fc4, and 90a9922 (head 90a9922).
do_ping() now passes DefaultDialect.do_ping() a _PingConnection proxy. Its cursor() opens the API cursor with self._connection.cursor(Cursor) (3.x has no _internal_cursor()), and every other attribute is the pinged connection's. On SQLAlchemy 1.4 and 2.0.0–2.0.4, a failed ping goes through SQLAlchemy's own is_disconnect() handling again; from 2.0.5 it goes through _do_ping_w_event() as before.
Repair self-review (range a2e6899..90a9922, base 5970145):
- Implementation behavior: CLEAN. The async adapter's
cursor(Cursor)maps toAioCursor(the ported Read the dialect's own queries through an API cursor and resolve federated absence from information_schema #798 commit). The proxy delegatesschema_name, and the adapter'sdriver_connection(SQLAlchemyAdaptedConnection). On SQLAlchemy 1.4.54, anis_disconnect()override that readsconnection.schema_namemakes the pre-ping reconnect. At a2e6899 the same override raisedOperationalErrorwith 1.4.54 and 2.0.4. Ahandle_error-only listener behaves on 1.4.54 exactly as on the base (it raises), because 1.4'sdo_ping()does not consult listeners. The 3.x test adds only theOperationalErrorimport and the 3.x adapter names. - Claims: CLEAN. The PR body lists the Let DefaultDialect.do_ping() run the ping with the API cursor #1200 row, the extended release note, and which results ran at 90a9922 and which at a2e6899. SQLAlchemy 1.3.24 cannot import
pyathena.sqlalchemyon the base either (ImportError: InternalTraversal), so 1.3 is not a supported runtime in practice; recorded, not changed here.
Validation at 90a9922: just lint passed; -k do_ping 4 passed; both live test_ping_with_unload tests passed.
An independent follow-up review of a2e6899..90a9922 is next.
There was a problem hiding this comment.
Independent follow-up review (relayed): Codex CLI 0.162.1, model gpt-6-astra, reasoning effort max, codex exec -s read-only, session 01a14530-25e6-71e0-86a7-0423d1949335. Static review only. Snapshot: detached worktree at 90a9922; ranges a2e68991..90a9922f and 59701452..90a9922f. The snapshot and the PR worktree were unchanged afterwards.
Coverage reported: sync/async execution, pool-proxied direct calls, delegation and recursion, DataFrame cursor_kwargs, cursor cleanup, typing, and the regression tests, against cached SQLAlchemy 1.4.54, 2.0.0, 2.0.4, 2.0.5, and 2.1.1.
Result: CLEAN.
The previous finding is resolved by delegating to the parent (pyathena/sqlalchemy/base.py:324). SQLAlchemy 1.4: restores the parent's
is_disconnect()handling andFalsereturn for recognized disconnects. 2.0.0–2.0.4: also restores thehandle_errorlistener processing. 2.0.5+: preserves the pool's outer_do_ping_w_event()handling, which receives the original connection.
The proxy forwardscursor(Cursor)through pool proxies without re-entering the dialect or checking out another connection; the async adapter maps it toAioCursor. DataFrame options such asunload,engine, andchunksizedo not activate DataFrame behavior in the API cursor. The guarded type import and string-form cast introduce no SQLAlchemy 1.4 runtime dependency.
The new regression test distinguishes the revision: both cases would propagate the injectedOperationalErroron the previous head instead of returningFalse.
Directdo_ping()calls on 2.0.5+ keep upstream semantics (errors propagate; the pool's outer handler classifies disconnects). Not a regression.
The independent review of this PR is complete at 90a9922.
…I cursor Since #1185, AthenaDialect.do_ping() ran SELECT 1 itself. Before SQLAlchemy 2.0.5, DefaultDialect.do_ping() is also where a failed ping is checked for a disconnect (is_disconnect() and the handle_error listeners), and it returns False so that the pool reconnects; the override made such a checkout raise instead. The override now passes DefaultDialect.do_ping() a connection whose cursor() opens the API cursor, so the ping keeps #1185's cursor and each SQLAlchemy version keeps its own error handling. Closes #1199 (cherry picked from commit 3aae3c7) 3.x resolution, as in the #1185 backport: - pyathena/sqlalchemy/base.py: the namespace's cursor() opens the API cursor with dbapi_connection.cursor(Cursor), since 3.x has no Connection._internal_cursor() (#1102). - tests/pyathena/sqlalchemy/test_base.py: the test uses the 3.x adapter name AsyncAdapt_pyathena_connection, and only DefaultDialect is added to 3.x's imports; the other master tests and imports in the conflict hunks are not part of this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Before SQLAlchemy 2.0.5, DefaultDialect.do_ping() passes the connection it pings to is_disconnect(). It received a namespace with only cursor(), so an is_disconnect() override that reads the connection, such as its schema_name or an adapter's driver_connection, raised AttributeError and the checkout failed instead of reconnecting. The ping now passes a small proxy whose cursor() opens the API cursor and whose other attributes are the pinged connection's. The regression test now fails a real ping: its stand-in for DefaultDialect.do_ping() asks is_disconnect() as SQLAlchemy before 2.0.5 does, so neither the earlier override nor the namespace pass it, and no AWS request is made. (cherry picked from commit 29d61f3) 3.x resolution: - pyathena/sqlalchemy/base.py: _PingConnection.cursor() opens the API cursor with self._connection.cursor(Cursor), since 3.x has no Connection._internal_cursor() (#1102). The async adapter maps Cursor to AioCursor since the #798 commit ported before. - tests/pyathena/sqlalchemy/test_base.py: the test uses the 3.x adapter names AsyncAdapt_pyathena_connection and AsyncAdapt_pyathena_cursor, and OperationalError is added to 3.x's imports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nit__ The repository's docstring rules describe constructor arguments in the Args: section of __init__, not of the class. (cherry picked from commit 3573420) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WHAT
This PR backports three master fixes and one prerequisite commit to the
3.xmaintenance branch, in master merge order.AsyncAdapt_pyathena_connection.cursor()takes an optional cursor class and mapsCursortoAioCursor. It takes no**kwargs, which came with #777 (not on 3.x). The rest of #798 builds on metadata work that 3.x does not have.-x; the author is kept.do_ping()opens the API cursor withdbapi_connection.cursor(Cursor), becauseConnection._internal_cursor()comes from #1102 (a 4.0.0 breaking change). It has no@override, which 3.x does not have. The unit test uses the 3.x adapter namesAsyncAdapt_pyathena_connectionandAsyncAdapt_pyathena_cursor._read_csv()opens the result file withfsspec.open()and PyAthena's storage options and passes the bytes topl.read_csv(). The dev group adds the Polars 2 marker pair;uv.lockchanges onlypolarsandpolars-runtime-32(Python 3.14 resolves 2.0.0, older versions keep 1.36.1). The master tests it adjusts do not exist on 3.x. 6ec3e00 (docs sentence and test comments that 3.x does not have) is skipped as empty. a473e9c:docs/polars.mdis unchanged for the same reason, andtest_nested_type_description_unloadruns withoutchunksizeonly (see below). 22de454 applies unchanged.do_ping()passesDefaultDialect.do_ping()a_PingConnectionproxy whosecursor()opens the API cursor and whose other attributes are the pinged connection's, so SQLAlchemy keeps its own handling of a failed ping. Before 2.0.5 that handling is insidedo_ping()and callsis_disconnect()with the connection, which matters on 3.x for SQLAlchemy 1.4 as well. The proxy'scursor()usesself._connection.cursor(Cursor)instead of_internal_cursor(Cursor), and the test uses the 3.x adapter names and adds theOperationalErrorimport.Each commit message records its conflict resolution.
Release notes
unload=true, the connection ping (pool_pre_ping, and connection tests such as Apache Superset's) no longer fails withMISSING_COLUMN_NAME. The ping runsSELECT 1through a plainCursorinstead of the connection's default cursor, while SQLAlchemy keeps its own handling of a failed ping, includingis_disconnect()before SQLAlchemy 2.0.5 (fix: run ping with plain cursor #1185 by @greg-om, reported in ping not working with UNLOAD=true #1189; SQLAlchemy ping no longer detects disconnects before SQLAlchemy 2.0.5 #1199/Let DefaultDialect.do_ping() run the ping with the API cursor #1200).PolarsCursorandAioPolarsCursorwork with Polars 2.0 (PolarsCursor fails with Polars 2.0 #1168). Before, every query read from a CSV result file withoutchunksizefailed withOperationalError(invalid value for 'connection'). The CSV result file is now read through PyAthena's S3 filesystem and passed to Polars as bytes, with Polars 1.x as well.Map, which the cursor description now reports asmap. Before, it wasstring, orarray/rowwhen the map's values were lists or structs. With Polars 1.x, a map staysarray. A struct containing a list is now described asrowinstead ofarray.PolarsCursordescribes its columns as the UNLOAD statement'srows bigintcolumn. Master fixed this in Describe chunked Polars UNLOAD results with the Parquet schema #1012, which is not on 3.x.WHY
#1185 fixes #1189 on master; 3.x has the same defect, because its dialect uses SQLAlchemy's default
do_ping(), which opens the connection's default cursor withcursor_kwargs(includingunload).3.x's
polarsextra requirespolars>=1.0.0without an upper bound, so a fresh install ofPyAthena[polars]<4resolves Polars 2.0.0, and 3.x passedconnectionin theread_csv()storage options just as master did before #1169.#1200 fixes #1199, a defect of #1185's override found by the independent review of this PR, and is needed with it. The maintainer chose #1185 and #1169 from the master changes after v3.39.0; the others are 4.x-only, performance work, or need prerequisites that 3.x does not have.
TEST
Tested commits: 90a9922 (head) for the #1200 port; a2e6899, before the #1200 commits, for the rest.
At 90a9922:
just lint(Python 3.14): passed.tests/pyathena/sqlalchemy/test_base.py -k do_ping(offline): 4 passed, includingtest_do_ping_keeps_default_dialect_pingfor the sync and async dialects.uv run --isolated --with sqlalchemy==<version>), with anis_disconnect()override that readsconnection.schema_name: the pre-ping reconnects with SQLAlchemy 1.4.54, 2.0.4, and 2.0.46. At a2e6899 it raisedOperationalErrorwith 1.4.54 and 2.0.4. With ahandle_errorlistener instead, 2.0.4 and 2.0.46 reconnect. 1.4.54 raises on this branch and on the base alike, since 1.4'sdo_ping()does not consult the listeners. SQLAlchemy 1.3.24 cannot importpyathena.sqlalchemyon the base either (ImportError: InternalTraversal).test_ping_with_unloadtests (live AWS, Python 3.14): 2 passed;do_ping()on a liveawsathena+pandasengine withunload=truereturnsTrue.At a2e6899:
just lint(Python 3.14): passed.uv run --env-file .env pytest -n 4 tests/pyathena/polars tests/pyathena/aio/polarsplustests/pyathena/arrow/test_cursor.py::TestArrowCursor::test_complex_unload_as_polars:test_get_athena_type_map, since Polars 1.x has noMaptype), withtest_iter_chunksfailing in 2 of 2 runs.The failure is a panic inside Polars 1.36.1 (
polars-io/src/file_cache/entry.rs:206,No such file or directory), whichcollect_batches()swallows, so the chunkedscan_csv()read yields no chunks.The panic means the downloaded data file was gone right after the download. A likely cause is the cache shared by the xdist workers in the temporary directory: Polars 1.36.1 evicts an entry whose metadata file is missing or unreadable (
EvictionCandidate::update_ttl()sets its TTL to 0), so one worker's eviction can delete a file that another is still writing. This was inferred from the Polars source, not traced in the run.This path (
_iter_csv_chunks()) is not changed here. The test passes alone, the wholetest_cursor.pypasses serially (55 passed), and the same parallel run passes (105 passed) with the two new tests (test_util.pyandtest_nested_type_description_unload) deselected; the baseorigin/3.xpassed the same parallel run 3 of 3 times.The release gate runs Python 3.10–3.13 with this Polars release and
-n 8, so this race can fail a run there regardless of this PR.tests/pyathena/sqlalchemyandtests/pyathena/aio/sqlalchemy, live AWS, Python 3.14: 521 passed, including the newtest_do_ping_uses_api_cursor_without_unloadand bothtest_ping_with_unloadtests.origin/3.x(5970145), Python 3.14:uv run --with polars==2.0.0),TestPolarsCursor::test_fetchonefails withOperationalError: invalid value for 'connection': '<pyathena.connection.Connection object at ...>' (expected str).AthenaDialect.do_ping()on anawsathena+pandasengine withunload=trueraisesOperationalError: MISSING_COLUMN_NAME: line 1:1: Column name not specified at position 1; on this branch it returnsTrue.uv lock --check: passed.Not run locally: the full
just test pyathenasuite, the SQLAlchemy compliance suites (just test sqla,just test sqla-async), and Python 3.10–3.12; the AWS CI of this PR and the release gate's full matrix cover them.🤖 Generated with Claude Code