Upgrade the locked data libraries and type-check per interpreter - #822
Conversation
Upgrade polars, pandas, pyarrow, numpy, fsspec, boto3, botocore, and s3transfer in uv.lock to their current releases. The benchmark shares this lock, so its measurements otherwise use stale library versions. numpy 2.5 ships stubs that use Python 3.12 syntax, which mypy rejects when python_version is pinned to 3.10. Let mypy target the running interpreter instead; CI runs lint on every supported Python version, so the 3.10 job still checks 3.10 compatibility. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| @@ -960,28 +1129,30 @@ wheels = [ | |||
|
|
|||
| [[package]] | |||
| name = "polars" | |||
| version = "1.36.1" | |||
| version = "1.44.2" | |||
There was a problem hiding this comment.
Self-review round 1 (behavior and implementation) — CLEAN.
Scope: base 0c26c7ece28b67a06d538e69f9732df3ea1eba42, head b2f0a8c3bb058899109b4a5a4c4ca3090f43eb02; files uv.lock, pyproject.toml.
The lock diff changes versions for exactly the nine requested packages (boto3, botocore, fsspec, numpy, pandas, polars, polars-runtime-32, pyarrow, s3transfer); mypy, SQLAlchemy, Sphinx, and pytest plugins are unchanged. pyproject.toml dependency ranges are unchanged, so installed users see no new constraint. Fresh locked environments for Python 3.10, 3.11, and 3.14 resolve numpy/pandas to 2.2.6/2.3.3, 2.4.6/3.0.6, and 2.5.3/3.0.6 with polars 1.44.2 and pyarrow 25.0.1, and mypy reports 0 issues in each.
| ] | ||
|
|
||
| [tool.mypy] | ||
| python_version = "3.10" |
There was a problem hiding this comment.
Self-review round 2 (claims, compatibility, operations) — CLEAN.
Claims checked: numpy 2.5.3 is resolved only for Python >= 3.12 (uv.lock markers), and its numpy/__init__.pyi:737 uses a type statement that mypy 1.14.0 and 2.3.1 both reject with python_version = "3.10"; a follow_imports = "skip" override for numpy did not avoid it because pandas imports numpy. Each tox pyathena environment runs just test pyathena, whose lint dependency runs mypy on that environment's interpreter, so the 3.10 CI job still type-checks for 3.10.
Operational consequence: a local just lint on Python 3.13 now checks 3.13 semantics only; 3.10-only incompatibilities surface in the 3.10 CI job. The AWS integration suites on all versions are running in CI and remain required before Ready.
pandas 3 infers its "str" dtype for string columns and represents NULL as NaN, while pandas 2 uses object columns with None. PyAthena leaves string dtypes to pandas, so derive the expected dtype and NULL value from the installed pandas instead of hard-coding pandas 2 results. This covers string, varchar, and unconverted array, map, and struct text in CSV results, and NULL strings in UNLOAD results. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
||
| # pandas 3 infers its "str" dtype for strings, which represents NULL as NaN; pandas 2 uses | ||
| # object columns with None. | ||
| STRING_TYPE = pd.Series(["a"]).dtype.type |
There was a problem hiding this comment.
Self-review of repair 9af4a07 (rounds 1 and 2, repair scope: git range-diff b2f0a8c → 9af4a07, test files only) — CLEAN.
Round 1 (behavior): dtype assertions stay exact; only the expected string type and NULL value come from the installed pandas. On pandas 2.3.3 the constants evaluate to numpy.object_ and None, identical to the previous hard-coded values; on pandas 3.0.6 they are str and nan. The UNLOAD NULL comparisons use np.testing.assert_equal, which treats NaN as equal, and still distinguish "" from NULL. No expectation became looser.
Round 2 (claims): the PR body's statements were checked against runs: CSV col_string, col_varchar, and unconverted col_array/col_map/col_struct text are str on pandas 3 (local Python 3.13 AWS run: the 3 test_complex_as_pandas cases pass after including indexes 13/15/17); UNLOAD NULL strings are NaN (4 null-string cases pass). The other 1743 pyathena tests and all sqla/sqla-async jobs passed in the first CI run. CI on 9af4a07 is still required before Ready.
With pandas 3, string columns use the str dtype and represent NULL as NaN; pandas 2 uses object columns and None. Show both in the UNLOAD example and the summary table, and describe how to get None by converting the columns or turning off future.infer_string. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| | `DictCursor` | Athena API | `''` | `None` | ✅ Yes | | ||
| | `PandasCursor` | CSV file | `NaN` | `NaN` | ❌ No | | ||
| | `PandasCursor` + unload | Parquet file | `''` | `None` | ✅ Yes | | ||
| | `PandasCursor` + unload | Parquet file | `''` | `NaN` (pandas 3) or `None` (pandas 2) | ✅ Yes | |
There was a problem hiding this comment.
Independent review (relayed) — reviewer: Codex CLI 0.156.0, model gpt-6-sol, codex exec --sandbox read-only, session 01a0d905-5e80-77b2-9867-1f8077490dbb; static source review (no tests run); base 0c26c7ece28b67a06d538e69f9732df3ea1eba42, head 9af4a07642e4bc2181fea375fcdc642fab4fe19d; verdict FINDINGS (1). The review snapshot was unchanged afterward.
P2 (uv.lock:1256, tests/pyathena/pandas/test_cursor.py:1204 at 9af4a07): "The new lock selects pandas 3 on Python 3.11+, and the revised test accepts NaN for a SQL NULL returned by PandasCursor with unload=True. [...] An existing caller that checks cursor.fetchone()[0] is None for a nullable string will get a false result in the newly locked environment. The documentation still shows that result as None in docs/null_handling.md:180. Introduced by this diff for locked development and benchmark environments; the pre-existing dependency range already allowed users to install pandas 3 independently. Preserve the tuple API's NULL value or document and test the changed contract."
Verified; resolved by documenting in e3aa3df (maintainer decision: follow pandas). Keeping None in PyAthena would need either object conversion of string columns (a copy and a memory regression for large results), a process-wide pandas option inside a thread-pool path, or NaN-to-None mapping that cannot tell NULL from a real double NaN. This table row, the UNLOAD example, and the following text now state the pandas 2 and pandas 3 representations, that the tuple methods return the same values, and two ways to get None: df.astype({...: object}).where(df.notna(), None) and pd.set_option("future.infer_string", False) (pandas 2.1+). Both were checked against Athena (PandasCursor, unload=True, pandas 3.0.6): default str/nan; converted and opted-out results return (2, None, 'null_value') from fetchall(). The tests already assert the installed pandas representation.
Self-review of the repair (rounds 1 and 2; scope 9af4a07..e3aa3df, docs only): CLEAN. just docs lint and just docs build pass; the CSV rows of the table (NaN for both) and the binary NULL statements are unchanged and still hold on pandas 3. An independent follow-up is pending.
There was a problem hiding this comment.
Independent follow-up (relayed) — reviewer: Codex CLI 0.156.0, model gpt-6-sol, codex exec --sandbox read-only, session 01a0d92e-4e60-71f0-adfb-788682133f60; static review of the repair 9af4a07..e3aa3df and the contracts it affects (PandasCursor CSV and Parquet read paths, fetch behavior, pandas cursor tests for pandas 2 and 3). Verdict: CLEAN — "The documented default NULL behavior matches the reviewed paths, and both documented ways to obtain None are consistent with them." No tests were run by the reviewer; the snapshot was unchanged afterward.
WHAT
uv.lock: polars 1.36.1 → 1.44.2, pyarrow 23.0.1 → 25.0.1, pandas 2.2.3/2.3.3 → 2.3.3 (Python 3.10) / 3.0.6 (Python 3.11+), numpy → 2.2.6 / 2.4.6 / 2.5.3 by Python version, fsspec 2024.12.0 → 2026.9.0, boto3/botocore 1.43.14 → 1.43.102, s3transfer 0.17.0 → 0.19.2.python_version = "3.10"from[tool.mypy], so mypy targets the running interpreter.Other locked packages (mypy, SQLAlchemy, Sphinx, pytest plugins, and so on) are intentionally unchanged.
WHY
Refs #644.
The benchmark is a workspace member and shares the root lock, so the large-data measurements on master used polars 1.36.1, pandas 2.2.3, and pyarrow 23.0.1 while users installing PyAthena today get the current releases.
The benchmark must be rerun on current library versions; for example, #820 was observed with polars 1.36.1, whose
collect_batches()implementation was replaced by a native iterator in 1.44.2.numpy 2.5 is resolved for Python 3.12+ and its stubs use the Python 3.12
typestatement.With
python_version = "3.10", mypy (1.14 and 2.3 alike) stops withType statement is only supported in Python 3.12 and greaterinnumpy/__init__.pyi.Each tox environment runs
just test pyathena, which runsjust linton its own interpreter, so the Python 3.10 job still type-checks the code for 3.10.pandas 3.0 is now resolved for Python 3.11+. It infers its
strdtype for string columns and represents NULL as NaN, where pandas 2 usedobjectcolumns andNone.PyAthena does not set string dtypes, so PandasCursor results follow the installed pandas: string, varchar, and unconverted array/map/struct text in CSV results become
str, and NULL strings in UNLOAD results become NaN (still distinct from"").Nine PandasCursor/AsyncPandasCursor tests hard-coded the pandas 2 results and failed on Python 3.11–3.14 in the first CI run; they now take the expected dtype and NULL from the installed pandas (
object_/Noneon pandas 2.3.3,str/nanon pandas 3.0.6), and the UNLOAD NULL comparison usesnp.testing.assert_equalso NaN compares equal.TEST
Tested commit: e3aa3df
just lint(Python 3.13): passed; mypy 0 issues in 92 files.uv run mypy .in fresh locked environments for Python 3.10, 3.11, and 3.14 (numpy/pandas/polars/pyarrow = 2.2.6/2.3.3/1.44.2/25.0.1, 2.4.6/3.0.6/1.44.2/25.0.1, 2.5.3/3.0.6/1.44.2/25.0.1): 0 issues each.just benchmark lintandjust benchmark test: 80 passed, 1 skipped; the benchmark environment resolves pandas 3.0.6, polars 1.44.2, pyarrow 25.0.1, numpy 2.5.3.docs/null_handling.md:just docs lintandjust docs buildpassed. The default behavior and both documented ways to getNonewere run against Athena withPandasCursor(unload=True)and pandas 3.0.6: defaultstr/nan; converted and opted-out results return(2, None, 'null_value')fromfetchall().🤖 Generated with Claude Code