Skip to content

Commit 34281bf

Browse files
Drop Python 3.9 support after EOL
Python 3.9 reached End-of-Life on October 31, 2025 and no longer receives security updates or bug fixes from the Python core team. Changes: - Update requires-python from ">=3.9" to ">=3.10" in pyproject.toml - Remove Python 3.9 classifier from pyproject.toml - Remove Python 3.9 from CI test matrix - Remove py39 from tox envlist - Update ruff target-version from py39 to py310 - Update mypy python_version from 3.9 to "3.10" - Update README.rst and docs/introduction.rst to reflect Python 3.10-3.13 support - Add strict=False to zip() calls (Python 3.10+ requirement) Closes #617 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 0d2671c commit 34281bf

11 files changed

Lines changed: 29 additions & 155 deletions

File tree

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
strategy:
2323
fail-fast: false
2424
matrix:
25-
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
25+
python-version: ['3.10', '3.11', '3.12', '3.13']
2626

2727
steps:
2828
- name: Checkout

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Requirements
4040

4141
* Python
4242

43-
- CPython 3.9 3.10, 3.11 3.12 3.13
43+
- CPython 3.10, 3.11, 3.12, 3.13
4444

4545
.. _installation:
4646

docs/introduction.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Requirements
1010

1111
* Python
1212

13-
- CPython 3.9 3.10, 3.11 3.12 3.13
13+
- CPython 3.10, 3.11, 3.12, 3.13
1414

1515
.. _installation:
1616

pyathena/arrow/result_set.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,8 @@ def _fetch(self) -> None:
225225
dict_rows = rows.to_pydict()
226226
column_names = dict_rows.keys()
227227
processed_rows = [
228-
tuple(self.converters[k](v) for k, v in zip(column_names, row))
229-
for row in zip(*dict_rows.values())
228+
tuple(self.converters[k](v) for k, v in zip(column_names, row, strict=False))
229+
for row in zip(*dict_rows.values(), strict=False)
230230
]
231231
self._rows.extend(processed_rows)
232232

pyathena/pandas/util.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,15 @@ def to_sql(
211211
for keys, group in df.groupby(by=partitions, observed=True):
212212
keys = keys if isinstance(keys, tuple) else (keys,)
213213
group = group.drop(partitions, axis=1)
214-
partition_prefix = "/".join([f"{key}={val}" for key, val in zip(partitions, keys)])
214+
partition_prefix = "/".join(
215+
[f"{key}={val}" for key, val in zip(partitions, keys, strict=False)]
216+
)
217+
partition_condition = ", ".join(
218+
[f"`{key}` = '{val}'" for key, val in zip(partitions, keys, strict=False)]
219+
)
215220
partition_prefixes.append(
216221
(
217-
", ".join([f"`{key}` = '{val}'" for key, val in zip(partitions, keys)]),
222+
partition_condition,
218223
f"{location}{partition_prefix}/",
219224
)
220225
)

pyathena/result_set.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ def _get_rows(
396396
tuple(
397397
[
398398
self._converter.convert(meta.get("Type"), row.get("VarCharValue"))
399-
for meta, row in zip(metadata, rows[i].get("Data", []))
399+
for meta, row in zip(metadata, rows[i].get("Data", []), strict=False)
400400
]
401401
)
402402
for i in range(offset, len(rows))
@@ -420,7 +420,7 @@ def _process_rows(self, response: Dict[str, Any]) -> None:
420420
def _is_first_row_column_labels(self, rows: List[Dict[str, Any]]) -> bool:
421421
first_row_data = rows[0].get("Data", [])
422422
metadata = cast(Tuple[Any, Any], self._metadata)
423-
for meta, data in zip(metadata, first_row_data):
423+
for meta, data in zip(metadata, first_row_data, strict=False):
424424
if meta.get("Name") != data.get("VarCharValue"):
425425
return False
426426
return True
@@ -496,7 +496,7 @@ def _get_rows(
496496
meta.get("Name"),
497497
self._converter.convert(meta.get("Type"), row.get("VarCharValue")),
498498
)
499-
for meta, row in zip(metadata, rows[i].get("Data", []))
499+
for meta, row in zip(metadata, rows[i].get("Data", []), strict=False)
500500
]
501501
)
502502
for i in range(offset, len(rows))

