Drop Python 3.10 support - #884
Conversation
Python 3.10 reaches end of life on 2026-10-31, and 4.0.0 drops it so a later 4.x minor release does not have to. - Require Python 3.11 and remove the 3.10 classifier, tox environment, and Test workflow version. - Remove the tomli fallback from the license header check and the _parse_datetime fallback from ARRAY timestamp decoding; Python 3.11's datetime.fromisoformat accepts any number of fractional digits. - Target Python 3.11 in ruff and apply its UP017 fixes (datetime.UTC) and the RUF100 fix for the PERF203 suppression, which no longer applies. - Regenerate uv.lock, which no longer resolves pandas 2.3.3. Closes #864 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| return Decimal(value) if type_.asdecimal else float(value) | ||
| if isinstance(type_, (types.DateTime, AthenaTimestamp)): | ||
| return value if isinstance(value, datetime) else _decode_datetime(value) | ||
| return value if isinstance(value, datetime) else datetime.fromisoformat(value) |
There was a problem hiding this comment.
Self-review round one (implementation behavior): CLEAN
Scope: base a86a180ebbe5e18920cd75802ea5350ce8d33f10 .. head 6aeb2ae9967f378478737cf6bcd7113c3f4342b6, all 21 changed files.
Covered:
- ARRAY timestamp decoding: on Python 3.11+ the old helper already returned
datetime.fromisoformat(value)whenever it succeeded, so inlining only removes the branch that ran whenfromisoformatraised. Checked on 3.11.11 and 3.14.5: text with 0–7, 9 and 12 fractional digits and the ISOTform parses and truncates to microseconds, like_parse_datetime. Text that neither accepts (e.g. a trailingUTC) still raisesValueError._parse_datetimekeeps its converter callers (pyathena/converter.py). timezone.utc→UTC(UP017, 31 sites in library, scripts, tests, docs/conf.py):datetime.UTC is datetime.timezone.utcisTrue, so the cache-expiration comparison inpyathena/common.py/pyathena/aio/common.pyand the UNLOAD location date inpyathena/formatter.pyare unchanged.- RUF100 in
pyathena/pandas/result_set.py:395: only a stalenoqa: PERF203(the rule targets Python < 3.11) is removed. scripts/check_license_headers.py:tomllibis stdlib from 3.11; no othertomliusers remain.- Test workflow:
PYTHON_VERSIONSstill matches the classifiers; the PR/schedule path (last) still selects 3.14, and the dispatch path now rejects 3.10 with the existing error. - No other
sys.version_infogates or 3.10 comments remain (git grep).uv.lockhasrequires-python = ">=3.11", and only 3.10-only resolutions were removed.
Tests: the existing test_array_result_conversion cases (1- and 9-digit fractions, T form) pass offline on 3.11 and 3.14. The 12-digit case was only checked by hand.
Limitation: the AWS suites have not been run for this head.
| "python-dateutil", | ||
| ] | ||
| requires-python = ">=3.10" | ||
| requires-python = ">=3.11" |
There was a problem hiding this comment.
Self-review round two (claims, callers, operations): FINDINGS (PR description only, corrected)
Scope: base a86a180ebbe5e18920cd75802ea5350ce8d33f10 .. head 6aeb2ae9967f378478737cf6bcd7113c3f4342b6, the full PR body, commit message, and changed docs.
Claims checked:
- "Python 3.10 reaches end of life on 2026-10-31": PEP 619 only gives "approximately October 2026". The PR body now says "in October 2026 (PEP 619)"; the commit message keeps the issue's date.
- The
uv.lockremoval list, checked by diffing the locked package/version pairs: backports-asyncio-runner, exceptiongroup, markdown-it-py 3.0.0, myst-parser 4.0.1, networkx 3.4.2, numpy 2.2.6, pandas 2.3.3, pytz, sphinx 7.4.7, and sphinx-design 0.6.1. sphinx-design was missing from the description; added.tomlistays in the lock as a transitive dependency forpython_full_version <= '3.11', not as our dev dependency. - "pandas 3.0 requires Python 3.11": pandas 3.0.0 release notes and the locked 3.0.6 metadata (
Requires-Python >=3.11). - "
fromisoformataccepts any number of fractional digits" (commit): matches the Python 3.11datetime.fromisoformatdocs and the 3.11.11/3.14.5 run recorded in round one. - "UP017 at 31 sites":
ruff check --statisticsreported 31 UP017 and 1 RUF100 before--fix.
Existing callers: Python 3.10 installers resolve to an earlier release through Requires-Python. pyathena.sqlalchemy.array._decode_datetime was added after v3.36.0 (git tag --contains 9a5ca5c is empty), so no released API disappears.
Operations: docs/testing.md stays accurate ("every supported Python version", dispatch example 3.11,3.14). The Release and full-dispatch runs now start 4 matrix jobs instead of 5, while PR/schedule runs still use 3.14 only, so AWS usage drops and nothing is added.
Evidence limits: all validation so far is local and offline. AWS behavior on 3.11 will come from a Test workflow dispatch with python-versions=3.11 before Ready.
| # Every supported version, oldest first; keep in sync with the | ||
| # pyproject.toml classifiers. | ||
| PYTHON_VERSIONS: '["3.10", "3.11", "3.12", "3.13", "3.14"]' | ||
| PYTHON_VERSIONS: '["3.11", "3.12", "3.13", "3.14"]' |
There was a problem hiding this comment.
Independent review (relayed): CLEAN (static)
Reviewer: OpenAI Codex CLI 0.157.1 (codex exec, model gpt-6-sol, sandbox read-only, ephemeral session 01a0e8db-e3d6-70d1-8e69-5b348cd195a7), which did not author the change.
Scope: base a86a180ebbe5e18920cd75802ea5350ce8d33f10 .. head 6aeb2ae9967f378478737cf6bcd7113c3f4342b6, in a clean detached snapshot without .env. The prompt omitted the PR number, description, commit message, and self-review findings. The reviewer was not allowed to edit, build, test, use the network, or write to GitHub.
Reviewer's result:
Surfaces covered: The specified diff; Python requirements, classifiers, Ruff and tox configuration; CI version selection for pull requests, schedules, dispatches, and releases; ARRAY timestamp decoding and its tests; UTC rewrites; repository-wide Python 3.10 references; docs, benchmarks, scripts, and
uv.lock.Verdict: CLEAN. I found no actionable regression or pre-existing issue in those surfaces. The remaining
cp310-abi3lock entries are wheels usable on supported Python versions, andtomliremains a transitive dependency on Python 3.11. This was a static review; no builds or tests were run.
After the review, the snapshot and PR worktree were unchanged (HEAD 6aeb2ae, clean status).
WHAT
Drop Python 3.10 support for 4.0.0.
requires-python = ">=3.11"; remove the 3.10 classifier, thepy310tox environment and[gh-actions]mapping, and 3.10 from the Test workflow's supported versions (PYTHON_VERSIONS), which the dispatch and Release runs use.tomlifallback inscripts/check_license_headers.pyand thetomlidev dependency;_parse_datetimefallback in ARRAY timestamp decoding (pyathena/sqlalchemy/array.py); the helper is inlined asdatetime.fromisoformat.target-version = "py311", with the fixes it now reports: UP017 (timezone.utc→datetime.UTC, 31 sites) and RUF100 (aPERF203suppression that no longer applies, since the rule only targets Python < 3.11).README.mdanddocs/introduction.mdlist CPython 3.11–3.14; the benchmark workspace member requires Python 3.11 and its packaging test expectsRequires-Python: >=3.11.uv.lockregenerated: the 3.10-only resolutions are gone (pandas 2.3.3, numpy 2.2.6, pytz, exceptiongroup, backports-asyncio-runner, and older sphinx/sphinx-design/myst-parser/networkx/markdown-it-py).Release note (breaking): Python 3.10 is no longer supported. Users on Python 3.10 can stay on the 3.x maintenance branch.
WHY
Closes #864. Python 3.10 reaches end of life in October 2026 (PEP 619), and dropping it in the major release avoids a breaking change in a later 4.x minor release. It also unblocks #852: pandas 3.0 requires Python 3.11.
Python 3.11's
datetime.fromisoformataccepts any number of fractional digits, truncating beyond microseconds as_parse_datetimedoes, so the fallback has no remaining input.TEST
Tested commit: 6aeb2ae
just format,just lint(license headers, ruff, format, mypy, cfn-lint): pass.just docs lint: pass.actionlint .github/workflows/test.yaml: pass.Script tests (
uv run --locked python -m pytest scripts/tests/): 108 passed.just benchmark test: 108 passed, 1 skipped (packaging); the packaging test run separately afteruv buildwithBENCHMARK_DIST_DIR: 1 passed. The built wheel metadata hasRequires-Python: >=3.11and classifiers 3.11–3.14.datetime.fromisoformaton Python 3.11.11 and 3.14.5 with Athena TIMESTAMP text of 0–7, 9, and 12 fractional digits and the ISOTform: all parsed, truncated to microseconds.tests/pyathena/sqlalchemy/test_array.py::…::test_array_result_conversion(includes 1- and 9-digit fractions and theTform) on Python 3.11 and 3.14 with--noconftest(offline): 12 passed each.AWS on the new minimum: Test workflow dispatch 36452239325 on
6aeb2aewithpython-versions=3.11:test,test-sqla, andtest-sqla-asyncpassed.Not run locally: the AWS suites. The Ready PR run covers the newest Python (3.14).
🤖 Generated with Claude Code