Skip to content

Commit ec5b21b

Browse files
committed
remove redundant logic (use same logic for dependency-groups as for tool.poetry.group)
1 parent d023277 commit ec5b21b

File tree

5 files changed

+219
-106
lines changed

5 files changed

+219
-106
lines changed

src/poetry/core/factory.py

Lines changed: 92 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from collections import defaultdict
66
from collections.abc import Mapping
7+
from itertools import chain
78
from pathlib import Path
89
from typing import TYPE_CHECKING
910
from typing import Any
@@ -13,14 +14,14 @@
1314

1415
from packaging.utils import canonicalize_name
1516

17+
from poetry.core.packages.dependency import Dependency
1618
from poetry.core.utils.helpers import combine_unicode
1719
from poetry.core.utils.helpers import readme_content_type
1820

1921

2022
if TYPE_CHECKING:
2123
from packaging.utils import NormalizedName
2224

23-
from poetry.core.packages.dependency import Dependency
2425
from poetry.core.packages.dependency_group import DependencyGroup
2526
from poetry.core.packages.project_package import ProjectPackage
2627
from poetry.core.poetry import Poetry
@@ -87,7 +88,27 @@ def get_package(cls, name: str, version: str) -> ProjectPackage:
8788
return ProjectPackage(name, version)
8889