pyproject.toml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ dependencies = [
1111
"fsspec",
1212
"python-dateutil",
1313
]
14-
requires-python = ">=3.9"
14+
requires-python = ">=3.10"
1515
readme = "README.rst"
1616
license = {file = "LICENSE"}
1717
classifiers = [
@@ -21,7 +21,6 @@ classifiers = [
2121
"Operating System :: OS Independent",
2222
"Topic :: Database :: Front-Ends",
2323
"Programming Language :: Python :: 3",
24-
"Programming Language :: Python :: 3.9",
2524
"Programming Language :: Python :: 3.10",
2625
"Programming Language :: Python :: 3.11",
2726
"Programming Language :: Python :: 3.12",
@@ -107,7 +106,7 @@ exclude = [
107106
".venv",
108107
".tox",
109108
]
110-
target-version = "py39"
109+
target-version = "py310"
111110

112111
[tool.ruff.lint]
113112
# https://docs.astral.sh/ruff/rules/
@@ -125,7 +124,7 @@ select = [
125124
]
126125

127126
[tool.mypy]
128-
python_version = 3.9
127+
python_version = "3.10"
129128
follow_imports = "silent"
130129
disallow_any_generics = true
131130
strict_optional = true
@@ -149,11 +148,10 @@ exclude = [
149148
legacy_tox_ini = """
150149
[tox]
151150
isolated_build = true
152-
envlist = py{39,310,311,312,313}
151+
envlist = py{310,311,312,313}
153152
154153
[gh-actions]
155154
python =
156-
3.9: py39
157155
3.10: py310
158156
3.11: py311
159157
3.12: py312

tests/pyathena/arrow/test_async_cursor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ def test_as_arrow(self, async_arrow_cursor):
226226
table = future.result().as_arrow()
227227
assert table.shape[0] == 1
228228
assert table.shape[1] == 1
229-
assert list(zip(*table.to_pydict().values())) == [(1,)]
229+
assert list(zip(*table.to_pydict().values(), strict=False)) == [(1,)]
230230

231231
@pytest.mark.parametrize(
232232
"async_arrow_cursor",
@@ -238,7 +238,7 @@ def test_many_as_arrow(self, async_arrow_cursor):
238238
table = future.result().as_arrow()
239239
assert table.shape[0] == 10000
240240
assert table.shape[1] == 1
241-
assert list(zip(*table.to_pydict().values())) == [(i,) for i in range(10000)]
241+
assert list(zip(*table.to_pydict().values(), strict=False)) == [(i,) for i in range(10000)]
242242

243243
def test_cancel(self, async_arrow_cursor):
244244
query_id, future = async_arrow_cursor.execute(

tests/pyathena/arrow/test_cursor.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ def test_as_arrow(self, arrow_cursor):
259259
table = arrow_cursor.execute("SELECT * FROM one_row").as_arrow()
260260
assert table.shape[0] == 1
261261
assert table.shape[1] == 1
262-
assert list(zip(*table.to_pydict().values())) == [(1,)]
262+
assert list(zip(*table.to_pydict().values(), strict=False)) == [(1,)]
263263

264264
@pytest.mark.parametrize(
265265
"arrow_cursor",
@@ -270,7 +270,7 @@ def test_many_as_arrow(self, arrow_cursor):
270270
table = arrow_cursor.execute("SELECT * FROM many_rows").as_arrow()
271271
assert table.shape[0] == 10000
272272
assert table.shape[1] == 1
273-
assert list(zip(*table.to_pydict().values())) == [(i,) for i in range(10000)]
273+
assert list(zip(*table.to_pydict().values(), strict=False)) == [(i,) for i in range(10000)]
274274

275275
def test_complex_as_arrow(self, arrow_cursor):
276276
table = arrow_cursor.execute(
@@ -323,7 +323,7 @@ def test_complex_as_arrow(self, arrow_cursor):
323323
pa.field("col_decimal", pa.string()),
324324
]
325325
)
326-
assert list(zip(*table.to_pydict().values())) == [
326+
assert list(zip(*table.to_pydict().values(), strict=False)) == [
327327
(
328328
True,
329329
127,
@@ -406,7 +406,7 @@ def test_complex_unload_as_arrow(self, arrow_cursor):
406406
pa.field("col_decimal", pa.decimal128(10, 1)),
407407
]
408408
)
409-
assert list(zip(*table.to_pydict().values())) == [
409+
assert list(zip(*table.to_pydict().values(), strict=False)) == [
410410
(
411411
True,
412412
127,

tests/pyathena/pandas/test_cursor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1582,5 +1582,5 @@ def test_pandas_cursor_iter_chunks_consistency(self, pandas_cursor):
15821582
assert len(chunks_via_method) == len(chunks_via_direct)
15831583

15841584
# Each corresponding chunk should be identical
1585-
for chunk1, chunk2 in zip(chunks_via_method, chunks_via_direct):
1585+
for chunk1, chunk2 in zip(chunks_via_method, chunks_via_direct, strict=False):
15861586
pd.testing.assert_frame_equal(chunk1, chunk2)

0 commit comments

Comments
 (0)