Skip to content

Commit 5ea4d85

Browse files
Remove support for Python 3.9
1 parent 89b7c09 commit 5ea4d85

File tree

11 files changed

+71
-75
lines changed

11 files changed

+71
-75
lines changed

.github/workflows/ci-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
matrix:
2323
# The README.rst file mentions the versions tested, please update it as well
2424
py-ver-major: [3]
25-
py-ver-minor: [9, 10, 11, 12, 13, 14]
25+
py-ver-minor: [10, 11, 12, 13, 14]
2626
step: [lint, unit, mypy, bandit]
2727

2828
env:

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ mypy: $(PYSOURCES)
168168
MYPYPATH=$$MYPYPATH:mypy-stubs mypy $^
169169

170170
pyupgrade: $(filter-out schema_salad/metaschema.py,$(PYSOURCES))
171-
pyupgrade --exit-zero-even-if-changed --py39-plus $^
171+
pyupgrade --exit-zero-even-if-changed --py310-plus $^
172172
auto-walrus $^
173173

174174
release-test: FORCE

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ This is a testing tool for checking the output of Tools and Workflows described
3232
with the Common Workflow Language. Among other uses, it is used to run the CWL
3333
conformance tests.
3434

35-
This is written and tested for Python 3.9, 3.10, 3.11, 3.12, 3.13, and 3.14
35+
This is written and tested for Python 3.10, 3.11, 3.12, 3.13, and 3.14
3636

3737
.. contents:: Table of Contents
3838
:local:

cwltest/compare.py

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import hashlib
44
import json
5-
from typing import Any, Callable, Optional
5+
from typing import Any
6+
from collections.abc import Callable
67

78
import cwltest.stdfsaccess
89

@@ -14,7 +15,7 @@ class CompareFail(Exception):
1415

