Skip to content

Commit

Permalink
Separate list dependencies to a separate installer class (#3347)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaborbernat committed Sep 19, 2024
1 parent 0cb816c commit 4d8f5fd
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 43 deletions.
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",
]

0 comments on commit 4d8f5fd

Please sign in to comment.