From 7b749c8e5e968b0c2d70d75962b32711d588b777 Mon Sep 17 00:00:00 2001 From: George Kettleborough Date: Sat, 7 Oct 2023 22:08:27 +0100 Subject: [PATCH] Run pip-compile in env python rather than pip-tools python --- piptools/scripts/compile.py | 61 +++++++++++++++++++++++++++++++++++++ tests/test_cli_compile.py | 15 +++++++++ 2 files changed, 76 insertions(+) diff --git a/piptools/scripts/compile.py b/piptools/scripts/compile.py index 96aa9fa9..be77457f 100755 --- a/piptools/scripts/compile.py +++ b/piptools/scripts/compile.py @@ -3,9 +3,12 @@ import itertools import os import shlex +import shutil import sys import tempfile +import typing from pathlib import Path +from subprocess import run # nosec from typing import IO, Any, BinaryIO, cast import click @@ -122,7 +125,65 @@ def _determine_linesep( @options.build_deps_for @options.all_build_deps @options.only_build_deps +@typing.no_type_check def cli( + *args, + verbose: int, + quiet: int, + **kwargs, +): + """ + Compiles requirements.txt from requirements.in, pyproject.toml, setup.cfg, + or setup.py specs. + """ + log.verbosity = verbose - quiet + current_python = sys.executable + log.debug(f"{current_python=}") + env_python = shutil.which("python") + log.debug(f"{env_python=}") + if current_python != env_python: + # pip-tools probably installed globally (e.g. via pipx) + # install pip-tools in a venv made by env_python and run it there + click.echo("Installing pip-tools in temporary venv...", err=True) + with tempfile.TemporaryDirectory(prefix="pip-tools-env-") as venv_path: + log.debug(f"Installing venv at {venv_path}") + run( # nosec + [ + env_python, + "-m", + "venv", + venv_path, + ], + capture_output=(not log.verbosity), + check=True, + ) + temp_python = venv_path + "/bin/python" + log.debug(f"{temp_python=}") + run( # nosec + [ + temp_python, + "-m", + "pip", + "install", + "pip-tools", # TODO fix to this version of pip-tools? + ], + capture_output=(not log.verbosity), + check=True, + ) + env = {**os.environ, "PATH": f"{venv_path}/bin:{os.environ['PATH']}"} + run( # nosec + [ + "pip-compile", + *sys.argv[1:], + ], + env=env, + ) + else: + # pip-tools running in current env python + cli_runner(*args, verbose, quiet, **kwargs) + + +def cli_runner( ctx: click.Context, color: bool | None, verbose: int, diff --git a/tests/test_cli_compile.py b/tests/test_cli_compile.py index c5031fc2..ef19daf5 100644 --- a/tests/test_cli_compile.py +++ b/tests/test_cli_compile.py @@ -6,6 +6,7 @@ import shutil import subprocess import sys +import venv from textwrap import dedent from unittest import mock from unittest.mock import MagicMock @@ -38,6 +39,12 @@ ) +@pytest.fixture(autouse=True) +def mock_run(): + with mock.patch("piptools.scripts.compile.run") as mock_run: + yield mock_run + + @pytest.fixture( autouse=True, params=[ @@ -3771,3 +3778,11 @@ def test_stdout_should_not_be_read_when_stdin_is_not_a_plain_file( out = runner.invoke(cli, [req_in.as_posix(), "--output-file", fifo.as_posix()]) assert out.exit_code == 0, out + + +@mock.patch("piptools.scripts.compile.cli_runner") +def test_pip_compile_run_with_env_python(mock_cli_runner, mock_run, runner, tmpdir): + venv.EnvBuilder(with_pip=True, symlinks=True).create(tmpdir) + runner.invoke(cli, ["--verbose"], env={"PATH": f"{tmpdir}/bin"}) + mock_run.assert_called() + mock_cli_runner.assert_not_called()