1516
@classmethod
1617
def format(
17-
cls, expected: Any, actual: Any, cause: Optional[Any] = None
18+
cls, expected: Any, actual: Any, cause: Any | None = None
1819
) -> "CompareFail":
1920
"""Load the difference details into the error message."""
2021
message = "expected: {}\ngot: {}".format(
@@ -197,10 +198,7 @@ def _compare_checksum(expected: dict[str, Any], actual: dict[str, Any]) -> None:
197198

198199

199200
def _compare_size(expected: dict[str, Any], actual: dict[str, Any]) -> None:
200-
if "path" in actual:
201-
path = actual["path"]
202-
else:
203-
path = actual["location"]
201+
path = actual.get("path", actual["location"])
204202

205203
actual_size_on_disk = fs_access.size(path)
206204

@@ -233,31 +231,32 @@ def compare(expected: Any, actual: Any, skip_details: bool = False) -> None:
233231
raise CompareFail.format(expected, actual)
234232

235233
try:
236-
if isinstance(expected, dict):
237-
if not isinstance(actual, dict):
238-
raise CompareFail.format(expected, actual)
239-
240-
if expected.get("class") == "File":
241-
_compare_file(expected, actual, skip_details)
242-
elif expected.get("class") == "Directory":
243-
_compare_directory(expected, actual, skip_details)
244-
else:
245-
_compare_dict(expected, actual, skip_details)
246-
247-
elif isinstance(expected, list):
248-
if not isinstance(actual, list):
249-
raise CompareFail.format(expected, actual)
250-
251-
if len(expected) != len(actual):
252-
raise CompareFail.format(expected, actual, "lengths don't match")
253-
for c in range(0, len(expected)):
254-
try:
255-
compare(expected[c], actual[c], skip_details)
256-
except CompareFail as e:
257-
raise CompareFail.format(expected, actual, e) from e
258-
else:
259-
if expected != actual:
260-
raise CompareFail.format(expected, actual)
234+
match expected:
235+
case dict():
236+
if not isinstance(actual, dict):
237+
raise CompareFail.format(expected, actual)
238+
239+
match expected.get("class"):
240+
case "File":
241+
_compare_file(expected, actual, skip_details)
242+
case "Directory":
243+
_compare_directory(expected, actual, skip_details)
244+
case _:
245+
_compare_dict(expected, actual, skip_details)
246+
case list():
247+
if not isinstance(actual, list):
248+
raise CompareFail.format(expected, actual)
249+
250+
if len(expected) != len(actual):
251+
raise CompareFail.format(expected, actual, "lengths don't match")
252+
for c in range(0, len(expected)):
253+
try:
254+
compare(expected[c], actual[c], skip_details)
255+
except CompareFail as e:
256+
raise CompareFail.format(expected, actual, e) from e
257+
case _:
258+
if expected != actual:
259+
raise CompareFail.format(expected, actual)
261260

262261
except Exception as e:
263262
raise CompareFail(str(e)) from e

cwltest/hooks.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
"""Hooks for pytest-cwl users."""
22

3-
from typing import Any, Optional
3+
from typing import Any
44

55
from cwltest import utils
66

77

88
def pytest_cwl_execute_test( # type: ignore[empty-body]
9-
config: utils.CWLTestConfig, processfile: str, jobfile: Optional[str]
10-
) -> tuple[int, Optional[dict[str, Any]]]:
9+
config: utils.CWLTestConfig, processfile: str, jobfile: str | None
10+
) -> tuple[int, dict[str, Any] | None]:
1111
"""
1212
Execute CWL test using a Python function instead of a command line runner.
1313

cwltest/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import sys
77
from collections import Counter, defaultdict
88
from concurrent.futures import ThreadPoolExecutor
9-
from typing import Optional, cast
9+
from typing import cast
1010

1111
import junit_xml
1212
import schema_salad.avro
@@ -119,7 +119,7 @@ def main() -> int:
119119
failures = 0
120120
unsupported = 0
121121
suite_name, _ = os.path.splitext(os.path.basename(args.test))
122-
report: Optional[junit_xml.TestSuite] = junit_xml.TestSuite(suite_name, [])
122+
report: junit_xml.TestSuite | None = junit_xml.TestSuite(suite_name, [])
123123

124124
load_optional_fsaccess_plugin()
125125

cwltest/plugin.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ class TestRunner(Protocol):
2727
"""Protocol to type-check test runner functions via the pluggy hook."""
2828

2929
def __call__(
30-
self, config: utils.CWLTestConfig, processfile: str, jobfile: Optional[str]
31-
) -> list[Optional[dict[str, Any]]]:
30+
self, config: utils.CWLTestConfig, processfile: str, jobfile: str | None
31+
) -> list[dict[str, Any] | None]:
3232
"""Type signature for pytest_cwl_execute_test hook results."""
3333
...
3434

@@ -224,7 +224,7 @@ def repr_failure(
224224
)
225225
)
226226

227-
def reportinfo(self) -> tuple[Union["os.PathLike[str]", str], Optional[int], str]:
227+
def reportinfo(self) -> tuple[Union["os.PathLike[str]", str], int | None, str]:
228228
"""Status report."""
229229
return self.path, 0, "cwl test: %s" % self.name
230230

@@ -372,7 +372,7 @@ def _doc_options() -> argparse.ArgumentParser:
372372

373373
def pytest_collect_file(
374374
file_path: Path, parent: pytest.Collector
375-
) -> Optional[pytest.Collector]:
375+
) -> pytest.Collector | None:
376376
"""Is this file for us."""
377377
if (
378378
file_path.suffix == ".yml" or file_path.suffix == ".yaml"

cwltest/utils.py

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from collections.abc import Iterable, MutableMapping, MutableSequence
1212
from importlib.metadata import EntryPoint, entry_points
1313
from importlib.resources import files
14-
from typing import Any, Optional, Union, cast
14+
from typing import Any, cast
1515
from urllib.parse import urljoin
1616

1717
import junit_xml
@@ -36,31 +36,31 @@ def __init__(
3636
self,
3737
entry: str,
3838
entry_line: str,
39-
basedir: Optional[str] = None,
40-
test_baseuri: Optional[str] = None,
41-
test_basedir: Optional[str] = None,
42-
outdir: Optional[str] = None,
43-
classname: Optional[str] = None,
44-
tool: Optional[str] = None,
45-
args: Optional[list[str]] = None,
46-
testargs: Optional[list[str]] = None,
47-
timeout: Optional[int] = None,
48-
verbose: Optional[bool] = None,
49-
runner_quiet: Optional[bool] = None,
39+
basedir: str | None = None,
40+
test_baseuri: str | None = None,
41+
test_basedir: str | None = None,
42+
outdir: str | None = None,
43+
classname: str | None = None,
44+
tool: str | None = None,
45+
args: list[str] | None = None,
46+
testargs: list[str] | None = None,
47+
timeout: int | None = None,
48+
verbose: bool | None = None,
49+
runner_quiet: bool | None = None,
5050
) -> None:
5151
"""Initialize test configuration."""
5252
self.basedir: str = basedir or os.getcwd()
5353
self.test_baseuri: str = test_baseuri or "file://" + self.basedir
5454
self.test_basedir: str = test_basedir or self.basedir
55-
self.outdir: Optional[str] = outdir
55+
self.outdir: str | None = outdir
5656
self.classname: str = classname or ""
5757
self.entry = urljoin(
5858
self.test_baseuri, os.path.basename(entry) + f"#L{entry_line}"
5959
)
6060
self.tool: str = tool or "cwl-runner"
6161
self.args: list[str] = args or []
6262
self.testargs: list[str] = testargs or []
63-
self.timeout: Optional[int] = timeout
63+
self.timeout: int | None = timeout
6464
self.verbose: bool = verbose or False
6565
self.runner_quiet: bool = runner_quiet or True
6666

@@ -70,11 +70,11 @@ class CWLTestReport:
7070

7171
def __init__(
7272
self,
73-
id: Union[int, str],
73+
id: int | str,
7474
category: list[str],
7575
entry: str,
7676
tool: str,
77-
job: Optional[str],
77+
job: str | None,
7878
) -> None:
7979
"""Initialize a CWLTestReport object."""
8080
self.id = id
@@ -96,7 +96,7 @@ def __init__(
9696
classname: str,
9797
entry: str,
9898
tool: str,
99-
job: Optional[str],
99+
job: str | None,
100100
message: str = "",
101101
) -> None:
102102
"""Initialize a TestResult object."""
@@ -240,7 +240,7 @@ def generate_badges(
240240

241241
def get_test_number_by_key(
242242
tests: list[dict[str, str]], key: str, value: str
243-
) -> Optional[int]:
243+
) -> int | None:
244244
"""Retrieve the test index from its name."""
245245
for i, test in enumerate(tests):
246246
if key in test and test[key] == value:
@@ -256,7 +256,7 @@ def load_and_validate_tests(path: str) -> tuple[Any, dict[str, Any]]:
256256
"""
257257
schema_resource = files("cwltest").joinpath("cwltest-schema.yml")
258258
with schema_resource.open("r", encoding="utf-8") as fp:
259-
cache: Optional[dict[str, Union[str, Graph, bool]]] = {
259+
cache: dict[str, str | Graph | bool] | None = {
260260
"https://w3id.org/cwl/cwltest/cwltest-schema.yml": fp.read()
261261
}
262262
(
@@ -283,8 +283,8 @@ def load_and_validate_tests(path: str) -> tuple[Any, dict[str, Any]]:
283283
def parse_results(
284284
results: Iterable[TestResult],
285285
tests: list[dict[str, Any]],
286-
suite_name: Optional[str] = None,
287-
report: Optional[junit_xml.TestSuite] = None,
286+
suite_name: str | None = None,
287+
report: junit_xml.TestSuite | None = None,
288288
) -> tuple[
289289
int, # total
290290
int, # passed
@@ -294,7 +294,7 @@ def parse_results(
294294
dict[str, list[CWLTestReport]], # passed for each tag
295295
dict[str, list[CWLTestReport]], # failures for each tag
296296
dict[str, list[CWLTestReport]], # unsupported for each tag
297-
Optional[junit_xml.TestSuite],
297+
junit_xml.TestSuite | None,
298298
]:
299299
"""
300300
Parse the results and return statistics and an optional report.
@@ -366,10 +366,10 @@ def parse_results(
366366
def prepare_test_command(
367367
tool: str,
368368
args: list[str],
369-
testargs: Optional[list[str]],
369+
testargs: list[str] | None,
370370
test: dict[str, Any],
371371
cwd: str,
372-
quiet: Optional[bool] = True,
372+
quiet: bool | None = True,
373373
) -> list[str]:
374374
"""Turn the test into a command line."""
375375
test_command = [tool]
@@ -407,7 +407,7 @@ def prepare_test_command(
407407
def prepare_test_paths(
408408
test: dict[str, str],
409409
cwd: str,
410-
) -> tuple[str, Optional[str]]:
410+
) -> tuple[str, str | None]:
411411
"""Determine the test path and the tool path."""
412412
cwd = schema_salad.ref_resolver.file_uri(cwd)
413413
processfile = test["tool"]
@@ -424,7 +424,7 @@ def prepare_test_paths(
424424
def run_test_plain(
425425
config: CWLTestConfig,
426426
test: dict[str, str],
427-
test_number: Optional[int] = None,
427+
test_number: int | None = None,
428428
) -> TestResult:
429429
"""Plain test runner."""
430430
out: dict[str, Any] = {}
@@ -443,7 +443,7 @@ def run_test_plain(
443443

444444
if test_number is not None:
445445
number = str(test_number)
446-
process: Optional[subprocess.Popen[str]] = None
446+
process: subprocess.Popen[str] | None = None
447447
try:
448448
cwd = os.getcwd()
449449
test_command = prepare_test_command(

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@ classifiers = [
1717
"Operating System :: POSIX",
1818
"Operating System :: MacOS :: MacOS X",
1919
"Development Status :: 5 - Production/Stable",
20-
"Programming Language :: Python :: 3.9",
2120
"Programming Language :: Python :: 3.10",
2221
"Programming Language :: Python :: 3.11",
2322
"Programming Language :: Python :: 3.12",
2423
"Programming Language :: Python :: 3.13",
2524
"Programming Language :: Python :: 3.14",
2625
"Typing :: Typed",
2726
]
28-
requires-python = ">=3.9,<3.15"
27+
requires-python = ">=3.10,<3.15"
2928
dynamic = ["version", "dependencies"]
3029

3130
[project.readme]

tests/util.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from contextlib import ExitStack
77
from importlib.resources import as_file, files
88
from pathlib import Path
9-
from typing import Optional
109

1110

1211
def get_data(filename: str) -> str:
@@ -28,7 +27,7 @@ def get_data(filename: str) -> str:
2827

2928

3029
def run_with_mock_cwl_runner(
31-
args: list[str], cwl_runner: Optional[str] = None
30+
args: list[str], cwl_runner: str | None = None
3231
) -> tuple[int, str, str]:
3332
"""Bind a mock cwlref-runner implementation to cwltest."""
3433
if cwl_runner is None:

0 commit comments

Comments
 (0)