Skip to content

Commit 45c7ccb

Browse files
authored
👷 exclude unsupported test matrix version combinations (#388)
2 parents 049c925 + c11242d commit 45c7ccb

File tree

2 files changed

+44
-39
lines changed

2 files changed

+44
-39
lines changed

‎.github/workflows/ci.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ jobs:
3535

3636
outputs:
3737
matrix: ${{ steps.set-matrix.outputs.matrix }}
38+
3839
steps:
3940
- uses: actions/checkout@v4
4041

@@ -56,9 +57,11 @@ jobs:
5657
needs: generate-matrix
5758
timeout-minutes: 3
5859
runs-on: ubuntu-latest
60+
5961
strategy:
6062
fail-fast: false
6163
matrix: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }}
64+
6265
steps:
6366
- uses: actions/checkout@v4
6467

@@ -72,7 +75,6 @@ jobs:
7275
run: uv tool install tox --with tox-uv
7376

7477
- name: Install dependencies
75-
continue-on-error: true
7678
run: |
7779
rm -rf .venv
7880
uv add --no-build-package=numpy "numpy<=${{ matrix.numpy }}"

‎scripts/generate_matrix.py

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
# ruff: noqa: TRY003
2+
# mypy: disable-error-code="no-any-expr, no-any-decorated"
3+
24
import importlib.metadata
35
import json
46
import sys
57
import urllib.error
68
import urllib.request
79
from functools import cache
8-
import typing
10+
from typing import Any, Final, TypedDict, cast
911

1012
from packaging.specifiers import SpecifierSet
1113
from packaging.version import Version, parse
1214

13-
# Constants
14-
PACKAGE_NAME = "scipy"
15-
DEPENDENCY_NAME = "numpy"
16-
MINIMUM_DEPENDENCY_VERSION_FOR_PYTHON_3_12 = Version("1.26.0")
15+
INDENT: Final = 4
16+
PACKAGE_NAME: Final = "scipy"
17+
DEPENDENCY_NAME: Final = "numpy"
18+
MIN_VERSIONS: Final = (
19+
(Version("3.11"), Version("1.24")),
20+
(Version("3.12"), Version("1.26")),
21+
(Version("3.13"), Version("2.1")),
22+
)
1723

1824

19-
class FileInfo(typing.TypedDict, total=False):
25+
class FileInfo(TypedDict, total=False):
2026
filename: str
2127
arch: str
2228
platform: str
@@ -25,18 +31,18 @@ class FileInfo(typing.TypedDict, total=False):
2531
requires_python: str
2632

2733

28-
class Release(typing.TypedDict):
34+
class Release(TypedDict):
2935
version: str
3036
stable: bool
3137
release_url: str
3238
files: list[FileInfo]
3339

3440

35-
class PackageVersions(typing.TypedDict, total=False):
41+
class PackageVersions(TypedDict, total=False):
3642
releases: dict[str, list[FileInfo]]
3743

3844

39-
@cache # type: ignore[no-any-expr]
45+
@cache
4046
def get_package_minimum_python_version(package: str) -> Version:
4147
"""
4248
Get the minimum Python version required by the specified package.
@@ -118,7 +124,7 @@ def get_available_python_versions(
118124
urllib.error.URLError: If fetching data fails.
119125
120126
"""
121-
data: list[Release] = typing.cast(
127+
data: list[Release] = cast(
122128
"list[Release]",
123129
fetch_json("https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json"),
124130
)
@@ -141,25 +147,26 @@ def get_available_python_versions(
141147
return sorted(versions.values())
142148

143149

144-
@cache # type: ignore[no-any-expr]
145-
def fetch_json(url: str) -> object:
150+
@cache
151+
def fetch_json(url: str) -> Any: # noqa: ANN401
146152
"""
147153
Fetch JSON data from a URL with caching.
148154
149155
Args:
150156
url (str): The URL to fetch.
151157
152158
Returns:
153-
dict[str, typing.Any] | list[typing.Any]: The parsed JSON data.
159+
dict[str, Any] | list[Any]: The parsed JSON data.
154160
155161
Raises:
156162
urllib.error.URLError: If fetching data fails.
157163
158164
"""
159165
try:
160-
with urllib.request.urlopen(url) as response: # type: ignore[no-any-expr] # noqa: S310
161-
return typing.cast("object", json.loads(response.read())) # type: ignore[no-any-expr]
162-
except urllib.error.URLError:
166+
with urllib.request.urlopen(url) as response: # noqa: S310
167+
return json.loads(response.read())
168+
except urllib.error.URLError as e:
169+
print(e, file=sys.stderr) # noqa: T201
163170
sys.exit(1)
164171

165172

@@ -181,10 +188,7 @@ def get_available_package_versions(package_name: str, min_version: Version) -> d
181188
RuntimeError: If no 'requires_python' is found for a package version.
182189
183190
"""
184-
data: PackageVersions = typing.cast(
185-
"PackageVersions",
186-
fetch_json(f"https://pypi.org/pypi/{package_name}/json"),
187-
)
191+
data: PackageVersions = fetch_json(f"https://pypi.org/pypi/{package_name}/json")
188192

189193
releases = data.get("releases", {})
190194
latest_versions: dict[tuple[int, int], tuple[Version, str]] = {}
@@ -205,13 +209,10 @@ def get_available_package_versions(package_name: str, min_version: Version) -> d
205209
# Skip versions without 'requires_python'
206210
continue
207211

208-
key = (version.major, version.minor)
212+
key = version.major, version.minor
209213
# Update to latest version within the minor version series
210214
if key not in latest_versions or version > latest_versions[key][0]:
211-
latest_versions[key] = (
212-
version,
213-
requires_python,
214-
)
215+
latest_versions[key] = version, requires_python
215216

216217
# Extract the versions and requires_python from the latest_versions dict
217218
return dict(latest_versions.values())
@@ -254,19 +255,21 @@ def main() -> None:
254255
specifier_set = SpecifierSet(requires_python)
255256

256257
for python_version in python_versions:
257-
if python_version in specifier_set:
258-
# Skip incompatible combinations
259-
if python_version >= Version("3.12") and package_version < MINIMUM_DEPENDENCY_VERSION_FOR_PYTHON_3_12:
260-
continue
261-
262-
include.append(
263-
{
264-
"python": str(python_version),
265-
DEPENDENCY_NAME: str(package_version),
266-
}
267-
)
268-
269-
json.dump({"include": include}, indent=4, fp=sys.stdout) # type: ignore[no-any-expr]
258+
if python_version not in specifier_set:
259+
continue
260+
261+
# Skip incompatible combinations
262+
if any(python_version >= py_min and package_version < np_min for py_min, np_min in MIN_VERSIONS):
263+
continue
264+
265+
include.append({
266+
"python": str(python_version),
267+
DEPENDENCY_NAME: str(package_version),
268+
}) # fmt: skip
269+
270+
json.dump({"include": include}, indent=INDENT, fp=sys.stdout)
271+
sys.stderr.flush()
272+
sys.stdout.flush()
270273

271274

272275
if __name__ == "__main__":

0 commit comments

Comments
 (0)