From 9eb3fbfed4b8df2c251b5c25410a629b9df52f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Sun, 11 Dec 2022 10:03:11 -0800 Subject: [PATCH] Support for --no-deps within deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bernát Gábor --- docs/changelog/2674.bugfix.rst | 1 + src/tox/tox_env/python/pip/pip_install.py | 5 ++-- src/tox/tox_env/python/pip/req/file.py | 16 +++++++---- src/tox/tox_env/python/pip/req_file.py | 34 ++++++++++++++++++----- tests/tox_env/python/pip/test_req_file.py | 21 ++++++++++++++ 5 files changed, 62 insertions(+), 15 deletions(-) create mode 100644 docs/changelog/2674.bugfix.rst diff --git a/docs/changelog/2674.bugfix.rst b/docs/changelog/2674.bugfix.rst new file mode 100644 index 000000000..4c700c9c9 --- /dev/null +++ b/docs/changelog/2674.bugfix.rst @@ -0,0 +1 @@ +Support for ``--no-deps`` flag within the :ref:`deps` - by :user:`gaborbernat`. diff --git a/src/tox/tox_env/python/pip/pip_install.py b/src/tox/tox_env/python/pip/pip_install.py index 7bd7fe3ab..262974371 100644 --- a/src/tox/tox_env/python/pip/pip_install.py +++ b/src/tox/tox_env/python/pip/pip_install.py @@ -10,8 +10,7 @@ from tox.config.main import Config from tox.config.types import Command from tox.execute.request import StdinSource -from tox.report import HandledError -from tox.tox_env.errors import Recreate +from tox.tox_env.errors import Fail, Recreate from tox.tox_env.installer import Installer from tox.tox_env.package import PathPackage from tox.tox_env.python.api import Python @@ -92,7 +91,7 @@ def _install_requirement_file(self, arguments: PythonDeps, section: str, of_type try: new_options, new_reqs = arguments.unroll() except ValueError as exception: - raise HandledError(f"{exception} for tox env py within deps") + raise Fail(f"{exception} for tox env py within deps") new_requirements: list[str] = [] new_constraints: list[str] = [] for req in new_reqs: diff --git a/src/tox/tox_env/python/pip/req/file.py b/src/tox/tox_env/python/pip/req/file.py index 272937e80..a37e24cce 100644 --- a/src/tox/tox_env/python/pip/req/file.py +++ b/src/tox/tox_env/python/pip/req/file.py @@ -137,6 +137,10 @@ def __init__(self, path: Path, constraint: bool) -> None: self._as_root_args: list[str] | None = None self._parser_private: ArgumentParser | None = None + @property + def _req_parser(self) -> RequirementsFile: + return self + def __str__(self) -> str: return f"{'-c' if self.is_constraint else '-r'} {self.path}" @@ -162,8 +166,12 @@ def requirements(self) -> list[ParsedRequirement]: def _parser(self) -> ArgumentParser: if self._parser_private is None: self._parser_private = build_parser() + self._extend_parser(self._parser_private) return self._parser_private + def _extend_parser(self, parser: ArgumentParser) -> None: # noqa: U100 + ... + def _ensure_requirements_parsed(self) -> None: if self._requirements is None: 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) -> # do a join so relative paths work req_path = os.path.join(os.path.dirname(filename), req_path) if recurse: - yield from self._parse_and_recurse(req_path, nested_constraint, recurse) + yield from self._req_parser._parse_and_recurse(req_path, nested_constraint, recurse) else: line.filename = req_path yield line @@ -278,8 +286,7 @@ def _handle_requirement_line(line: ParsedLine) -> ParsedRequirement: req_options["hash"] = hash_values return ParsedRequirement(line.requirement, req_options, line.filename, line.lineno) - @staticmethod - def _merge_option_line(base_opt: Namespace, opt: Namespace, filename: str) -> None: # noqa: C901 + def _merge_option_line(self, base_opt: Namespace, opt: Namespace, filename: str) -> None: # noqa: C901 # percolate options upward if opt.requirements: if not hasattr(base_opt, "requirements"): @@ -428,8 +435,7 @@ def as_root_args(self) -> list[str]: self._as_root_args = result return self._as_root_args - @staticmethod - def _option_to_args(opt: Namespace) -> list[str]: + def _option_to_args(self, opt: Namespace) -> list[str]: result: list[str] = [] for req in getattr(opt, "requirements", []): result.extend(("-r", req)) diff --git a/src/tox/tox_env/python/pip/req_file.py b/src/tox/tox_env/python/pip/req_file.py index 91202345d..9a7da5468 100644 --- a/src/tox/tox_env/python/pip/req_file.py +++ b/src/tox/tox_env/python/pip/req_file.py @@ -1,7 +1,7 @@ from __future__ import annotations import re -from argparse import Namespace +from argparse import ArgumentParser, Namespace from pathlib import Path from .req.file import ParsedRequirement, ReqFileLines, RequirementsFile @@ -16,6 +16,27 @@ def __init__(self, raw: str, root: Path): super().__init__(root / "tox.ini", constraint=False) self._raw = self._normalize_raw(raw) self._unroll: tuple[list[str], list[str]] | None = None + self._req_parser_: RequirementsFile | None = None + + def _extend_parser(self, parser: ArgumentParser) -> None: + parser.add_argument("--no-deps", action="store_true", default=False) + + def _merge_option_line(self, base_opt: Namespace, opt: Namespace, filename: str) -> None: + super()._merge_option_line(base_opt, opt, filename) + if opt.no_deps: + base_opt.no_deps = True + + def _option_to_args(self, opt: Namespace) -> list[str]: + result = super()._option_to_args(opt) + if getattr(opt, "no_deps", False): + result.append("--no-deps") + return result + + @property + def _req_parser(self) -> RequirementsFile: + if self._req_parser_ is None: + self._req_parser_ = RequirementsFile(path=self._path, constraint=False) + return self._req_parser_ def _get_file_content(self, url: str) -> str: if self._is_url_self(url): @@ -69,14 +90,13 @@ def _parse_requirements(self, opt: Namespace, recurse: bool) -> list[ParsedRequi # check for any invalid options in the deps list # (requirements recursively included from other files are not checked) requirements = super()._parse_requirements(opt, recurse) - for r in requirements: - if r.from_file != str(self.path): + for req in requirements: + if req.from_file != str(self.path): continue for illegal_option in self._illegal_options: - if r.options.get(illegal_option): - raise ValueError( - f"Cannot use --{illegal_option} in deps list, it must be in requirements file. ({r})", - ) + if req.options.get(illegal_option): + msg = f"Cannot use --{illegal_option} in deps list, it must be in requirements file. ({req})" + raise ValueError(msg) return requirements def unroll(self) -> tuple[list[str], list[str]]: diff --git a/tests/tox_env/python/pip/test_req_file.py b/tests/tox_env/python/pip/test_req_file.py index 0abc6151e..4e9dadb4d 100644 --- a/tests/tox_env/python/pip/test_req_file.py +++ b/tests/tox_env/python/pip/test_req_file.py @@ -37,3 +37,24 @@ def test_deps_with_requirements_with_hash(tmp_path: Path) -> None: assert str(parsed_req.requirement) == "foo==1" assert parsed_req.options == {"hash": [exp_hash]} assert parsed_req.from_file == str(requirements) + + +def test_deps_with_no_deps(tmp_path: Path) -> None: + """deps with --hash should raise an exception.""" + (tmp_path / "r.txt").write_text("urrlib3") + python_deps = PythonDeps(raw="-rr.txt\n--no-deps", root=tmp_path) + + assert len(python_deps.requirements) == 1 + parsed_req = python_deps.requirements[0] + assert str(parsed_req.requirement) == "urrlib3" + + assert python_deps.options.no_deps is True + assert python_deps.as_root_args == ["-r", "r.txt", "--no-deps"] + + +def test_req_with_no_deps(tmp_path: Path) -> None: + """deps with --hash should raise an exception.""" + (tmp_path / "r.txt").write_text("--no-deps") + python_deps = PythonDeps(raw="-rr.txt", root=tmp_path) + with pytest.raises(ValueError, match="unrecognized arguments: --no-deps"): + python_deps.requirements