8990
@classmethod
90-
def _add_package_group_dependencies(
91+
def _add_package_pep735_group_dependencies(
92+
cls,
93+
package: ProjectPackage,
94+
group: DependencyGroup,
95+
dependencies: list[str | dict[str, str]],
96+
) -> list[str]:
97+
group_includes = []
98+
for constraint in dependencies:
99+
if isinstance(constraint, str):
100+
dep = Dependency.create_from_pep_508(
101+
constraint,
102+
relative_to=package.root_dir,
103+
groups=[group.pretty_name],
104+
)
105+
group.add_dependency(dep)
106+
else:
107+
group_includes.append(constraint["include-group"])
108+
return group_includes
109+
110+
@classmethod
111+
def _add_package_poetry_group_dependencies(
91112
cls,
92113
package: ProjectPackage,
93114
group: str | DependencyGroup,
@@ -339,48 +360,62 @@ def _configure_package_dependencies(
339360
package.extras = package_extras
340361

341362
if "dependencies" in tool_poetry:
342-
cls._add_package_group_dependencies(
363+
cls._add_package_poetry_group_dependencies(
343364
package=package,
344365
group=MAIN_GROUP,
345366
dependencies=tool_poetry["dependencies"],
346367
)
347368

348369
if with_groups:
349-
normalized_groups = cls._normalize_dependency_group_names(dependency_groups)
350-
included = cls._resolve_dependency_group_includes(normalized_groups)
351-
for group_name, dependencies in normalized_groups.items():
352-
poetry_group_config = tool_poetry.get("group", {}).get(group_name, {})
370+
tool_poetry_groups = tool_poetry.get("group", {})
371+
tool_poetry_groups_normalized = {
372+
canonicalize_name(name): config
373+
for name, config in tool_poetry_groups.items()
374+
}
375+
# create groups from the dependency-groups section considering
376+
# additional information from the corresponding tool.poetry.group section
377+
pep739_include_groups = {}
378+
for group_name, dependencies in dependency_groups.items():
379+
poetry_group_config = tool_poetry_groups_normalized.get(
380+
canonicalize_name(group_name), {}
381+
)
353382
group = DependencyGroup(
354383
name=group_name,
355384
optional=poetry_group_config.get("optional", False),
356385
)
357386
package.add_dependency_group(group)
358-
359-
for constraint in dependencies:
360-
dep = Dependency.create_from_pep_508(
361-
constraint,
362-
relative_to=package.root_dir,
363-
groups=[group_name],
364-
)
365-
group.add_dependency(dep)
366-
367-
for group_name, group_config in tool_poetry.get("group", {}).items():
368-
if not package.has_dependency_group(group_name):
387+
included_groups = cls._add_package_pep735_group_dependencies(
388+
package=package,
389+
group=group,
390+
dependencies=dependencies,
391+
)
392+
pep739_include_groups[group_name] = included_groups
393+
# create groups from the tool.poetry.group section
394+
# with no corresponding entry in dependency-groups
395+
# and add dependency information for existing groups
396+
poetry_include_groups = {}
397+
for group_name, group_config in tool_poetry_groups.items():
398+
poetry_include_groups[group_name] = group_config.get(
399+
"include-groups", []
400+
)
401+
if package.has_dependency_group(group_name):
402+
group = package.dependency_group(group_name)
403+
else:
369404
group = DependencyGroup(
370405
name=group_name,
371406
optional=group_config.get("optional", False),
372407
)
373408
package.add_dependency_group(group)
409+
cls._add_package_poetry_group_dependencies(
410+
package=package,
411+
group=group,
412+
dependencies=group_config.get("dependencies", {}),
413+
)
374414

375-
for group_name_ in (group_name, *included.get(group_name, [])):
376-
cls._add_package_group_dependencies(
377-
package=package,
378-
group=group_name_,
379-
dependencies=group_config.get("dependencies", {}),
380-
)
381-
382-
for group_name, group_config in tool_poetry.get("group", {}).items():
383-
if include_groups := group_config.get("include-groups", []):
415+
for group_name, include_groups in chain(
416+
pep739_include_groups.items(), poetry_include_groups.items()
417+
):
418+
if include_groups:
384419
current_group = package.dependency_group(group_name)
385420
for name in include_groups:
386421
try:
@@ -396,7 +431,7 @@ def _configure_package_dependencies(
396431
current_group.include_dependency_group(group_to_include)
397432

398433
if with_groups and "dev-dependencies" in tool_poetry:
399-
cls._add_package_group_dependencies(
434+
cls._add_package_poetry_group_dependencies(
400435
package=package,
401436
group="dev",
402437
dependencies=tool_poetry["dev-dependencies"],
@@ -422,65 +457,6 @@ def _configure_package_dependencies(
422457

423458
package.extras = package_extras
424459

425-
@classmethod
426-
def _normalize_dependency_group_names(
427-
cls,
428-
dependency_groups: dict[str, list[str | dict[str, str]]],
429-
) -> dict[NormalizedName, list[str | dict[str, str]]]:
430-
original_names = defaultdict(list)
431-
normalized_groups: dict[NormalizedName, list[str | dict[str, str]]] = {}
432-
433-
for group_name, value in dependency_groups.items():
434-
normed_group_name = canonicalize_name(group_name)
435-
original_names[normed_group_name].append(group_name)
436-
normalized_groups[normed_group_name] = value
437-
438-
errors = []
439-
for normed_name, names in original_names.items():
440-
if len(names) > 1:
441-
errors.append(f"{normed_name} ({', '.join(names)})")
442-
if errors:
443-
raise ValueError(f"Duplicate dependency group names: {', '.join(errors)}")
444-
445-
return normalized_groups
446-
447-
@classmethod
448-
def _resolve_dependency_group_includes(
449-
cls, dependency_groups: dict[NormalizedName, list[str | dict[str, str]]]
450-
) -> dict[NormalizedName, list[NormalizedName]]:
451-
resolved_groups: set[NormalizedName] = set()
452-
included: dict[NormalizedName, list[NormalizedName]] = defaultdict(list)
453-
while resolved_groups != set(dependency_groups):
454-
for group, dependencies in dependency_groups.items():
455-
if group in resolved_groups:
456-
continue
457-
if all(isinstance(dep, str) for dep in dependencies):
458-
resolved_groups.add(group)
459-
continue
460-
resolved_dependencies: list[str | dict[str, str]] = []
461-
for dep in dependencies:
462-
if isinstance(dep, str):
463-
resolved_dependencies.append(dep)
464-
else:
465-
included_group = canonicalize_name(dep["include-group"])
466-
if included_group in included[group]:
467-
raise ValueError(
468-
f"Cyclic dependency group include:"
469-
f" {group} -> {included_group}"
470-
)
471-
included[included_group].append(group)
472-
try:
473-
resolved_dependencies.extend(
474-
dependency_groups[included_group]
475-
)
476-
except KeyError:
477-
raise ValueError(
478-
f"Dependency group '{included_group}'"
479-
f" (included in '{group}') not found"
480-
)
481-
dependency_groups[group] = resolved_dependencies
482-
return included
483-
484460
@classmethod
485461
def _prepare_formats(
486462
cls,
@@ -726,7 +702,7 @@ def validate(
726702
' Use "poetry.group.dev.dependencies" instead.'
727703
)
728704

729-
cls._validate_dependency_groups_includes(toml_data, result)
705+
cls._validate_dependency_groups(toml_data, result)
730706

731707
if strict:
732708
# Validate relation between [project] and [tool.poetry]
@@ -737,19 +713,41 @@ def validate(
737713
return result
738714

739715
@classmethod
740-
def _validate_dependency_groups_includes(
716+
def _validate_dependency_groups(
741717
cls, toml_data: dict[str, Any], result: dict[str, list[str]]
742718
) -> None:
743-
"""Ensure that dependency groups do not include themselves."""
744-
config = toml_data.setdefault("tool", {}).setdefault("poetry", {})
745-
719+
"""Ensure that there are no duplicated dependency groups
720+
and that they do not include themselves."""
721+
original_names = defaultdict(set)
746722
group_includes: dict[NormalizedName, list[NormalizedName]] = {}
747-
for group_name, group_config in config.get("group", {}).items():
723+
724+
for group_name, dependencies in toml_data.get("dependency-groups", {}).items():
725+
normalized_group_name = canonicalize_name(group_name)
726+
original_names[normalized_group_name].add(group_name)
727+
for constraint in dependencies:
728+
if isinstance(constraint, dict) and (
729+
include := constraint.get("include-group")
730+
):
731+
group_includes.setdefault(normalized_group_name, []).append(
732+
canonicalize_name(include)
733+
)
734+
735+
poetry_config = toml_data.get("tool", {}).get("poetry", {})
736+
for group_name, group_config in poetry_config.get("group", {}).items():
737+
normalized_group_name = canonicalize_name(group_name)
738+
original_names[normalized_group_name].add(group_name)
748739
if include_groups := group_config.get("include-groups", []):
749-
group_includes[canonicalize_name(group_name)] = [
740+
group_includes[normalized_group_name] = [
750741
canonicalize_name(name) for name in include_groups
751742
]
752743

744+
for normed_name, names in original_names.items():
745+
if len(names) > 1:
746+
result["errors"].append(
747+
"Duplicate dependency group name after normalization:"
748+
f" {normed_name} ({', '.join(sorted(names))})"
749+
)
750+
753751
for root in group_includes:
754752
# group, path to group, ancestors
755753
stack: list[
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
My Package
2+
==========
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
[tool.poetry]
2+
name = "my-package"
3+
version = "1.2.3"
4+
description = "Some description."
5+
authors = [
6+
"Sébastien Eustace <sebastien@eustace.io>"
7+
]
8+
maintainers = [
9+
"Sébastien Eustace <sebastien@eustace.io>"
10+
]
11+
license = "MIT"
12+
13+
readme = "README.rst"
14+
15+
homepage = "https://python-poetry.org"
16+
repository = "https://github.com/python-poetry/poetry"
17+
documentation = "https://python-poetry.org/docs"
18+
19+
keywords = ["packaging", "dependency", "poetry"]
20+
21+
classifiers = [
22+
"Topic :: Software Development :: Build Tools",
23+
"Topic :: Software Development :: Libraries :: Python Modules"
24+
]
25+
26+
# Requirements
27+
[tool.poetry.dependencies]
28+
python = ">=3.6"
29+
cleo = "^0.6"
30+
pendulum = { git = "https://github.com/sdispater/pendulum.git", branch = "2.0" }
31+
tomlkit = { git = "https://github.com/sdispater/tomlkit.git", rev = "3bff550", develop = true }
32+
requests = { version = "^2.18", optional = true, extras = [ "security" ] }
33+
pathlib2 = { version = "^2.2", python = "~2.7" }
34+
35+
orator = { version = "^0.9", optional = true }
36+
37+
# File dependency
38+
demo = { path = "../distributions/demo-0.1.0-py2.py3-none-any.whl" }
39+
40+
# Dir dependency with setup.py
41+
my-package = { path = "../project_with_setup/" }
42+
43+
# Dir dependency with pyproject.toml
44+
simple-project = { path = "../simple_project/" }
45+
46+
# Dependency with markers
47+
functools32 = { version = "^3.2.3", markers = "python_version ~= '2.7' and sys_platform == 'win32' or python_version in '3.4 3.5'" }
48+
49+
# Dependency with python constraint
50+
dataclasses = { version = "^0.7", python = ">=3.6.1,<3.7" }
51+
52+
53+
[tool.poetry.group.Test.dependencies]
54+
pytest = ">7"
55+
coverage = "*"
56+
57+
[tool.poetry.group.Dev]
58+
include-groups = [ "tEsT" ]
59+
60+
[tool.poetry.group.Docs]
61+
optional = true
62+
63+
[tool.poetry.group.Docs.dependencies]
64+
mkdocs = { git = "git+https://github.com/mkdocs/mkdocs.git", develop = true }
65+
66+
[tool.poetry.group.all]
67+
include-groups = [ "test", "dev", "docs" ]
68+
69+
70+
[tool.poetry.extras]
71+
db = [ "orator" ]
72+
network = [ "requests" ]
73+
74+
# Non-regression test for https://github.com/python-poetry/poetry-core/pull/492.
75+
# The underlying issue occurred because `tomlkit` can either return a TOML table as `Table` instance or an
76+
# `OutOfOrderProxy` one, if a table is discontinuous and multiple sections of a table are separated by a non-related
77+
# table, but we were too strict in our type check assertions.
78+
# So adding `tool.black` here ensure that we have discontinuous tables, so that we don't re-introduce the issue caused
79+
# by the type check assertion that ended up being reverted.
80+
[tool.black]
81+
preview = true
82+
83+
[tool.poetry.group.Dev.dependencies]
84+
pre-commit = "*"
85+
86+
87+
[tool.poetry.scripts]
88+
my-script = "my_package:main"
89+
90+
91+
[tool.poetry.plugins."blogtool.parsers"]
92+
".rst" = "some_module::SomeClass"

tests/fixtures/sample_project_with_groups_new/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ all = [
6969
{ "include-group" = "docs" },
7070
]
7171

72-
[tool.poetry.group.docs]
72+
[tool.poetry.group.Docs]
7373
optional = true
7474

75-
[tool.poetry.group.docs.dependencies]
75+
[tool.poetry.group.Docs.dependencies]
7676
mkdocs = { develop = true }

0 commit comments

Comments
 (0)