Skip to content

Commit eead325

Browse files
akaiholapre-commit-ci[bot]domdfcoding
authored
Add parser for [dependency-groups] (#78)
* Add parser for [dependency-groups] * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add tests for dependency-groups parser * Update documentation --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Dominic Davis-Foster <[email protected]>
1 parent a1136d4 commit eead325

File tree

71 files changed

+454
-20
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+454
-20
lines changed

doc-source/api/pyproject-parser.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Parser for ``pyproject.toml``.
1616
:autosummary-sections: Data
1717

1818
.. autoattrs:: pyproject_parser.PyProject
19-
:exclude-members: __ge__,__gt__,__le__,__lt__,__ne__
19+
:exclude-members: __ge__,__gt__,__le__,__lt__,__ne__,__repr__,__eq__
2020
:no-autosummary:
2121

2222
.. autoclass:: pyproject_parser.PyProjectTomlEncoder

doc-source/api/type_hints.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
:mod:`pyproject_parser.type_hints`
33
===================================
44

5-
.. autosummary-widths:: 1/5
5+
.. autosummary-widths:: 1/3
66
.. automodule:: pyproject_parser.type_hints

pyproject_parser/__init__.py

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,12 @@
6363

6464
# this package
6565
from pyproject_parser.classes import License, Readme, _NormalisedName
66-
from pyproject_parser.parsers import BuildSystemParser, PEP621Parser
66+
from pyproject_parser.parsers import BuildSystemParser, DependencyGroupsParser, PEP621Parser
6767
from pyproject_parser.type_hints import ( # noqa: F401
6868
Author,
6969
BuildSystemDict,
7070
ContentTypes,
71+
DependencyGroupsDict,
7172
ProjectDict,
7273
_PyProjectAsTomlDict
7374
)
@@ -239,23 +240,29 @@ class PyProject:
239240
240241
:param build_system:
241242
242-
.. autosummary-widths:: 23/64
243+
.. versionchanged:: 0.13.0 Added ``dependency_groups`` and ``dependency_groups_table_parser`` properties.
244+
245+
.. autosummary-widths:: 4/10
243246
244247
.. autoclasssumm:: PyProject
245248
:autosummary-sections: Methods
246-
:autosummary-exclude-members: __ge__,__gt__,__le__,__lt__,__ne__,__init__
249+
:autosummary-exclude-members: __ge__,__gt__,__le__,__lt__,__ne__,__init__,__repr__,__eq__
247250
248251
.. latex:clearpage::
249252
253+
.. autosummary-widths:: 1/2
254+
250255
.. autoclasssumm:: PyProject
251256
:autosummary-sections: Attributes
252257
253-
.. latex:vspace:: 10px
254258
"""
255259

256260
#: Represents the :pep:`build-system table <518#build-system-table>` defined in :pep:`517` and :pep:`518`.
257261
build_system: Optional[BuildSystemDict] = attr.ib(default=None)
258262

263+
#: Represents the :pep:`dependency groups table <735#specification>` defined in :pep:`735`.
264+
dependency_groups: Optional[DependencyGroupsDict] = attr.ib(default=None)
265+
259266
#: Represents the :pep621:`project table <table-name>` defined in :pep:`621`.
260267
project: Optional[ProjectDict] = attr.ib(default=None)
261268

@@ -268,6 +275,14 @@ class PyProject:
268275
to parse the :pep:`build-system table <518#build-system-table>` with.
269276
"""
270277

278+
dependency_groups_table_parser: ClassVar[DependencyGroupsParser] = DependencyGroupsParser()
279+
"""
280+
The :class:`~dom_toml.parser.AbstractConfigParser`
281+
to parse the :pep:`dependency groups table <735#specification>` with.
282+
283+
.. versionadded:: 0.13.0
284+
"""
285+
271286
project_table_parser: ClassVar[PEP621Parser] = PEP621Parser()
272287
"""
273288
The :class:`~dom_toml.parser.AbstractConfigParser`
@@ -313,6 +328,7 @@ def load(
313328
keys = set(config.keys())
314329

315330
build_system_table: Optional[BuildSystemDict] = None
331+
dependency_groups_table: Optional[DependencyGroupsDict] = None
316332
project_table: Optional[ProjectDict] = None
317333
tool_table: Dict[str, Dict[str, Any]] = {}
318334

@@ -323,6 +339,12 @@ def load(
323339
)
324340
keys.remove("build-system")
325341

342+
if "dependency-groups" in config:
343+
dependency_groups_table = cls.dependency_groups_table_parser.parse(
344+
config["dependency-groups"], set_defaults=set_defaults
345+
)
346+
keys.remove("dependency-groups")
347+
326348
if "project" in config:
327349
project_table = cls.project_table_parser.parse(config["project"], set_defaults=set_defaults)
328350
keys.remove("project")
@@ -336,7 +358,7 @@ def load(
336358
tool_table[tool_name] = cls.tool_parsers[tool_name].parse(tool_subtable)
337359

338360
if keys:
339-
allowed_top_level = ("build-system", "project", "tool")
361+
allowed_top_level = ("build-system", "dependency-groups", "project", "tool")
340362

341363
for top_level_key in sorted(keys):
342364
if top_level_key in allowed_top_level:
@@ -355,6 +377,7 @@ def load(
355377

356378
return cls(
357379
build_system=build_system_table,
380+
dependency_groups=dependency_groups_table,
358381
project=project_table,
359382
tool=tool_table,
360383
)
@@ -375,6 +398,7 @@ def dumps(
375398
"build-system": self.build_system,
376399
"project": self.project,
377400
"tool": self.tool,
401+
"dependency-groups": self.dependency_groups,
378402
}
379403

380404
if toml_dict["project"] is not None:
@@ -478,6 +502,8 @@ def from_dict(cls: Type[_PP], d: Mapping[str, Any]) -> _PP:
478502
for key, value in d.items():
479503
if key == "build-system":
480504
key = "build_system"
505+
elif key == "dependency-groups":
506+
key = "dependency_groups"
481507

482508
kwargs[key] = value
483509

@@ -494,4 +520,5 @@ def to_dict(self) -> MutableMapping[str, Any]:
494520
"build_system": self.build_system,
495521
"project": self.project,
496522
"tool": self.tool,
523+
"dependency_groups": self.dependency_groups,
497524
}

pyproject_parser/parsers.py

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,13 @@
4949

5050
# this package
5151
from pyproject_parser.classes import License, Readme, _NormalisedName
52-
from pyproject_parser.type_hints import Author, BuildSystemDict, ProjectDict
52+
from pyproject_parser.type_hints import Author, BuildSystemDict, DependencyGroupsDict, ProjectDict
5353
from pyproject_parser.utils import PyProjectDeprecationWarning, content_type_from_filename, render_readme
5454

5555
__all__ = [
5656
"RequiredKeysConfigParser",
5757
"BuildSystemParser",
58+
"DependencyGroupsParser",
5859
"PEP621Parser",
5960
]
6061

@@ -258,6 +259,60 @@ def parse( # type: ignore[override]
258259
return cast(BuildSystemDict, parsed_config)
259260

260261

262+
class DependencyGroupsParser(AbstractConfigParser):
263+
"""
264+
Parser for the :pep:`dependency groups table <735#specification>` table from ``pyproject.toml``.
265+
266+
.. versionadded:: 0.13.0
267+
""" # noqa: RST399
268+
269+
table_name: ClassVar[str] = "dependency-groups"
270+
271+
@property
272+
def keys(self) -> List[str]:
273+
"""
274+
The keys to parse from the TOML file.
275+
"""
276+
277+
return []
278+
279+
@staticmethod
280+
def parse_group(config: TOML_TYPES) -> List[Union[str, Dict[str, str]]]:
281+
"""
282+
Parse a group from the TOML configuration.
283+
284+
:param config:
285+
"""
286+
287+
if isinstance(config, list):
288+
return config
289+
290+
raise BadConfigError("A dependency group must be an array.")
291+
292+
def parse(
293+
self,
294+
config: Dict[str, TOML_TYPES],
295+
set_defaults: bool = False,
296+
) -> DependencyGroupsDict:
297+
"""
298+
Parse the TOML configuration.
299+
300+
:param config:
301+
:param set_defaults: If :py:obj:`True`, the values in
302+
:attr:`self.defaults <dom_toml.parser.AbstractConfigParser.defaults>` and
303+
:attr:`self.factories <dom_toml.parser.AbstractConfigParser.factories>`
304+
will be set as defaults for the returned mapping.
305+
306+
:rtype:
307+
308+
.. latex:clearpage::
309+
"""
310+
311+
parsed_config = {key: self.parse_group(value) for key, value in config.items()}
312+
313+
return cast(DependencyGroupsDict, parsed_config)
314+
315+
261316
class PEP621Parser(RequiredKeysConfigParser):
262317
"""
263318
Parser for :pep:`621` metadata from ``pyproject.toml``.
@@ -896,10 +951,6 @@ def parse_entry_points(self, config: Dict[str, TOML_TYPES]) -> Dict[str, Dict[st
896951
nbval = "nbval.plugin"
897952
898953
:param config: The unparsed TOML config for the :pep621:`project table <table-name>`.
899-
900-
:rtype:
901-
902-
.. latex:clearpage::
903954
"""
904955

905956
entry_points = config["entry-points"]

pyproject_parser/type_hints.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
#
2828

2929
# stdlib
30-
from typing import Any, Dict, List, Optional
30+
from typing import Any, Dict, List, Optional, Union
3131

3232
# 3rd party
3333
from packaging.markers import Marker
@@ -40,6 +40,8 @@
4040

4141
__all__ = [
4242
"BuildSystemDict",
43+
"IncludeGroupDict",
44+
"DependencyGroupsDict",
4345
"Dynamic",
4446
"ProjectDict",
4547
"Author",
@@ -57,6 +59,20 @@
5759
}
5860
)
5961

62+
IncludeGroupDict = TypedDict("IncludeGroupDict", {"include-group": str})
63+
"""
64+
:class:`typing.TypedDict`.
65+
66+
.. versionadded:: 0.13.0
67+
"""
68+
69+
DependencyGroupsDict = Dict[str, List[Union[str, IncludeGroupDict]]]
70+
"""
71+
The return type from the :class:`~.DependencyGroupsParser` class.
72+
73+
.. versionadded:: 0.13.0
74+
"""
75+
6076
#: Type hint for the :pep621:`dynamic` field defined in :pep:`621`.
6177
Dynamic = Literal[
6278
"name",
@@ -130,5 +146,10 @@ class ReadmeDict(TypedDict, total=False):
130146

131147
_PyProjectAsTomlDict = TypedDict(
132148
"_PyProjectAsTomlDict",
133-
{"build-system": Optional[BuildSystemDict], "project": Optional[ProjectDict], "tool": Dict[str, Any]},
149+
{
150+
"build-system": Optional[BuildSystemDict],
151+
"project": Optional[ProjectDict],
152+
"tool": Dict[str, Any],
153+
"dependency-groups": Optional[DependencyGroupsDict]
154+
},
134155
)

tests/conftest.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Callable, Type, TypeVar, Union
33

44
# 3rd party
5+
from dom_toml.decoder import InlineTableDict
56
from packaging.markers import Marker
67
from packaging.requirements import Requirement
78
from packaging.specifiers import SpecifierSet
@@ -46,3 +47,11 @@ def represent_readme_or_license( # noqa: MAN002
4647
data: Union[Readme, License],
4748
):
4849
return dumper.represent_dict(data.to_dict())
50+
51+
52+
@_representer_for(InlineTableDict)
53+
def represent_inline_table( # noqa: MAN002
54+
dumper: RegressionYamlDumper,
55+
data: InlineTableDict,
56+
):
57+
return dumper.represent_dict(dict(data))

tests/test_cli.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@
2727
from pyproject_parser.cli import ConfigTracebackHandler
2828
from tests.test_dumping import COMPLETE_UNDERSCORE_NAME, UNORDERED
2929

30+
COMPLETE_DEPENDENCY_GROUPS = COMPLETE_A + """
31+
32+
[dependency-groups]
33+
test = ["pytest", "coverage"]
34+
docs = ["sphinx", "sphinx-rtd-theme"]
35+
typing = ["mypy", "types-requests"]
36+
typing-test = [{include-group = "typing"}, {include-group = "test"}, "useful-types"]
37+
"""
38+
3039

3140
@pytest.mark.parametrize(
3241
"toml_string",
@@ -37,6 +46,7 @@
3746
pytest.param(COMPLETE_PROJECT_A, id="COMPLETE_PROJECT_A"),
3847
pytest.param(UNORDERED, id="UNORDERED"),
3948
pytest.param(COMPLETE_UNDERSCORE_NAME, id="COMPLETE_UNDERSCORE_NAME"),
49+
pytest.param(COMPLETE_DEPENDENCY_GROUPS, id="COMPLETE_DEPENDENCY_GROUPS"),
4050
]
4151
)
4252
@pytest.mark.parametrize("show_diff", [True, False])
@@ -177,7 +187,7 @@ def test_check_extra_deprecation_warning(
177187
),
178188
pytest.param(
179189
"[coverage]\nomit = 'demo.py'\n[flake8]\nselect = ['F401']",
180-
"Unexpected top-level key 'coverage'. Only 'build-system', 'project' and 'tool' are allowed.",
190+
"Unexpected top-level key 'coverage'. Only 'build-system', 'dependency-groups', 'project' and 'tool' are allowed.",
181191
id="top-level",
182192
),
183193
pytest.param(
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
BadConfigError: Unexpected top-level key 'coverage'. Only 'build-system', 'project' and 'tool' are allowed.
1+
BadConfigError: Unexpected top-level key 'coverage'. Only 'build-system', 'dependency-groups', 'project' and 'tool' are allowed.
22
Use '--traceback' to view the full traceback.
33
Aborted!
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Reformatting 'pyproject.toml'
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[build-system]
2+
requires = [ "whey",]
3+
build-backend = "whey"
4+
5+
[project]
6+
name = "whey"
7+
version = "2021.0.0"
8+
description = "A simple Python wheel builder for simple projects."
9+
keywords = [ "build", "distribution", "packaging", "pep517", "pep621", "sdist", "wheel",]
10+
dependencies = [ 'django>2.1; os_name != "nt"', 'django>2.0; os_name == "nt"', "gidgethub[httpx]>4.0.0", "httpx",]
11+
dynamic = [ "classifiers", "requires-python",]
12+
13+
[[project.authors]]
14+
name = "Dominic Davis-Foster"
15+
16+
17+
[project.urls]
18+
Homepage = "https://whey.readthedocs.io/en/latest"
19+
Documentation = "https://whey.readthedocs.io/en/latest"
20+
"Issue Tracker" = "https://github.com/repo-helper/whey/issues"
21+
"Source Code" = "https://github.com/repo-helper/whey"
22+
23+
[tool.whey]
24+
base-classifiers = [ "Development Status :: 4 - Beta",]
25+
python-versions = [ "3.6", "3.7", "3.8", "3.9", "3.10",]
26+
python-implementations = [ "CPython", "PyPy",]
27+
platforms = [ "Windows", "macOS", "Linux",]
28+
license-key = "MIT"
29+
30+
[dependency-groups]
31+
test = [ "pytest", "coverage",]
32+
docs = [ "sphinx", "sphinx-rtd-theme",]
33+
typing = [ "mypy", "types-requests",]
34+
typing-test = [ { include-group = "typing" }, { include-group = "test" }, "useful-types",]

0 commit comments

Comments
 (0)