Skip to content

Upgrade the locked data libraries and type-check per interpreter - #822

Merged
laughingman7743 merged 3 commits into
masterfrom
chore/644-upgrade-lock
Sep 25, 2026
Merged

laughingman7743 merged 3 commits into
masterfrom
chore/644-upgrade-lock

Conversation

@laughingman7743

@laughingman7743 laughingman7743 commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

WHAT

  • Upgrade the locked runtime data libraries in 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.
  • Remove python_version = "3.10" from [tool.mypy], so mypy targets the running interpreter.
  • PandasCursor tests derive the expected string dtype and NULL value from the installed pandas instead of hard-coding pandas 2 results.

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 type statement.
With python_version = "3.10", mypy (1.14 and 2.3 alike) stops with Type statement is only supported in Python 3.12 and greater in numpy/__init__.pyi.
Each tox environment runs just test pyathena, which runs just lint on 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 str dtype for string columns and represents NULL as NaN, where pandas 2 used object columns and None.
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_/None on pandas 2.3.3, str/nan on pandas 3.0.6), and the UNLOAD NULL comparison uses np.testing.assert_equal so 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 lint and just 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.
  • First CI run (b2f0a8c): sqla and sqla-async passed on Python 3.10–3.14 and pyathena passed on 3.10; pyathena on 3.11–3.14 had the same 9 PandasCursor failures (1743 passed), fixed in 9af4a07.
  • CI on 9af4a07: all 18 checks passed (pyathena, sqla, sqla-async on Python 3.10–3.14 against AWS).
  • docs/null_handling.md: just docs lint and just docs build passed. The default behavior and both documented ways to get None were run against Athena with PandasCursor(unload=True) and pandas 3.0.6: default str/nan; converted and opted-out results return (2, None, 'null_value') from fetchall().
  • Independent review (Codex, static): findings on the documented NULL value were addressed by the docs change; the follow-up review of e3aa3df was CLEAN.
  • CI on e3aa3df: all 20 checks passed, including pyathena, sqla, and sqla-async on Python 3.10–3.14 against AWS, and Docs Lint.

🤖 Generated with Claude Code

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>
Comment thread uv.lock
@@ -960,28 +1129,30 @@ wheels = [

[[package]]
name = "polars"
version = "1.36.1"
version = "1.44.2"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Comment thread pyproject.toml
]

[tool.mypy]
python_version = "3.10"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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>
Comment thread docs/null_handling.md
| `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 |

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

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.

1 participant