Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions scripts/.ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
extend = "../pyproject.toml"
line-length = 88
59 changes: 42 additions & 17 deletions scripts/generate_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
(Version("3.11"), Version("1.25")),
(Version("3.12"), Version("1.26")),
(Version("3.13"), Version("2.1")),
) # fmt: skip
)


class UVPythonVersionParts(TypedDict):
Expand Down Expand Up @@ -81,7 +81,9 @@ def get_package_minimum_python_version(package: str) -> Version:
"""
raw_version = importlib.metadata.metadata(package)["Requires-Python"]
if "<" in raw_version:
raise NotImplementedError("Version specifier with upper bound not yet supported!")
raise NotImplementedError(
"Version specifier with upper bound not yet supported!"
)

return parse(raw_version.replace(">=", "").replace("~=", ""))

Expand All @@ -98,7 +100,8 @@ def get_dependency_minimum_version(package: str, dependency: str) -> Version:
Version: The minimum required version of the dependency.

Raises:
ValueError: If the package does not list any requirements or the dependency is not found.
ValueError:
The package does not list any requirements or the dependency is not found.

"""
requirements = importlib.metadata.requires(package)
Expand All @@ -107,33 +110,49 @@ def get_dependency_minimum_version(package: str, dependency: str) -> Version:

try:
# Find the first requirement that matches the dependency and is not an extra
dependency_req = next(req for req in requirements if req.startswith(dependency) and " extra " not in req)
dependency_req = next(
req
for req in requirements
if req.startswith(dependency) and " extra " not in req
)
except StopIteration as e:
raise ValueError(f"Dependency {dependency} not found in requirements for {package}") from e
raise ValueError(
f"Dependency {dependency} not found in requirements for {package}"
) from e

# Extract the version specifier (e.g., ">=1.21.0")
version_specifier = next((ver for ver in dependency_req.split(",") if ">" in ver), None)
version_specifier = next(
(ver for ver in dependency_req.split(",") if ">" in ver), None
)
if version_specifier is None:
raise ValueError(f"No version specifier found for dependency {dependency} in {package}")
raise ValueError(
f"No version specifier found for dependency {dependency} in {package}"
)

# Remove dependency name and comparison operator to get the version
version_str = version_specifier.replace(dependency, "").replace(">=", "").replace(">", "")
version_str = (
version_specifier.replace(dependency, "").replace(">=", "").replace(">", "")
)
return parse(version_str)


def get_available_python_versions(
min_version: Version | None = None, max_version: Version | None = None, pre_releases: bool = False
min_version: Version | None = None,
max_version: Version | None = None,
pre_releases: bool = False,
) -> list[Version]:
"""
Get a list of available Python versions from GitHub Actions' Python Versions Manifest.
Get a list of available Python versions from GitHub Actions' Python Versions
Manifest.

Args:
min_version (Version | None): The minimum Python version to include in the list.
max_version (Version | None): The maximum Python version to include in the list.
pre_releases (bool): Whether to include pre-release versions.

Returns:
list[Version]: A list of available Python versions satisfying the specified criteria.
list[Version]:
A list of available Python versions satisfying the specified criteria.

Raises:
urllib.error.URLError: If fetching data fails.
Expand Down Expand Up @@ -189,19 +208,22 @@ def fetch_json(url: str) -> Any:
sys.exit(1)


def get_available_package_versions(package_name: str, min_version: Version, pre_releases: bool = False) -> dict[Version, str]:
def get_available_package_versions(
package_name: str, min_version: Version, pre_releases: bool = False
) -> dict[Version, str]:
"""
Get available package versions from PyPI starting from the specified minimum version,
but only include the latest micro version within each minor version series,
Get available package versions from PyPI starting from the specified minimum
version, but only include the latest micro version within each minor version series,
along with their 'requires_python' specifiers.

Args:
package_name (str): The name of the package on PyPI.
min_version (Version): The minimum version to include.

Returns:
dict[Version, str]: A mapping from the latest package versions in each minor version
series to their 'requires_python' specifier.
dict[Version, str]:
A mapping from the latest package versions in each minor version series to
their 'requires_python' specifier.

Raises:
RuntimeError: If no 'requires_python' is found for a package version.
Expand Down Expand Up @@ -268,7 +290,10 @@ def main() -> None:
continue

# Skip incompatible combinations
if any(py_version >= py_min and np_version < np_min for py_min, np_min in MIN_VERSIONS):
if any(
py_version >= py_min and np_version < np_min
for py_min, np_min in MIN_VERSIONS
):
continue

matrix_entries.append({
Expand Down
27 changes: 21 additions & 6 deletions scripts/unstubbed_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,19 @@

import scipy

BUNDLED = "scipy._lib.array_api_compat", "scipy._lib.array_api_extra", "scipy.fft._pocketfft"


def modules(mod: types.ModuleType, _seen: set[types.ModuleType] | None = None) -> list[str]:
BUNDLED = (
"scipy._lib.array_api_compat",
"scipy._lib.array_api_extra",
"scipy.fft._pocketfft",
"scipy.optimize._highspy",
"scipy.sparse.linalg._eigen.arpack",
"scipy.sparse.linalg._propack",
)


def modules(
mod: types.ModuleType, _seen: set[types.ModuleType] | None = None
) -> list[str]:
seen = _seen or set()
out: list[str] = []

Expand All @@ -28,7 +37,11 @@ def modules(mod: types.ModuleType, _seen: set[types.ModuleType] | None = None) -
mod_vars |= vars(mod)

for k, v in mod_vars.items():
if isinstance(v, types.ModuleType) and v not in seen and v.__name__.startswith("scipy"):
if (
isinstance(v, types.ModuleType)
and v not in seen
and v.__name__.startswith("scipy")
):
seen.add(v)
fname = v.__spec__.name if v.__spec__ else k
if "." in fname:
Expand All @@ -51,7 +64,9 @@ def is_stubbed(mod: str) -> bool:

*subpackages, submod = submods
subpackage_path = stubs_path.joinpath(*subpackages)
return (subpackage_path / f"{submod}.pyi").is_file() or (subpackage_path / submod / "__init__.pyi").is_file()
return (subpackage_path / f"{submod}.pyi").is_file() or (
subpackage_path / submod / "__init__.pyi"
).is_file()


if __name__ == "__main__":
Expand Down