Skip to content

Commit 99df94a

Browse files
committed
Add unit tests for dependency group loading
A new unit test module is added for parsing dependency groups and used to verify all of the pip-defined behaviors for handling dependency-groups. In one path, the underlying exception message from `dependency-groups` is exposed to users, where it should offer some explanation of why parsing failed, and this is therefore tested. Some related changes are applied to the dependency groups usage sites in the src tree. The signature of the dependency group requirement parse function is simplified, and its usage is therefore updated. A bugfix is applied to add a missing `f` on an intended f-string.
1 parent 8c542cd commit 99df94a

File tree

3 files changed

+120
-16
lines changed

3 files changed

+120
-16
lines changed

src/pip/_internal/cli/req_command.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,7 @@ def get_requirements(
242242
requirements.append(req_to_add)
243243

244244
if options.dependency_groups:
245-
for req in parse_dependency_groups(
246-
options.dependency_groups, session, finder=finder, options=options
247-
):
245+
for req in parse_dependency_groups(options.dependency_groups):
248246
req_to_add = install_req_from_req_string(
249247
req,
250248
isolated=options.isolated_mode,

src/pip/_internal/req/req_dependency_group.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
1-
import optparse
2-
from typing import TYPE_CHECKING, Any, Dict, List, Optional
1+
from typing import Any, Dict, List
32

43
from pip._vendor import tomli
54
from pip._vendor.dependency_groups import resolve as resolve_dependency_group
65

76
from pip._internal.exceptions import InstallationError
8-
from pip._internal.network.session import PipSession
97

10-
if TYPE_CHECKING:
11-
from pip._internal.index.package_finder import PackageFinder
128

13-
14-
def parse_dependency_groups(
15-
groups: List[str],
16-
session: PipSession,
17-
finder: Optional["PackageFinder"] = None,
18-
options: Optional[optparse.Values] = None,
19-
) -> List[str]:
9+
def parse_dependency_groups(groups: List[str]) -> List[str]:
2010
"""
2111
Parse dependency groups data in a way which is sensitive to the `pip` context and
2212
raises InstallationErrors if anything goes wrong.
@@ -36,7 +26,7 @@ def parse_dependency_groups(
3626
try:
3727
return list(resolve_dependency_group(raw_dependency_groups, *groups))
3828
except (ValueError, TypeError, LookupError) as e:
39-
raise InstallationError("[dependency-groups] resolution failed: {e}") from e
29+
raise InstallationError(f"[dependency-groups] resolution failed: {e}") from e
4030

4131

4232
def _load_pyproject() -> Dict[str, Any]:
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import errno
2+
from pathlib import Path
3+
from typing import Any
4+
5+
import pytest
6+
7+
from pip._internal.exceptions import InstallationError
8+
from pip._internal.req.req_dependency_group import parse_dependency_groups
9+
10+
11+
def test_parse_simple_dependency_groups(
12+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
13+
) -> None:
14+
pyproject = tmp_path.joinpath("pyproject.toml")
15+
pyproject.write_text(
16+
"""\
17+
[dependency-groups]
18+
foo = ["bar"]
19+
"""
20+
)
21+
monkeypatch.chdir(tmp_path)
22+
23+
result = list(parse_dependency_groups(["foo"]))
24+
25+
assert len(result) == 1, result
26+
assert result[0] == "bar"
27+
28+
29+
def test_parse_cyclic_dependency_groups(
30+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
31+
) -> None:
32+
pyproject = tmp_path.joinpath("pyproject.toml")
33+
pyproject.write_text(
34+
"""\
35+
[dependency-groups]
36+
foo = [{include-group="bar"}]
37+
bar = [{include-group="foo"}]
38+
"""
39+
)
40+
monkeypatch.chdir(tmp_path)
41+
42+
with pytest.raises(
43+
InstallationError, match=r"\[dependency-groups\] resolution failed:"
44+
) as excinfo:
45+
parse_dependency_groups(["foo"])
46+
47+
exception = excinfo.value
48+
assert (
49+
"Cyclic dependency group include while resolving foo: foo -> bar, bar -> foo"
50+
) in str(exception)
51+
52+
53+
def test_parse_with_no_dependency_groups_defined(
54+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
55+
) -> None:
56+
pyproject = tmp_path.joinpath("pyproject.toml")
57+
pyproject.write_text(
58+
"""\
59+
"""
60+
)
61+
monkeypatch.chdir(tmp_path)
62+
63+
with pytest.raises(
64+
InstallationError, match=r"\[dependency-groups\] table was missing\."
65+
):
66+
parse_dependency_groups(["foo"])
67+
68+
69+
def test_parse_with_no_pyproject_file(
70+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
71+
) -> None:
72+
monkeypatch.chdir(tmp_path)
73+
74+
with pytest.raises(InstallationError, match=r"pyproject\.toml not found\."):
75+
parse_dependency_groups(["foo"])
76+
77+
78+
def test_parse_with_malformed_pyproject_file(
79+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
80+
) -> None:
81+
pyproject = tmp_path.joinpath("pyproject.toml")
82+
pyproject.write_text(
83+
"""\
84+
[dependency-groups # no closing bracket
85+
foo = ["bar"]
86+
"""
87+
)
88+
monkeypatch.chdir(tmp_path)
89+
90+
with pytest.raises(InstallationError, match=r"Error parsing pyproject\.toml"):
91+
parse_dependency_groups(["foo"])
92+
93+
94+
def test_parse_gets_unexpected_oserror(
95+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
96+
) -> None:
97+
pyproject = tmp_path.joinpath("pyproject.toml")
98+
pyproject.write_text(
99+
"""\
100+
[dependency-groups]
101+
foo = ["bar"]
102+
"""
103+
)
104+
monkeypatch.chdir(tmp_path)
105+
106+
# inject an implementation of `tomli.load()` which emits an 'OSError(EPIPE, ...)'
107+
# as though we were loading from a fifo or other unusual filetype
108+
def epipe_toml_load(*args: Any, **kwargs: Any) -> None:
109+
raise OSError(errno.EPIPE, "Broken pipe")
110+
111+
monkeypatch.setattr(
112+
"pip._internal.req.req_dependency_group.tomli.load", epipe_toml_load
113+
)
114+
115+
with pytest.raises(InstallationError, match=r"Error reading pyproject\.toml"):
116+
parse_dependency_groups(["foo"])

0 commit comments

Comments
 (0)