Skip to content

Commit f3215b7

Browse files
committed
Add support for dependency groups
1 parent 58138c5 commit f3215b7

15 files changed

+349
-70
lines changed

poetry/core/factory.py

+58-18
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class Factory(object):
2727
"""
2828

2929
def create_poetry(
30-
self, cwd: Optional[Path] = None, with_dev: bool = True
30+
self, cwd: Optional[Path] = None, with_groups: bool = True
3131
) -> "Poetry":
3232
from .poetry import Poetry
3333
from .pyproject.toml import PyProjectTOML
@@ -49,7 +49,7 @@ def create_poetry(
4949
version = local_config["version"]
5050
package = self.get_package(name, version)
5151
package = self.configure_package(
52-
package, local_config, poetry_file.parent, with_dev=with_dev
52+
package, local_config, poetry_file.parent, with_groups=with_groups
5353
)
5454

5555
return Poetry(poetry_file, local_config, package)
@@ -66,9 +66,10 @@ def configure_package(
6666
package: "ProjectPackage",
6767
config: Dict[str, Any],
6868
root: Path,
69-
with_dev: bool = True,
69+
with_groups: bool = True,
7070
) -> "ProjectPackage":
7171
from .packages.dependency import Dependency
72+
from .packages.dependency_group import DependencyGroup
7273
from .spdx.helpers import license_by_id
7374

7475
package.root_dir = root
@@ -99,46 +100,82 @@ def configure_package(
99100
package.platform = config["platform"]
100101

101102
if "dependencies" in config:
103+
group = DependencyGroup("default")
102104
for name, constraint in config["dependencies"].items():
103105
if name.lower() == "python":
104106
package.python_versions = constraint
105107
continue
106108

107109
if isinstance(constraint, list):
108110
for _constraint in constraint:
109-
package.add_dependency(
111+
group.add_dependency(
110112
cls.create_dependency(
111113
name, _constraint, root_dir=package.root_dir
112114
)
113115
)
114116

115117
continue
116118

117-
package.add_dependency(
119+
group.add_dependency(
118120
cls.create_dependency(name, constraint, root_dir=package.root_dir)
119121
)
120122

121-
if with_dev and "dev-dependencies" in config:
123+
package.add_dependency_group(group)
124+
125+
if with_groups and "group" in config:
126+
for group_name, group_config in config["group"].items():
127+
group = DependencyGroup(
128+
group_name, optional=group_config.get("optional", False)
129+
)
130+
for name, constraint in group_config["dependencies"].items():
131+
if isinstance(constraint, list):
132+
for _constraint in constraint:
133+
group.add_dependency(
134+
cls.create_dependency(
135+
name,
136+
_constraint,
137+
groups=[group_name],
138+
root_dir=package.root_dir,
139+
)
140+
)
141+
142+
continue
143+
144+
group.add_dependency(
145+
cls.create_dependency(
146+
name,
147+
constraint,
148+
groups=[group_name],
149+
root_dir=package.root_dir,
150+
)
151+
)
152+
153+
package.add_dependency_group(group)
154+
155+
if with_groups and "dev-dependencies" in config:
156+
group = DependencyGroup("dev")
122157
for name, constraint in config["dev-dependencies"].items():
123158
if isinstance(constraint, list):
124159
for _constraint in constraint:
125-
package.add_dependency(
160+
group.add_dependency(
126161
cls.create_dependency(
127162
name,
128163
_constraint,
129-
category="dev",
164+
groups=["dev"],
130165
root_dir=package.root_dir,
131166
)
132167
)
133168

134169
continue
135170

136-
package.add_dependency(
171+
group.add_dependency(
137172
cls.create_dependency(
138-
name, constraint, category="dev", root_dir=package.root_dir
173+
name, constraint, groups=["dev"], root_dir=package.root_dir
139174
)
140175
)
141176

177+
package.add_dependency_group(group)
178+
142179
extras = config.get("extras", {})
143180
for extra_name, requirements in extras.items():
144181
package.extras[extra_name] = []
@@ -191,7 +228,7 @@ def create_dependency(
191228
cls,
192229
name: str,
193230
constraint: Union[str, Dict[str, Any]],
194-
category: str = "main",
231+
groups: Optional[List[str]] = None,
195232
root_dir: Optional[Path] = None,
196233
) -> "DependencyTypes":
197234
from .packages.constraints import parse_constraint as parse_generic_constraint
@@ -204,6 +241,9 @@ def create_dependency(
204241
from .version.markers import AnyMarker
205242
from .version.markers import parse_marker
206243

244+
if groups is None:
245+
groups = ["default"]
246+
207247
if constraint is None:
208248
constraint = "*"
209249

@@ -234,7 +274,7 @@ def create_dependency(
234274
branch=constraint.get("branch", None),
235275
tag=constraint.get("tag", None),
236276
rev=constraint.get("rev", None),
237-
category=category,
277+
groups=groups,
238278
optional=optional,
239279
develop=constraint.get("develop", False),
240280
extras=constraint.get("extras", []),
@@ -245,7 +285,7 @@ def create_dependency(
245285
dependency = FileDependency(
246286
name,
247287
file_path,
248-
category=category,
288+
groups=groups,
249289
base=root_dir,
250290
extras=constraint.get("extras", []),
251291
)
@@ -261,7 +301,7 @@ def create_dependency(
261301
dependency = FileDependency(
262302
name,
263303
path,
264-
category=category,
304+
groups=groups,
265305
optional=optional,
266306
base=root_dir,
267307
extras=constraint.get("extras", []),
@@ -270,7 +310,7 @@ def create_dependency(
270310
dependency = DirectoryDependency(
271311
name,
272312
path,
273-
category=category,
313+
groups=groups,
274314
optional=optional,
275315
base=root_dir,
276316
develop=constraint.get("develop", False),
@@ -280,7 +320,7 @@ def create_dependency(
280320
dependency = URLDependency(
281321
name,
282322
constraint["url"],
283-
category=category,
323+
groups=groups,
284324
optional=optional,
285325
extras=constraint.get("extras", []),
286326
)
@@ -291,7 +331,7 @@ def create_dependency(
291331
name,
292332
version,
293333
optional=optional,
294-
category=category,
334+
groups=groups,
295335
allows_prereleases=allows_prereleases,
296336
extras=constraint.get("extras", []),
297337
)
@@ -324,7 +364,7 @@ def create_dependency(
324364

325365
dependency.source_name = constraint.get("source")
326366
else:
327-
dependency = Dependency(name, constraint, category=category)
367+
dependency = Dependency(name, constraint, groups=groups)
328368

329369
return dependency
330370

poetry/core/json/schemas/poetry-schema.json

+26
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,32 @@
147147
}
148148
}
149149
},
150+
"group": {
151+
"type": "object",
152+
"description": "This represents groups of dependencies",
153+
"patternProperties": {
154+
"^[a-zA-Z-_.0-9]+$": {
155+
"type": "object",
156+
"description": "This represents a single dependency group",
157+
"required": [
158+
"dependencies"
159+
],
160+
"properties": {
161+
"optional": {
162+
"type": "boolean",
163+
"description": "Whether the dependency group is optional or not"
164+
},
165+
"dependencies": {
166+
"type": "object",
167+
"description": "The dependencies of this dependency group",
168+
"$ref": "#/definitions/dependencies",
169+
"additionalProperties": false
170+
}
171+
},
172+
"additionalProperties": false
173+
}
174+
}
175+
},
150176
"build": {
151177
"$ref": "#/definitions/build-section"
152178
},

poetry/core/masonry/api.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def get_requires_for_build_wheel(
3939
def prepare_metadata_for_build_wheel(
4040
metadata_directory: str, config_settings: Optional[Dict[str, Any]] = None
4141
) -> str:
42-
poetry = Factory().create_poetry(Path(".").resolve(), with_dev=False)
42+
poetry = Factory().create_poetry(Path(".").resolve(), with_groups=False)
4343
builder = WheelBuilder(poetry)
4444

4545
dist_info = Path(metadata_directory, builder.dist_info)
@@ -64,7 +64,7 @@ def build_wheel(
6464
metadata_directory: Optional[str] = None,
6565
) -> str:
6666
"""Builds a wheel, places it in wheel_directory"""
67-
poetry = Factory().create_poetry(Path(".").resolve(), with_dev=False)
67+
poetry = Factory().create_poetry(Path(".").resolve(), with_groups=False)
6868

6969
return WheelBuilder.make_in(poetry, Path(wheel_directory))
7070

@@ -73,7 +73,7 @@ def build_sdist(
7373
sdist_directory: str, config_settings: Optional[Dict[str, Any]] = None
7474
) -> str:
7575
"""Builds an sdist, places it in sdist_directory"""
76-
poetry = Factory().create_poetry(Path(".").resolve(), with_dev=False)
76+
poetry = Factory().create_poetry(Path(".").resolve(), with_groups=False)
7777

7878
path = SdistBuilder(poetry).build(Path(sdist_directory))
7979

poetry/core/packages/dependency.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def __init__(
3434
name: str,
3535
constraint: Union[str, "VersionTypes"],
3636
optional: bool = False,
37-
category: str = "main",
37+
groups: Optional[List[str]] = None,
3838
allows_prereleases: bool = False,
3939
extras: Union[List[str], FrozenSet[str]] = None,
4040
source_type: Optional[str] = None,
@@ -58,7 +58,11 @@ def __init__(
5858

5959
self._pretty_constraint = str(constraint)
6060
self._optional = optional
61-
self._category = category
61+
62+
if not groups:
63+
groups = ["default"]
64+
65+
self._groups = frozenset(groups)
6266

6367
if (
6468
isinstance(self._constraint, VersionRangeConstraint)
@@ -113,8 +117,8 @@ def pretty_name(self) -> str:
113117
return self._pretty_name
114118

115119
@property
116-
def category(self) -> str:
117-
return self._category
120+
def groups(self) -> FrozenSet[str]:
121+
return self._groups
118122

119123
@property
120124
def python_versions(self) -> str:
@@ -387,7 +391,7 @@ def with_constraint(self, constraint: Union[str, "VersionTypes"]) -> "Dependency
387391
self.pretty_name,
388392
constraint,
389393
optional=self.is_optional(),
390-
category=self.category,
394+
groups=list(self._groups),
391395
allows_prereleases=self.allows_prereleases(),
392396
extras=self._extras,
393397
source_type=self._source_type,
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from typing import TYPE_CHECKING
2+
from typing import List
3+
4+
5+
if TYPE_CHECKING:
6+
from .types import DependencyTypes
7+
8+
9+
class DependencyGroup:
10+
def __init__(self, name: str, optional: bool = False) -> None:
11+
self._name: str = name
12+
self._optional: bool = optional
13+
self._dependencies: List["DependencyTypes"] = []
14+
15+
@property
16+
def name(self) -> str:
17+
return self._name
18+
19+
@property
20+
def dependencies(self) -> List["DependencyTypes"]:
21+
return self._dependencies
22+
23+
def is_optional(self) -> bool:
24+
return self._optional
25+
26+
def add_dependency(self, dependency: "DependencyTypes") -> None:
27+
self._dependencies.append(dependency)
28+
29+
def remove_dependency(self, name: "str") -> None:
30+
from poetry.core.utils.helpers import canonicalize_name
31+
32+
name = canonicalize_name(name)
33+
34+
dependencies = []
35+
for dependency in dependencies:
36+
if dependency.name == name:
37+
continue
38+
39+
dependencies.append(dependency)
40+
41+
self._dependencies = dependencies
42+
43+
def __eq__(self, other: "DependencyGroup") -> bool:
44+
if not isinstance(other, DependencyGroup):
45+
return NotImplemented
46+
47+
return self._name == other.name and set(self._dependencies) == set(
48+
other.dependencies
49+
)
50+
51+
def __repr__(self) -> str:
52+
return "{}({}, optional={})".format(
53+
self.__class__.__name__, self._name, self._optional
54+
)

poetry/core/packages/directory_dependency.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def __init__(
1818
self,
1919
name: str,
2020
path: Path,
21-
category: str = "main",
21+
groups: Optional[List[str]] = None,
2222
optional: bool = False,
2323
base: Optional[Path] = None,
2424
develop: bool = False,
@@ -61,7 +61,7 @@ def __init__(
6161
super(DirectoryDependency, self).__init__(
6262
name,
6363
"*",
64-
category=category,
64+
groups=groups,
6565
optional=optional,
6666
allows_prereleases=True,
6767
source_type="directory",
@@ -97,7 +97,7 @@ def with_constraint(self, constraint: "BaseConstraint") -> "DirectoryDependency"
9797
path=self.path,
9898
base=self.base,
9999
optional=self.is_optional(),
100-
category=self.category,
100+
groups=list(self._groups),
101101
develop=self._develop,
102102
extras=self._extras,
103103
)

0 commit comments

Comments
 (0)