Skip to content

Commit 7c4f177

Browse files
authored
Support for --no-deps within deps (#2678)
Resolves #2674
1 parent 36afe0c commit 7c4f177

File tree

5 files changed

+62
-15
lines changed

5 files changed

+62
-15
lines changed

docs/changelog/2674.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Support for ``--no-deps`` flag within the :ref:`deps` - by :user:`gaborbernat`.

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
from tox.config.main import Config
1111
from tox.config.types import Command
1212
from tox.execute.request import StdinSource
13-
from tox.report import HandledError
14-
from tox.tox_env.errors import Recreate
13+
from tox.tox_env.errors import Fail, Recreate
1514
from tox.tox_env.installer import Installer
1615
from tox.tox_env.package import PathPackage
1716
from tox.tox_env.python.api import Python
@@ -92,7 +91,7 @@ def _install_requirement_file(self, arguments: PythonDeps, section: str, of_type
9291
try:
9392
new_options, new_reqs = arguments.unroll()
9493
except ValueError as exception:
95-
raise HandledError(f"{exception} for tox env py within deps")
94+
raise Fail(f"{exception} for tox env py within deps")
9695
new_requirements: list[str] = []
9796
new_constraints: list[str] = []
9897
for req in new_reqs:

src/tox/tox_env/python/pip/req/file.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ def __init__(self, path: Path, constraint: bool) -> None:
137137
self._as_root_args: list[str] | None = None
138138
self._parser_private: ArgumentParser | None = None
139139

140+
@property
141+
def _req_parser(self) -> RequirementsFile:
142+
return self
143+
140144
def __str__(self) -> str:
141145
return f"{'-c' if self.is_constraint else '-r'} {self.path}"
142146

@@ -162,8 +166,12 @@ def requirements(self) -> list[ParsedRequirement]:
162166
def _parser(self) -> ArgumentParser:
163167
if self._parser_private is None:
164168
self._parser_private = build_parser()
169+
self._extend_parser(self._parser_private)
165170
return self._parser_private
166171

172+
def _extend_parser(self, parser: ArgumentParser) -> None: # noqa: U100
173+
...
174+
167175
def _ensure_requirements_parsed(self) -> None:
168176
if self._requirements is None:
169177
self._requirements = self._parse_requirements(opt=self._opt, recurse=True)
@@ -204,7 +212,7 @@ def _parse_and_recurse(self, filename: str, constraint: bool, recurse: bool) ->
204212
# do a join so relative paths work
205213
req_path = os.path.join(os.path.dirname(filename), req_path)
206214
if recurse:
207-
yield from self._parse_and_recurse(req_path, nested_constraint, recurse)
215+
yield from self._req_parser._parse_and_recurse(req_path, nested_constraint, recurse)
208216
else:
209217
line.filename = req_path
210218
yield line
@@ -278,8 +286,7 @@ def _handle_requirement_line(line: ParsedLine) -> ParsedRequirement:
278286
req_options["hash"] = hash_values
279287
return ParsedRequirement(line.requirement, req_options, line.filename, line.lineno)
280288

281-
@staticmethod
282-
def _merge_option_line(base_opt: Namespace, opt: Namespace, filename: str) -> None: # noqa: C901
289+
def _merge_option_line(self, base_opt: Namespace, opt: Namespace, filename: str) -> None: # noqa: C901
283290
# percolate options upward
284291
if opt.requirements:
285292
if not hasattr(base_opt, "requirements"):
@@ -428,8 +435,7 @@ def as_root_args(self) -> list[str]:
428435
self._as_root_args = result
429436
return self._as_root_args
430437

431-
@staticmethod
432-
def _option_to_args(opt: Namespace) -> list[str]:
438+
def _option_to_args(self, opt: Namespace) -> list[str]:
433439
result: list[str] = []
434440
for req in getattr(opt, "requirements", []):
435441
result.extend(("-r", req))

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

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
import re
4-
from argparse import Namespace
4+
from argparse import ArgumentParser, Namespace
55
from pathlib import Path
66

77
from .req.file import ParsedRequirement, ReqFileLines, RequirementsFile
@@ -16,6 +16,27 @@ def __init__(self, raw: str, root: Path):
1616
super().__init__(root / "tox.ini", constraint=False)
1717
self._raw = self._normalize_raw(raw)
1818
self._unroll: tuple[list[str], list[str]] | None = None
19+
self._req_parser_: RequirementsFile | None = None
20+
21+
def _extend_parser(self, parser: ArgumentParser) -> None:
22+
parser.add_argument("--no-deps", action="store_true", default=False)
23+
24+
def _merge_option_line(self, base_opt: Namespace, opt: Namespace, filename: str) -> None:
25+
super()._merge_option_line(base_opt, opt, filename)
26+
if opt.no_deps:
27+
base_opt.no_deps = True
28+
29+
def _option_to_args(self, opt: Namespace) -> list[str]:
30+
result = super()._option_to_args(opt)
31+
if getattr(opt, "no_deps", False):
32+
result.append("--no-deps")
33+
return result
34+
35+
@property
36+
def _req_parser(self) -> RequirementsFile:
37+
if self._req_parser_ is None:
38+
self._req_parser_ = RequirementsFile(path=self._path, constraint=False)
39+
return self._req_parser_
1940

2041
def _get_file_content(self, url: str) -> str:
2142
if self._is_url_self(url):
@@ -69,14 +90,13 @@ def _parse_requirements(self, opt: Namespace, recurse: bool) -> list[ParsedRequi
6990
# check for any invalid options in the deps list
7091
# (requirements recursively included from other files are not checked)
7192
requirements = super()._parse_requirements(opt, recurse)
72-
for r in requirements:
73-
if r.from_file != str(self.path):
93+
for req in requirements:
94+
if req.from_file != str(self.path):
7495
continue
7596
for illegal_option in self._illegal_options:
76-
if r.options.get(illegal_option):
77-
raise ValueError(
78-
f"Cannot use --{illegal_option} in deps list, it must be in requirements file. ({r})",
79-
)
97+
if req.options.get(illegal_option):
98+
msg = f"Cannot use --{illegal_option} in deps list, it must be in requirements file. ({req})"
99+
raise ValueError(msg)
80100
return requirements
81101

82102
def unroll(self) -> tuple[list[str], list[str]]:

tests/tox_env/python/pip/test_req_file.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,24 @@ def test_deps_with_requirements_with_hash(tmp_path: Path) -> None:
3737
assert str(parsed_req.requirement) == "foo==1"
3838
assert parsed_req.options == {"hash": [exp_hash]}
3939
assert parsed_req.from_file == str(requirements)
40+
41+
42+
def test_deps_with_no_deps(tmp_path: Path) -> None:
43+
"""deps with --hash should raise an exception."""
44+
(tmp_path / "r.txt").write_text("urrlib3")
45+
python_deps = PythonDeps(raw="-rr.txt\n--no-deps", root=tmp_path)
46+
47+
assert len(python_deps.requirements) == 1
48+
parsed_req = python_deps.requirements[0]
49+
assert str(parsed_req.requirement) == "urrlib3"
50+
51+
assert python_deps.options.no_deps is True
52+
assert python_deps.as_root_args == ["-r", "r.txt", "--no-deps"]
53+
54+
55+
def test_req_with_no_deps(tmp_path: Path) -> None:
56+
"""deps with --hash should raise an exception."""
57+
(tmp_path / "r.txt").write_text("--no-deps")
58+
python_deps = PythonDeps(raw="-rr.txt", root=tmp_path)
59+
with pytest.raises(ValueError, match="unrecognized arguments: --no-deps"):
60+
python_deps.requirements

0 commit comments

Comments
 (0)