Skip to content

Commit dc1d175

Browse files
committed
improve output of algorithm to find cycles
1 parent 3439837 commit dc1d175

File tree

2 files changed

+71
-39
lines changed

2 files changed

+71
-39
lines changed

src/poetry/core/factory.py

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -654,34 +654,22 @@ def _validate_dependency_groups_includes(
654654
canonicalize_name(name) for name in include_groups
655655
]
656656

657-
for group_name in group_includes:
658-
ancestors: defaultdict[NormalizedName, set[NormalizedName]] = defaultdict(
659-
set
660-
)
661-
stack = [group_name]
657+
for root in group_includes:
658+
# group, path to group, ancestors
659+
stack: list[
660+
tuple[NormalizedName, list[NormalizedName], set[NormalizedName]]
661+
] = [(root, [], {root})]
662662
while stack:
663-
group = stack.pop()
664-
current_ancestors = ancestors.get(group, set())
665-
child_ancestors = current_ancestors.union({group})
666-
663+
group, path, ancestors = stack.pop()
667664
for include in group_includes.get(group, []):
668-
if include in child_ancestors:
665+
new_path = [*path, include]
666+
if include in ancestors:
669667
result["errors"].append(
670-
f"Cyclic dependency group include in {group_name}:"
671-
f" {group} -> {include}"
668+
f"Cyclic dependency group include in {root}:"
669+
f" {' -> '.join(new_path)}"
672670
)
673-
# Avoid infinite loop; we've already found an error.
674-
continue
675-
676-
# This must be a union with any existing known ancestors.
677-
# Otherwise, we might accidentally miss a cycle (since we'd
678-
# be tossing out known dependencies). That cycle will be
679-
# caught when we use the share dependency as `group_name`,
680-
# but best to be safe.
681-
ancestors[include] = ancestors.get(include, set()).union(
682-
child_ancestors
683-
)
684-
stack.append(include)
671+
else:
672+
stack.append((include, new_path, ancestors | {include}))
685673

