Skip to content

Commit 4d8f5fd

Browse files
authored
Separate list dependencies to a separate installer class (#3347)
1 parent 0cb816c commit 4d8f5fd

File tree

3 files changed

+78
-43
lines changed

3 files changed

+78
-43
lines changed

docs/changelog/3347.feature.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Separate the list dependencies functionality to a separate abstract class allowing code reuse in plugins (such as
2+
``tox-uv``) - by :gaborbernat`.

src/tox/tox_env/python/pip/pip_install.py

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import logging
44
import operator
5+
from abc import ABC, abstractmethod
56
from collections import defaultdict
67
from pathlib import Path
78
from typing import TYPE_CHECKING, Any, Callable, Sequence
@@ -21,14 +22,36 @@
2122
from tox.tox_env.package import PathPackage
2223

2324

24-
class Pip(Installer[Python]):
25-
"""Pip is a python installer that can install packages as defined by PEP-508 and PEP-517."""
26-
25+
class PythonInstallerListDependencies(Installer[Python], ABC):
2726
def __init__(self, tox_env: Python, with_list_deps: bool = True) -> None: # noqa: FBT001, FBT002
2827
self._with_list_deps = with_list_deps
2928
super().__init__(tox_env)
3029

3130
def _register_config(self) -> None:
31+
if self._with_list_deps: # pragma: no branch
32+
self._env.conf.add_config(
33+
keys=["list_dependencies_command"],
34+
of_type=Command,
35+
default=Command(self.freeze_cmd()),
36+
desc="command used to list installed packages",
37+
)
38+
39+
@abstractmethod
40+
def freeze_cmd(self) -> list[str]:
41+
raise NotImplementedError
42+
43+
def installed(self) -> list[str]:
44+
cmd: Command = self._env.conf["list_dependencies_command"]
45+
result = self._env.execute(cmd=cmd.args, stdin=StdinSource.OFF, run_id="freeze", show=False)
46+
result.assert_success()
47+
return result.out.splitlines()
48+
49+
50+
class Pip(PythonInstallerListDependencies):
51+
"""Pip is a python installer that can install packages as defined by PEP-508 and PEP-517."""
52+
53+
def _register_config(self) -> None:
54+
super()._register_config()
3255
self._env.conf.add_config(
3356
keys=["pip_pre"],
3457
of_type=bool,
@@ -54,13 +77,9 @@ def _register_config(self) -> None:
5477
default=False,
5578
desc="Use the exact versions of installed deps as constraints, otherwise use the listed deps.",
5679
)
57-
if self._with_list_deps: # pragma: no branch
58-
self._env.conf.add_config(
59-
keys=["list_dependencies_command"],
60-
of_type=Command,
61-
default=Command(["python", "-m", "pip", "freeze", "--all"]),
62-
desc="command used to list installed packages",
63-
)
80+
81+
def freeze_cmd(self) -> list[str]: # noqa: PLR6301
82+
return ["python", "-m", "pip", "freeze", "--all"]
6483

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

85-
def installed(self) -> list[str]:
86-
cmd: Command = self._env.conf["list_dependencies_command"]
87-
result = self._env.execute(cmd=cmd.args, stdin=StdinSource.OFF, run_id="freeze", show=False)
88-
result.assert_success()
89-
return result.out.splitlines()
90-
91104
def install(self, arguments: Any, section: str, of_type: str) -> None:
92105
if isinstance(arguments, PythonDeps):
93106
self._install_requirement_file(arguments, section, of_type)
@@ -239,4 +252,7 @@ def build_install_cmd(self, args: Sequence[str]) -> list[str]:
239252
return install_command[:opts_at] + list(args) + install_command[opts_at + 1 :]
240253

241254

242-
__all__ = ("Pip",)
255+
__all__ = [
256+
"Pip",
257+
"PythonInstallerListDependencies",
258+
]

src/tox/tox_env/python/runner.py

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
from abc import ABC
56
from functools import partial
67
from typing import TYPE_CHECKING, Set
78

@@ -16,11 +17,13 @@
1617
from .api import Python
1718

1819
if TYPE_CHECKING:
20+
from tox.config.cli.parser import Parsed
21+
from tox.config.sets import CoreConfigSet, EnvConfigSet
1922
from tox.tox_env.api import ToxEnvCreateArgs
2023
from tox.tox_env.package import Package
2124

2225

23-
class PythonRun(Python, RunToxEnv):
26+
class PythonRun(Python, RunToxEnv, ABC):
2427
def __init__(self, create_args: ToxEnvCreateArgs) -> None:
2528
super().__init__(create_args)
2629

@@ -34,19 +37,7 @@ def register_config(self) -> None:
3437
default=PythonDeps("", root),
3538
desc="Name of the python dependencies as specified by PEP-440",
3639
)
37-
38-
def skip_missing_interpreters_post_process(value: bool) -> bool: # noqa: FBT001
39-
if getattr(self.options, "skip_missing_interpreters", "config") != "config":
40-
return StrConvert().to_bool(self.options.skip_missing_interpreters)
41-
return value
42-
43-
self.core.add_config(
44-
keys=["skip_missing_interpreters"],
45-
default=True,
46-
of_type=bool,
47-
post_process=skip_missing_interpreters_post_process,
48-
desc="skip running missing interpreters",
49-
)
40+
add_skip_missing_interpreters_to_core(self.core, self.options)
5041

5142
@property
5243
def _package_types(self) -> tuple[str, ...]:
@@ -77,18 +68,7 @@ def _register_package_conf(self) -> bool:
7768
if pkg_type == "skip":
7869
return False
7970

80-
def _normalize_extras(values: set[str]) -> set[str]:
81-
# although _ and . is allowed this will be normalized during packaging to -
82-
# https://packaging.python.org/en/latest/specifications/dependency-specifiers/#grammar
83-
return {canonicalize_name(v) for v in values}
84-
85-
self.conf.add_config(
86-
keys=["extras"],
87-
of_type=Set[str],
88-
default=set(),
89-
desc="extras to install of the target package",
90-
post_process=_normalize_extras,
91-
)
71+
add_extras_to_env(self.conf)
9272
return True
9373

9474
@property
@@ -122,3 +102,40 @@ def _build_packages(self) -> list[Package]:
122102
msg = f"{exception.args[0]} for package environment {package_env.conf['env_name']}"
123103
raise Skip(msg) from exception
124104
return packages
105+
106+
107+
def add_skip_missing_interpreters_to_core(core: CoreConfigSet, options: Parsed) -> None:
108+
def skip_missing_interpreters_post_process(value: bool) -> bool: # noqa: FBT001
109+
if getattr(options, "skip_missing_interpreters", "config") != "config":
110+
return StrConvert().to_bool(options.skip_missing_interpreters)
111+
return value
112+
113+
core.add_config(
114+
keys=["skip_missing_interpreters"],
115+
default=True,
116+
of_type=bool,
117+
post_process=skip_missing_interpreters_post_process,
118+
desc="skip running missing interpreters",
119+
)
120+
121+
122+
def add_extras_to_env(conf: EnvConfigSet) -> None:
123+
def _normalize_extras(values: set[str]) -> set[str]:
124+
# although _ and . is allowed this will be normalized during packaging to -
125+
# https://packaging.python.org/en/latest/specifications/dependency-specifiers/#grammar
126+
return {canonicalize_name(v) for v in values}
127+
128+
conf.add_config(
129+
keys=["extras"],
130+
of_type=Set[str],
131+
default=set(),
132+
desc="extras to install of the target package",
133+
post_process=_normalize_extras,
134+
)
135+
136+
137+
__all__ = [
138+
"PythonRun",
139+
"add_extras_to_env",
140+
"add_skip_missing_interpreters_to_core",
141+
]

0 commit comments

Comments
 (0)