1
1
# ruff: noqa: TRY003
2
+ # mypy: disable-error-code="no-any-expr, no-any-decorated"
3
+
2
4
import importlib .metadata
3
5
import json
4
6
import sys
5
7
import urllib .error
6
8
import urllib .request
7
9
from functools import cache
8
- import typing
10
+ from typing import Any , Final , TypedDict , cast
9
11
10
12
from packaging .specifiers import SpecifierSet
11
13
from packaging .version import Version , parse
12
14
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
+ )
17
23
18
24
19
- class FileInfo (typing . TypedDict , total = False ):
25
+ class FileInfo (TypedDict , total = False ):
20
26
filename : str
21
27
arch : str
22
28
platform : str
@@ -25,18 +31,18 @@ class FileInfo(typing.TypedDict, total=False):
25
31
requires_python : str
26
32
27
33
28
- class Release (typing . TypedDict ):
34
+ class Release (TypedDict ):
29
35
version : str
30
36
stable : bool
31
37
release_url : str
32
38
files : list [FileInfo ]
33
39
34
40
35
- class PackageVersions (typing . TypedDict , total = False ):
41
+ class PackageVersions (TypedDict , total = False ):
36
42
releases : dict [str , list [FileInfo ]]
37
43
38
44
39
- @cache # type: ignore[no-any-expr]
45
+ @cache
40
46
def get_package_minimum_python_version (package : str ) -> Version :
41
47
"""
42
48
Get the minimum Python version required by the specified package.
@@ -118,7 +124,7 @@ def get_available_python_versions(
118
124
urllib.error.URLError: If fetching data fails.
119
125
120
126
"""
121
- data : list [Release ] = typing . cast (
127
+ data : list [Release ] = cast (
122
128
"list[Release]" ,
123
129
fetch_json ("https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json" ),
124
130
)
@@ -141,25 +147,26 @@ def get_available_python_versions(
141
147
return sorted (versions .values ())
142
148
143
149
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
146
152
"""
147
153
Fetch JSON data from a URL with caching.
148
154
149
155
Args:
150
156
url (str): The URL to fetch.
151
157
152
158
Returns:
153
- dict[str, typing. Any] | list[typing. Any]: The parsed JSON data.
159
+ dict[str, Any] | list[Any]: The parsed JSON data.
154
160
155
161
Raises:
156
162
urllib.error.URLError: If fetching data fails.
157
163
158
164
"""
159
165
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
163
170
sys .exit (1 )
164
171
165
172
@@ -181,10 +188,7 @@ def get_available_package_versions(package_name: str, min_version: Version) -> d
181
188
RuntimeError: If no 'requires_python' is found for a package version.
182
189
183
190
"""
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" )
188
192
189
193
releases = data .get ("releases" , {})
190
194
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
205
209
# Skip versions without 'requires_python'
206
210
continue
207
211
208
- key = ( version .major , version .minor )
212
+ key = version .major , version .minor
209
213
# Update to latest version within the minor version series
210
214
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
215
216
216
217
# Extract the versions and requires_python from the latest_versions dict
217
218
return dict (latest_versions .values ())
@@ -254,19 +255,21 @@ def main() -> None:
254
255
specifier_set = SpecifierSet (requires_python )
255
256
256
257
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 ()
270
273
271
274
272
275
if __name__ == "__main__" :
0 commit comments