Skip to content

Commit 315d539

Browse files
authored
Drop Python 3.9 support and test against 3.14 (#343)
* Drop Python 3.9 support and test against 3.14 Python 3.9 reaches end-of-life in October 2025, with Python 3.14 released in the same month. * Update dependencies * Fix new Ruff lints
1 parent 85258c3 commit 315d539

File tree

11 files changed

+400
-303
lines changed

11 files changed

+400
-303
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ jobs:
1212
strategy:
1313
matrix:
1414
python:
15-
- "3.9"
1615
- "3.10"
1716
- "3.11"
1817
- "3.12"
1918
- "3.13"
19+
- "3.14"
2020
env:
2121
UV_PYTHON: ${{ matrix.python }}
2222
steps:

.python-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.9
1+
3.10

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ and accompanying [blog post][blog post].
3535
3636
## Installation
3737

38-
nb-clean requires Python 3.9 or later. To run the latest release of nb-clean in
38+
nb-clean requires Python 3.10 or later. To run the latest release of nb-clean in
3939
an ephemeral virtual environment, use [uv]:
4040

4141
```bash

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ authors = [{ name = "Scott Stevenson", email = "[email protected]" }]
66
readme = "README.md"
77
license = "ISC"
88
license-files = ["LICENSE"]
9-
requires-python = ">=3.9"
9+
requires-python = ">=3.10"
1010
keywords = ["jupyter", "notebook", "clean", "filter", "git"]
1111
classifiers = [
1212
"Development Status :: 5 - Production/Stable",
@@ -45,7 +45,7 @@ typeCheckingMode = "recommended"
4545
reportUnusedCallResult = false
4646

4747
[tool.ruff]
48-
target-version = "py39"
48+
target-version = "py310"
4949

5050
[tool.ruff.format]
5151
docstring-code-format = true

src/nb_clean/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ def check_notebook(
240240
print(f"{prefix}: outputs")
241241
is_clean = False
242242

243-
if remove_all_notebook_metadata and cast(dict[str, Any], notebook.metadata): # pyright: ignore[reportExplicitAny]
243+
if remove_all_notebook_metadata and cast("dict[str, Any]", notebook.metadata): # pyright: ignore[reportExplicitAny]
244244
print(f"{filename}: metadata")
245245
is_clean = False
246246

src/nb_clean/cli.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,10 @@ def check(
137137

138138
all_clean = True
139139
for input_ in processed_inputs:
140-
name = "stdin" if input_ is sys.stdin else os.fspath(cast(Path, input_))
140+
name = "stdin" if input_ is sys.stdin else os.fspath(cast("Path", input_))
141141

142142
notebook = cast(
143-
nbformat.NotebookNode,
143+
"nbformat.NotebookNode",
144144
nbformat.read(input_, as_version=nbformat.NO_CONVERT), # pyright: ignore[reportUnknownMemberType]
145145
)
146146
is_clean = nb_clean.check_notebook(
@@ -188,9 +188,9 @@ def clean(
188188
processed_inputs = [sys.stdin]
189189
outputs = [sys.stdout]
190190

191-
for input_, output in zip(processed_inputs, outputs):
191+
for input_, output in zip(processed_inputs, outputs, strict=True):
192192
notebook = cast(
193-
nbformat.NotebookNode,
193+
"nbformat.NotebookNode",
194194
nbformat.read(input_, as_version=nbformat.NO_CONVERT), # pyright: ignore[reportUnknownMemberType]
195195
)
196196

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
def _read_notebook(filename: str) -> nbformat.NotebookNode:
1111
return cast(
12-
nbformat.NotebookNode,
12+
"nbformat.NotebookNode",
1313
nbformat.read(NOTEBOOKS_DIR / filename, as_version=nbformat.NO_CONVERT), # pyright: ignore[reportUnknownMemberType]
1414
)
1515

tests/test_check_notebook.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
from typing import TYPE_CHECKING, cast
44

5-
import nbformat
65
import pytest
76

87
import nb_clean
98

109
if TYPE_CHECKING:
1110
from collections.abc import Collection
1211

12+
import nbformat
13+
1314

1415
@pytest.mark.parametrize(
1516
("notebook_name", "is_clean"),
@@ -22,7 +23,7 @@
2223
def test_check_notebook(
2324
notebook_name: str, *, is_clean: bool, request: pytest.FixtureRequest
2425
) -> None:
25-
notebook = cast(nbformat.NotebookNode, request.getfixturevalue(notebook_name))
26+
notebook = cast("nbformat.NotebookNode", request.getfixturevalue(notebook_name))
2627
assert nb_clean.check_notebook(notebook) is is_clean
2728

2829

@@ -141,7 +142,7 @@ def test_check_notebook_preserve_outputs(
141142
is_clean: bool,
142143
request: pytest.FixtureRequest,
143144
) -> None:
144-
notebook = cast(nbformat.NotebookNode, request.getfixturevalue(notebook_name))
145+
notebook = cast("nbformat.NotebookNode", request.getfixturevalue(notebook_name))
145146
output = nb_clean.check_notebook(
146147
notebook, preserve_cell_outputs=preserve_cell_outputs
147148
)
@@ -162,7 +163,7 @@ def test_check_notebook_preserve_execution_counts(
162163
is_clean: bool,
163164
request: pytest.FixtureRequest,
164165
) -> None:
165-
notebook = cast(nbformat.NotebookNode, request.getfixturevalue(notebook_name))
166+
notebook = cast("nbformat.NotebookNode", request.getfixturevalue(notebook_name))
166167
output = nb_clean.check_notebook(
167168
notebook, preserve_execution_counts=preserve_execution_counts
168169
)
@@ -190,7 +191,7 @@ def test_check_notebook_remove_all_notebook_metadata(
190191
# The test with `("clean_notebook_with_notebook_metadata", False, True)`
191192
# is False due to `clean_notebook_with_notebook_metadata` containing
192193
# `language_info.version` detected when `preserve_notebook_metadata=False`.
193-
notebook = cast(nbformat.NotebookNode, request.getfixturevalue(notebook_name))
194+
notebook = cast("nbformat.NotebookNode", request.getfixturevalue(notebook_name))
194195
assert (
195196
nb_clean.check_notebook(
196197
notebook, remove_all_notebook_metadata=remove_all_notebook_metadata

tests/test_clean_notebook.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def test_clean_notebook_with_notebook_metadata(
2525
request: pytest.FixtureRequest,
2626
) -> None:
2727
expected_output = cast(
28-
nbformat.NotebookNode, request.getfixturevalue(expected_output_name)
28+
"nbformat.NotebookNode", request.getfixturevalue(expected_output_name)
2929
)
3030
assert (
3131
nb_clean.clean_notebook(

tests/test_cli.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,9 @@ def test_check_stdin(
156156
expect_exit: bool,
157157
request: pytest.FixtureRequest,
158158
) -> None:
159-
notebook = cast(nbformat.NotebookNode, request.getfixturevalue(notebook_name))
159+
notebook = cast("nbformat.NotebookNode", request.getfixturevalue(notebook_name))
160160
monkeypatch.setattr(sys, "argv", ["nb-clean", "check"])
161-
content = cast(str, nbformat.writes(notebook)) # pyright: ignore[reportUnknownMemberType]
161+
content = cast("str", nbformat.writes(notebook)) # pyright: ignore[reportUnknownMemberType]
162162
monkeypatch.setattr(sys, "stdin", io.StringIO(content))
163163
if expect_exit:
164164
with pytest.raises(SystemExit) as exc:
@@ -177,11 +177,11 @@ def test_clean_file(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
177177
nb_clean.cli.main()
178178

179179
cleaned = cast(
180-
nbformat.NotebookNode,
180+
"nbformat.NotebookNode",
181181
nbformat.read(dst_dirty, as_version=nbformat.NO_CONVERT), # pyright: ignore[reportUnknownMemberType]
182182
)
183183
expected = cast(
184-
nbformat.NotebookNode,
184+
"nbformat.NotebookNode",
185185
nbformat.read( # pyright: ignore[reportUnknownMemberType]
186186
Path("tests/notebooks/clean.ipynb"), as_version=nbformat.NO_CONVERT
187187
),
@@ -193,26 +193,26 @@ def test_clean_stdin(
193193
capsys: CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
194194
) -> None:
195195
dirty = cast(
196-
nbformat.NotebookNode,
196+
"nbformat.NotebookNode",
197197
nbformat.read( # pyright: ignore[reportUnknownMemberType]
198198
Path("tests/notebooks/dirty.ipynb"), as_version=nbformat.NO_CONVERT
199199
),
200200
)
201201
expected = cast(
202-
nbformat.NotebookNode,
202+
"nbformat.NotebookNode",
203203
nbformat.read( # pyright: ignore[reportUnknownMemberType]
204204
Path("tests/notebooks/clean.ipynb"), as_version=nbformat.NO_CONVERT
205205
),
206206
)
207207

208208
monkeypatch.setattr(sys, "argv", ["nb-clean", "clean"])
209-
dirty_content = cast(str, nbformat.writes(dirty)) # pyright: ignore[reportUnknownMemberType]
209+
dirty_content = cast("str", nbformat.writes(dirty)) # pyright: ignore[reportUnknownMemberType]
210210
monkeypatch.setattr(sys, "stdin", io.StringIO(dirty_content))
211211

212212
nb_clean.cli.main()
213213

214214
out = capsys.readouterr().out
215-
expected_text = cast(str, nbformat.writes(expected)) # pyright: ignore[reportUnknownMemberType]
215+
expected_text = cast("str", nbformat.writes(expected)) # pyright: ignore[reportUnknownMemberType]
216216
assert out.strip() == expected_text.strip()
217217

218218

@@ -231,7 +231,7 @@ def test_clean_stdin(
231231
("add-filter -e", [], True, False, None, False, False, False),
232232
(
233233
"check -m -o a.ipynb b.ipynb",
234-
"a.ipynb b.ipynb".split(),
234+
["a.ipynb", "b.ipynb"],
235235
False,
236236
False,
237237
[],
@@ -241,7 +241,7 @@ def test_clean_stdin(
241241
),
242242
(
243243
"check -m tags -o a.ipynb b.ipynb",
244-
"a.ipynb b.ipynb".split(),
244+
["a.ipynb", "b.ipynb"],
245245
False,
246246
False,
247247
["tags"],
@@ -251,7 +251,7 @@ def test_clean_stdin(
251251
),
252252
(
253253
"check -m tags special -o a.ipynb b.ipynb",
254-
"a.ipynb b.ipynb".split(),
254+
["a.ipynb", "b.ipynb"],
255255
False,
256256
False,
257257
["tags", "special"],

0 commit comments

Comments
 (0)