Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate list dependencies to a separate installer class #3347

Merged
merged 1 commit into from
Sep 19, 2024
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 docs/changelog/3347.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Separate the list dependencies functionality to a separate abstract class allowing code reuse in plugins (such as
``tox-uv``) - by :gaborbernat`.
50 changes: 33 additions & 17 deletions src/tox/tox_env/python/pip/pip_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import logging
import operator
from abc import ABC, abstractmethod
from collections import defaultdict
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Sequence
Expand All @@ -21,14 +22,36 @@
from tox.tox_env.package import PathPackage


class Pip(Installer[Python]):
"""Pip is a python installer that can install packages as defined by PEP-508 and PEP-517."""

class PythonInstallerListDependencies(Installer[Python], ABC):
def __init__(self, tox_env: Python, with_list_deps: bool = True) -> None: # noqa: FBT001, FBT002
self._with_list_deps = with_list_deps
super().__init__(tox_env)

def _register_config(self) -> None:
if self._with_list_deps: # pragma: no branch
self._env.conf.add_config(
keys=["list_dependencies_command"],
of_type=Command,
default=Command(self.freeze_cmd()),
desc="command used to list installed packages",
)

@abstractmethod
def freeze_cmd(self) -> list[str]:
raise NotImplementedError

def installed(self) -> list[str]:
cmd: Command = self._env.conf["list_dependencies_command"]
result = self._env.execute(cmd=cmd.args, stdin=StdinSource.OFF, run_id="freeze", show=False)
result.assert_success()
return result.out.splitlines()


class Pip(PythonInstallerListDependencies):
"""Pip is a python installer that can install packages as defined by PEP-508 and PEP-517."""

def _register_config(self) -> None:
super()._register_config()
self._env.conf.add_config(
keys=["pip_pre"],
of_type=bool,
Expand All @@ -54,13 +77,9 @@ def _register_config(self) -> None:
default=False,
desc="Use the exact versions of installed deps as constraints, otherwise use the listed deps.",
)
if self._with_list_deps: # pragma: no branch
self._env.conf.add_config(
keys=["list_dependencies_command"],
of_type=Command,
default=Command(["python", "-m", "pip", "freeze", "--all"]),
desc="command used to list installed packages",
)

def freeze_cmd(self) -> list[str]: # noqa: PLR6301
return ["python", "-m", "pip", "freeze", "--all"]

def default_install_command(self, conf: Config, env_name: str | None) -> Command: # noqa: ARG002
isolated_flag = "-E" if self._env.base_python.version_info.major == 2 else "-I" # noqa: PLR2004
Expand All @@ -82,12 +101,6 @@ def post_process_install_command(self, cmd: Command) -> Command:
install_command.pop(opts_at)
return cmd

def installed(self) -> list[str]:
cmd: Command = self._env.conf["list_dependencies_command"]
result = self._env.execute(cmd=cmd.args, stdin=StdinSource.OFF, run_id="freeze", show=False)
result.assert_success()
return result.out.splitlines()

def install(self, arguments: Any, section: str, of_type: str) -> None:
if isinstance(arguments, PythonDeps):
self._install_requirement_file(arguments, section, of_type)
Expand Down Expand Up @@ -239,4 +252,7 @@ def build_install_cmd(self, args: Sequence[str]) -> list[str]:
return install_command[:opts_at] + list(args) + install_command[opts_at + 1 :]


__all__ = ("Pip",)
__all__ = [
"Pip",
"PythonInstallerListDependencies",
]
69 changes: 43 additions & 26 deletions src/tox/tox_env/python/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

from abc import ABC
from functools import partial
from typing import TYPE_CHECKING, Set

Expand All @@ -16,11 +17,13 @@
from .api import Python

if TYPE_CHECKING:
from tox.config.cli.parser import Parsed
from tox.config.sets import CoreConfigSet, EnvConfigSet
from tox.tox_env.api import ToxEnvCreateArgs
from tox.tox_env.package import Package


class PythonRun(Python, RunToxEnv):
class PythonRun(Python, RunToxEnv, ABC):
def __init__(self, create_args: ToxEnvCreateArgs) -> None:
super().__init__(create_args)

Expand All @@ -34,19 +37,7 @@ def register_config(self) -> None:
default=PythonDeps("", root),
desc="Name of the python dependencies as specified by PEP-440",
)

def skip_missing_interpreters_post_process(value: bool) -> bool: # noqa: FBT001
if getattr(self.options, "skip_missing_interpreters", "config") != "config":
return StrConvert().to_bool(self.options.skip_missing_interpreters)
return value

self.core.add_config(
keys=["skip_missing_interpreters"],
default=True,
of_type=bool,
post_process=skip_missing_interpreters_post_process,
desc="skip running missing interpreters",
)
add_skip_missing_interpreters_to_core(self.core, self.options)

@property
def _package_types(self) -> tuple[str, ...]:
Expand Down Expand Up @@ -77,18 +68,7 @@ def _register_package_conf(self) -> bool:
if pkg_type == "skip":
return False

def _normalize_extras(values: set[str]) -> set[str]:
# although _ and . is allowed this will be normalized during packaging to -
# https://packaging.python.org/en/latest/specifications/dependency-specifiers/#grammar
return {canonicalize_name(v) for v in values}

self.conf.add_config(
keys=["extras"],
of_type=Set[str],
default=set(),
desc="extras to install of the target package",
post_process=_normalize_extras,
)
add_extras_to_env(self.conf)
return True

@property
Expand Down Expand Up @@ -122,3 +102,40 @@ def _build_packages(self) -> list[Package]:
msg = f"{exception.args[0]} for package environment {package_env.conf['env_name']}"
raise Skip(msg) from exception
return packages


def add_skip_missing_interpreters_to_core(core: CoreConfigSet, options: Parsed) -> None:
def skip_missing_interpreters_post_process(value: bool) -> bool: # noqa: FBT001
if getattr(options, "skip_missing_interpreters", "config") != "config":
return StrConvert().to_bool(options.skip_missing_interpreters)
return value

core.add_config(
keys=["skip_missing_interpreters"],
default=True,
of_type=bool,
post_process=skip_missing_interpreters_post_process,
desc="skip running missing interpreters",
)


def add_extras_to_env(conf: EnvConfigSet) -> None:
def _normalize_extras(values: set[str]) -> set[str]:
# although _ and . is allowed this will be normalized during packaging to -
# https://packaging.python.org/en/latest/specifications/dependency-specifiers/#grammar
return {canonicalize_name(v) for v in values}

conf.add_config(
keys=["extras"],
of_type=Set[str],
default=set(),
desc="extras to install of the target package",
post_process=_normalize_extras,
)


__all__ = [
"PythonRun",
"add_extras_to_env",
"add_skip_missing_interpreters_to_core",
]