686674
@classmethod
687675
def _validate_legacy_vs_project(

tests/test_factory.py

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,6 +1191,7 @@ def test_create_poetry_with_self_referenced_dependency_groups(
11911191
include_group_name: str,
11921192
temporary_directory: Path,
11931193
) -> None:
1194+
"""testing-group -> testing-group"""
11941195
content = f"""\
11951196
[project]
11961197
name = "my-package"
@@ -1208,7 +1209,7 @@ def test_create_poetry_with_self_referenced_dependency_groups(
12081209

12091210
expected = """\
12101211
The Poetry configuration is invalid:
1211-
- Cyclic dependency group include in testing-group: testing-group -> testing-group
1212+
- Cyclic dependency group include in testing-group: testing-group
12121213
"""
12131214
assert_invalid_group_including(
12141215
toml_data=content,
@@ -1221,6 +1222,10 @@ def test_create_poetry_with_self_referenced_dependency_groups(
12211222
def test_create_poetry_with_direct_cyclic_dependency_groups(
12221223
temporary_directory: Path,
12231224
) -> None:
1225+
"""
1226+
testing -> dev
1227+
dev -> testing
1228+
"""
12241229
content = """\
12251230
[project]
12261231
name = "my-package"
@@ -1259,6 +1264,11 @@ def test_create_poetry_with_direct_cyclic_dependency_groups(
12591264
def test_create_poetry_with_indirect_full_cyclic_dependency_groups(
12601265
temporary_directory: Path,
12611266
) -> None:
1267+
"""
1268+
group-1 -> group-3
1269+
group-2 -> group-1
1270+
group-3 -> group-2
1271+
"""
12621272
content = """\
12631273
[project]
12641274
name = "my-package"
@@ -1289,9 +1299,9 @@ def test_create_poetry_with_indirect_full_cyclic_dependency_groups(
12891299

12901300
expected = """\
12911301
The Poetry configuration is invalid:
1292-
- Cyclic dependency group include in group-1: group-2 -> group-1
1293-
- Cyclic dependency group include in group-2: group-3 -> group-2
1294-
- Cyclic dependency group include in group-3: group-1 -> group-3
1302+
- Cyclic dependency group include in group-1: group-3 -> group-2 -> group-1
1303+
- Cyclic dependency group include in group-2: group-1 -> group-3 -> group-2
1304+
- Cyclic dependency group include in group-3: group-2 -> group-1 -> group-3
12951305
"""
12961306
assert_invalid_group_including(
12971307
toml_data=content,
@@ -1304,6 +1314,11 @@ def test_create_poetry_with_indirect_full_cyclic_dependency_groups(
13041314
def test_create_poetry_with_indirect_partial_cyclic_dependency_groups(
13051315
temporary_directory: Path,
13061316
) -> None:
1317+
"""
1318+
group-1 -> group-2
1319+
group-2 -> group-1
1320+
group-3 -> group-2
1321+
"""
13071322
content = """\
13081323
[project]
13091324
name = "my-package"
@@ -1336,7 +1351,7 @@ def test_create_poetry_with_indirect_partial_cyclic_dependency_groups(
13361351
The Poetry configuration is invalid:
13371352
- Cyclic dependency group include in group-1: group-2 -> group-1
13381353
- Cyclic dependency group include in group-2: group-1 -> group-2
1339-
- Cyclic dependency group include in group-3: group-1 -> group-2
1354+
- Cyclic dependency group include in group-3: group-2 -> group-1 -> group-2
13401355
"""
13411356
assert_invalid_group_including(
13421357
toml_data=content,
@@ -1349,6 +1364,11 @@ def test_create_poetry_with_indirect_partial_cyclic_dependency_groups(
13491364
def test_create_poetry_with_shared_dependency_groups(
13501365
temporary_directory: Path,
13511366
) -> None:
1367+
"""
1368+
root -> child-1, child-2
1369+
child-1 -> shared
1370+
child-2 -> shared
1371+
"""
13521372
content = """\
13531373
[project]
13541374
name = "my-package"
@@ -1396,15 +1416,23 @@ def test_create_poetry_with_shared_dependency_groups(
13961416
("foo", "root"),
13971417
("quux", "child-1"),
13981418
("quux", "child-2"),
1419+
# Duplicates because dependency is included via several groups.
1420+
# This is ok because they are merged during depenendency resolution.
1421+
("quux", "root"),
13991422
("quux", "root"),
1400-
("quux", "root"), # TODO: is this expected?
14011423
("quux", "shared"),
14021424
]
14031425

14041426

14051427
def test_create_poetry_with_shared_dependency_groups_more_complicated(
14061428
temporary_directory: Path,
14071429
) -> None:
1430+
"""
1431+
root -> child-1, child-2
1432+
child-1 -> shared
1433+
child-2 -> grandchild
1434+
grandchild -> shared
1435+
"""
14081436
content = """\
14091437
[project]
14101438
name = "my-package"
@@ -1463,15 +1491,24 @@ def test_create_poetry_with_shared_dependency_groups_more_complicated(
14631491
("quux", "child-1"),
14641492
("quux", "child-2"),
14651493
("quux", "grandchild"),
1494+
# Duplicates because dependency is included via several groups.
1495+
# This is ok because they are merged during depenendency resolution.
1496+
("quux", "root"),
14661497
("quux", "root"),
1467-
("quux", "root"), # TODO: is this expected?
14681498
("quux", "shared"),
14691499
]
14701500

14711501

14721502
def test_create_poetry_with_complicated_cyclic_diamond_dependency_groups(
14731503
temporary_directory: Path,
14741504
) -> None:
1505+
"""
1506+
root -> child-1, child-2
1507+
child-1 -> shared
1508+
child-2 -> shared
1509+
shared -> grandchild
1510+
grandchild -> child-2
1511+
"""
14751512
content = """\
14761513
[project]
14771514
name = "my-package"
@@ -1517,12 +1554,12 @@ def test_create_poetry_with_complicated_cyclic_diamond_dependency_groups(
15171554

15181555
expected = """\
15191556
The Poetry configuration is invalid:
1520-
- Cyclic dependency group include in root: grandchild -> child-2
1521-
- Cyclic dependency group include in root: grandchild -> child-2
1522-
- Cyclic dependency group include in child-1: child-2 -> shared
1523-
- Cyclic dependency group include in child-2: grandchild -> child-2
1524-
- Cyclic dependency group include in shared: child-2 -> shared
1525-
- Cyclic dependency group include in grandchild: shared -> grandchild
1557+
- Cyclic dependency group include in root: child-2 -> shared -> grandchild -> child-2
1558+
- Cyclic dependency group include in root: child-1 -> shared -> grandchild -> child-2 -> shared
1559+
- Cyclic dependency group include in child-1: shared -> grandchild -> child-2 -> shared
1560+
- Cyclic dependency group include in child-2: shared -> grandchild -> child-2
1561+
- Cyclic dependency group include in shared: grandchild -> child-2 -> shared
1562+
- Cyclic dependency group include in grandchild: child-2 -> shared -> grandchild
15261563
"""
15271564

15281565
assert_invalid_group_including(
@@ -1536,6 +1573,11 @@ def test_create_poetry_with_complicated_cyclic_diamond_dependency_groups(
15361573
def test_create_poetry_with_noncanonical_names_cyclic_dependency_groups(
15371574
temporary_directory: Path,
15381575
) -> None:
1576+
"""
1577+
group-1 -> group-2
1578+
group-2 -> group-1
1579+
group-3 -> group-2
1580+
"""
15391581
content = """\
15401582
[project]
15411583
name = "my-package"
@@ -1568,7 +1610,7 @@ def test_create_poetry_with_noncanonical_names_cyclic_dependency_groups(
15681610
The Poetry configuration is invalid:
15691611
- Cyclic dependency group include in group-1: group-2 -> group-1
15701612
- Cyclic dependency group include in group-2: group-1 -> group-2
1571-
- Cyclic dependency group include in group-3: group-1 -> group-2
1613+
- Cyclic dependency group include in group-3: group-2 -> group-1 -> group-2
15721614
"""
15731615
assert_invalid_group_including(
15741616
toml_data=content,
@@ -1665,7 +1707,9 @@ def test_create_poetry_with_nested_similar_dependencies(
16651707
assert [
16661708
(dep.name, ",".join(dep.groups)) for dep in poetry.package.all_requires
16671709
] == [
1710+
# Duplicates because dependency is included via several groups.
1711+
# This is ok because they are merged during depenendency resolution.
1712+
("foo", "parent"),
16681713
("foo", "parent"),
1669-
("foo", "parent"), # TODO: dupe!
16701714
("foo", "child"),
16711715
]

0 commit comments

Comments
 (0)