diff --git a/.cspell.json b/.cspell.json index 5e270ee..5e9ee88 100644 --- a/.cspell.json +++ b/.cspell.json @@ -31,7 +31,9 @@ "codecov", "commitlint", "compwa", + "elif", "prereleased", + "pyproject", "redeboer", "venv" ], diff --git a/action.yml b/action.yml index fc78cca..a35b00a 100644 --- a/action.yml +++ b/action.yml @@ -13,8 +13,30 @@ runs: uses: actions/setup-python@v5 with: python-version: ${{ inputs.python-version }} - - run: pip install git+https://github.com/ComPWA/update-pip-constraints@main + + - name: Determine package configuration file + run: | + if [ -f pyproject.toml ]; then + if grep -q "\[project\]" pyproject.toml; then + echo 'SETUP_FILE=pyproject.toml' | tee -a $GITHUB_ENV + fi + elif [ -f setup.cfg ]; then + if grep -q "\[metadata\]" setup.cfg && grep -q "\[options\]" setup.cfg; then + echo 'SETUP_FILE=setup.cfg' | tee -a $GITHUB_ENV + fi + fi + shell: bash + + - if: env.SETUP_FILE == 'setup.cfg' + run: | + pip install update-pip-constraints@git+https://github.com/ComPWA/update-pip-constraints@v1 shell: bash + - if: env.SETUP_FILE == 'pyproject.toml' + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + uv pip install --system update-pip-constraints@git+https://github.com/ComPWA/update-pip-constraints@v1 + shell: bash + - run: update-pip-constraints shell: bash - uses: actions/upload-artifact@v4 diff --git a/setup.cfg b/setup.cfg index 49d20e5..77184df 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,6 +35,7 @@ install_requires = importlib-metadata; python_version <"3.8.0" pip-tools >=6.2.0 # strip-extras and extras_require toml + uv; python_version >="3.8.0" packages = find: package_dir = =src diff --git a/src/update_pip_constraints/__init__.py b/src/update_pip_constraints/__init__.py index 5b0aef5..9b0a999 100644 --- a/src/update_pip_constraints/__init__.py +++ b/src/update_pip_constraints/__init__.py @@ -8,41 +8,51 @@ # fmt: off import warnings # noqa: I001 warnings.filterwarnings("ignore", category=UserWarning) - from setuptools import find_packages # noqa: I001 # import setuptools first! # fmt: on +import shutil import os import sys from configparser import ConfigParser from pathlib import Path -from typing import List, Optional - +from typing import List, Optional, Sequence +from argparse import ArgumentParser import toml -from piptools.scripts import compile # type: ignore[import] -if sys.version_info < (3, 8): - from importlib_metadata import version -else: - from importlib.metadata import version - -def main() -> None: - python_version = _get_python_version() +def main(argv: Optional[Sequence[str]] = None) -> int: + parser = ArgumentParser(description="Update pip constraints file") + parser.add_argument( + "-p", + "--python-version", + default=_get_python_version(), + help="Python version to use. E.g. 3.9, 3.10, 3.11, etc.", + type=str, + ) + args = parser.parse_args(argv) + python_version = args.python_version output_file = _form_constraint_file_path(python_version) - unsafe_packages = None - if not os.path.exists("setup.cfg"): - unsafe_packages = _get_main_packages() - unsafe_packages.insert(0, "setuptools") - unsafe_packages.insert(0, "pip") - if update_constraints_file(output_file, unsafe_packages): + excludes: List[str] = [] + if shutil.which("uv") is None or not __uses_pyproject(): + excludes = [ + "pip", + "setuptools", + *_get_main_packages(), + ] + exit_code = update_constraints_file_py36(output_file, excludes) + else: + excludes = ["setuptools"] + exit_code = update_constraints_file(output_file, python_version, excludes) + if exit_code != 0: msg = "There were issues running pip-compile" raise RuntimeError(msg) + return exit_code def _get_python_version() -> str: - version = sys.version_info - return f"{version.major}.{version.minor}" + v = sys.version_info + return f"{v.major}.{v.minor}" def _get_main_packages() -> List[str]: @@ -86,14 +96,53 @@ def __get_package_directory() -> str: return "." +def __uses_pyproject() -> bool: + with open("pyproject.toml") as f: + pyproject = toml.load(f) # type: ignore[arg-type] + return pyproject.get("project", {}).get("name", None) is not None + + def _form_constraint_file_path(python_version: str) -> Path: constraints_dir = Path(".constraints") return constraints_dir / f"py{python_version}.txt" def update_constraints_file( - output_file: Path, unsafe_packages: Optional[List[str]] = None + output_file: Path, python_version: str, unsafe_packages: List[str] ) -> int: + import subprocess # noqa: PLC0415, S404 + + if not __uses_pyproject(): + msg = "Package has to be configured with pyproject.toml" + raise ValueError(msg) + output_file.parent.mkdir(exist_ok=True) + command_arguments = [ + "uv", + "pip", + "compile", + "pyproject.toml", + "-o", + str(output_file), + "--all-extras", + "--no-annotate", + f"--python-version={python_version}", + "--upgrade", + ] + for package in unsafe_packages: + command_arguments.append("--no-emit-package") + command_arguments.append(package) + return subprocess.check_call(command_arguments) # noqa: S603 + + +def update_constraints_file_py36(output_file: Path, unsafe_packages: List[str]) -> int: + from piptools.scripts import compile # type: ignore[import] # noqa: PLC0415 + + if sys.version_info < (3, 8): + from importlib_metadata import version # noqa: PLC0415 + else: + from importlib.metadata import version # noqa: PLC0415 + + print("Resolving dependencies with pip-tools...") # noqa: T201 output_file.parent.mkdir(exist_ok=True) command_arguments = [ "-o", @@ -107,12 +156,11 @@ def update_constraints_file( major, minor, *_ = (int(i) for i in version("pip-tools").split(".")) if (major, minor) >= (6, 8): command_arguments.append("--resolver=backtracking") - if unsafe_packages is not None: - for package in unsafe_packages: - command_arguments.append("--unsafe-package") - command_arguments.append(package) + for package in unsafe_packages: + command_arguments.append("--unsafe-package") + command_arguments.append(package) return compile.cli(command_arguments) # type: ignore[misc] -if "__main__" in __name__: - main() +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_update_pip_constraints.py b/tests/test_update_pip_constraints.py index f5ba17c..2581af6 100644 --- a/tests/test_update_pip_constraints.py +++ b/tests/test_update_pip_constraints.py @@ -7,7 +7,7 @@ from update_pip_constraints import ( _form_constraint_file_path, # pyright: ignore[reportPrivateUsage] _get_python_version, # pyright: ignore[reportPrivateUsage] - update_constraints_file, + update_constraints_file_py36, ) @@ -22,13 +22,13 @@ def test_get_python_version(): @pytest.mark.slow() -def test_update_constraints_file(): +def test_update_constraints_file_py36(): if "CI" in os.environ: pytest.skip() this_directory = Path(__file__).parent.absolute() output_file = this_directory / "constraints.txt" with pytest.raises(SystemExit) as error: - update_constraints_file(output_file) + update_constraints_file_py36(output_file, unsafe_packages=[]) assert error.type is SystemExit assert error.value.code == 0 with open(output_file) as stream: