Skip to content

Commit

Permalink
Add parser for [dependency-groups] (#78)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
3 people authored Feb 17, 2025
1 parent a1136d4 commit eead325
Show file tree
Hide file tree
Showing 71 changed files with 454 additions and 20 deletions.
2 changes: 1 addition & 1 deletion doc-source/api/pyproject-parser.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Parser for ``pyproject.toml``.
:autosummary-sections: Data

.. autoattrs:: pyproject_parser.PyProject
:exclude-members: __ge__,__gt__,__le__,__lt__,__ne__
:exclude-members: __ge__,__gt__,__le__,__lt__,__ne__,__repr__,__eq__
:no-autosummary:

.. autoclass:: pyproject_parser.PyProjectTomlEncoder
Expand Down
2 changes: 1 addition & 1 deletion doc-source/api/type_hints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
:mod:`pyproject_parser.type_hints`
===================================

.. autosummary-widths:: 1/5
.. autosummary-widths:: 1/3
.. automodule:: pyproject_parser.type_hints
37 changes: 32 additions & 5 deletions pyproject_parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,12 @@

# this package
from pyproject_parser.classes import License, Readme, _NormalisedName
from pyproject_parser.parsers import BuildSystemParser, PEP621Parser
from pyproject_parser.parsers import BuildSystemParser, DependencyGroupsParser, PEP621Parser
from pyproject_parser.type_hints import ( # noqa: F401
Author,
BuildSystemDict,
ContentTypes,
DependencyGroupsDict,
ProjectDict,
_PyProjectAsTomlDict
)
Expand Down Expand Up @@ -239,23 +240,29 @@ class PyProject:
:param build_system:
.. autosummary-widths:: 23/64
.. versionchanged:: 0.13.0 Added ``dependency_groups`` and ``dependency_groups_table_parser`` properties.
.. autosummary-widths:: 4/10
.. autoclasssumm:: PyProject
:autosummary-sections: Methods
:autosummary-exclude-members: __ge__,__gt__,__le__,__lt__,__ne__,__init__
:autosummary-exclude-members: __ge__,__gt__,__le__,__lt__,__ne__,__init__,__repr__,__eq__
.. latex:clearpage::
.. autosummary-widths:: 1/2
.. autoclasssumm:: PyProject
:autosummary-sections: Attributes
.. latex:vspace:: 10px
"""

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

#: Represents the :pep:`dependency groups table <735#specification>` defined in :pep:`735`.
dependency_groups: Optional[DependencyGroupsDict] = attr.ib(default=None)

#: Represents the :pep621:`project table <table-name>` defined in :pep:`621`.
project: Optional[ProjectDict] = attr.ib(default=None)

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

dependency_groups_table_parser: ClassVar[DependencyGroupsParser] = DependencyGroupsParser()
"""
The :class:`~dom_toml.parser.AbstractConfigParser`
to parse the :pep:`dependency groups table <735#specification>` with.
.. versionadded:: 0.13.0
"""

project_table_parser: ClassVar[PEP621Parser] = PEP621Parser()
"""
The :class:`~dom_toml.parser.AbstractConfigParser`
Expand Down Expand Up @@ -313,6 +328,7 @@ def load(
keys = set(config.keys())

build_system_table: Optional[BuildSystemDict] = None
dependency_groups_table: Optional[DependencyGroupsDict] = None
project_table: Optional[ProjectDict] = None
tool_table: Dict[str, Dict[str, Any]] = {}

Expand All @@ -323,6 +339,12 @@ def load(
)
keys.remove("build-system")

if "dependency-groups" in config:
dependency_groups_table = cls.dependency_groups_table_parser.parse(
config["dependency-groups"], set_defaults=set_defaults
)
keys.remove("dependency-groups")

if "project" in config:
project_table = cls.project_table_parser.parse(config["project"], set_defaults=set_defaults)
keys.remove("project")
Expand All @@ -336,7 +358,7 @@ def load(
tool_table[tool_name] = cls.tool_parsers[tool_name].parse(tool_subtable)

if keys:
allowed_top_level = ("build-system", "project", "tool")
allowed_top_level = ("build-system", "dependency-groups", "project", "tool")

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

return cls(
build_system=build_system_table,
dependency_groups=dependency_groups_table,
project=project_table,
tool=tool_table,
)
Expand All @@ -375,6 +398,7 @@ def dumps(
"build-system": self.build_system,
"project": self.project,
"tool": self.tool,
"dependency-groups": self.dependency_groups,
}

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

kwargs[key] = value

Expand All @@ -494,4 +520,5 @@ def to_dict(self) -> MutableMapping[str, Any]:
"build_system": self.build_system,
"project": self.project,
"tool": self.tool,
"dependency_groups": self.dependency_groups,
}
61 changes: 56 additions & 5 deletions pyproject_parser/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,13 @@

# this package
from pyproject_parser.classes import License, Readme, _NormalisedName
from pyproject_parser.type_hints import Author, BuildSystemDict, ProjectDict
from pyproject_parser.type_hints import Author, BuildSystemDict, DependencyGroupsDict, ProjectDict
from pyproject_parser.utils import PyProjectDeprecationWarning, content_type_from_filename, render_readme

__all__ = [
"RequiredKeysConfigParser",
"BuildSystemParser",
"DependencyGroupsParser",
"PEP621Parser",
]

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


class DependencyGroupsParser(AbstractConfigParser):
"""
Parser for the :pep:`dependency groups table <735#specification>` table from ``pyproject.toml``.
.. versionadded:: 0.13.0
""" # noqa: RST399

table_name: ClassVar[str] = "dependency-groups"

@property
def keys(self) -> List[str]:
"""
The keys to parse from the TOML file.
"""

return []

@staticmethod
def parse_group(config: TOML_TYPES) -> List[Union[str, Dict[str, str]]]:
"""
Parse a group from the TOML configuration.
:param config:
"""

if isinstance(config, list):
return config

raise BadConfigError("A dependency group must be an array.")

def parse(
self,
config: Dict[str, TOML_TYPES],
set_defaults: bool = False,
) -> DependencyGroupsDict:
"""
Parse the TOML configuration.
:param config:
:param set_defaults: If :py:obj:`True`, the values in
:attr:`self.defaults <dom_toml.parser.AbstractConfigParser.defaults>` and
:attr:`self.factories <dom_toml.parser.AbstractConfigParser.factories>`
will be set as defaults for the returned mapping.
:rtype:
.. latex:clearpage::
"""

parsed_config = {key: self.parse_group(value) for key, value in config.items()}

return cast(DependencyGroupsDict, parsed_config)


class PEP621Parser(RequiredKeysConfigParser):
"""
Parser for :pep:`621` metadata from ``pyproject.toml``.
Expand Down Expand Up @@ -896,10 +951,6 @@ def parse_entry_points(self, config: Dict[str, TOML_TYPES]) -> Dict[str, Dict[st
nbval = "nbval.plugin"
:param config: The unparsed TOML config for the :pep621:`project table <table-name>`.
:rtype:
.. latex:clearpage::
"""

entry_points = config["entry-points"]
Expand Down
25 changes: 23 additions & 2 deletions pyproject_parser/type_hints.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
#

# stdlib
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, Union

# 3rd party
from packaging.markers import Marker
Expand All @@ -40,6 +40,8 @@

__all__ = [
"BuildSystemDict",
"IncludeGroupDict",
"DependencyGroupsDict",
"Dynamic",
"ProjectDict",
"Author",
Expand All @@ -57,6 +59,20 @@
}
)

IncludeGroupDict = TypedDict("IncludeGroupDict", {"include-group": str})
"""
:class:`typing.TypedDict`.
.. versionadded:: 0.13.0
"""

DependencyGroupsDict = Dict[str, List[Union[str, IncludeGroupDict]]]
"""
The return type from the :class:`~.DependencyGroupsParser` class.
.. versionadded:: 0.13.0
"""

#: Type hint for the :pep621:`dynamic` field defined in :pep:`621`.
Dynamic = Literal[
"name",
Expand Down Expand Up @@ -130,5 +146,10 @@ class ReadmeDict(TypedDict, total=False):

_PyProjectAsTomlDict = TypedDict(
"_PyProjectAsTomlDict",
{"build-system": Optional[BuildSystemDict], "project": Optional[ProjectDict], "tool": Dict[str, Any]},
{
"build-system": Optional[BuildSystemDict],
"project": Optional[ProjectDict],
"tool": Dict[str, Any],
"dependency-groups": Optional[DependencyGroupsDict]
},
)
9 changes: 9 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Callable, Type, TypeVar, Union

# 3rd party
from dom_toml.decoder import InlineTableDict
from packaging.markers import Marker
from packaging.requirements import Requirement
from packaging.specifiers import SpecifierSet
Expand Down Expand Up @@ -46,3 +47,11 @@ def represent_readme_or_license( # noqa: MAN002
data: Union[Readme, License],
):
return dumper.represent_dict(data.to_dict())


@_representer_for(InlineTableDict)
def represent_inline_table( # noqa: MAN002
dumper: RegressionYamlDumper,
data: InlineTableDict,
):
return dumper.represent_dict(dict(data))
12 changes: 11 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
from pyproject_parser.cli import ConfigTracebackHandler
from tests.test_dumping import COMPLETE_UNDERSCORE_NAME, UNORDERED

COMPLETE_DEPENDENCY_GROUPS = COMPLETE_A + """
[dependency-groups]
test = ["pytest", "coverage"]
docs = ["sphinx", "sphinx-rtd-theme"]
typing = ["mypy", "types-requests"]
typing-test = [{include-group = "typing"}, {include-group = "test"}, "useful-types"]
"""


@pytest.mark.parametrize(
"toml_string",
Expand All @@ -37,6 +46,7 @@
pytest.param(COMPLETE_PROJECT_A, id="COMPLETE_PROJECT_A"),
pytest.param(UNORDERED, id="UNORDERED"),
pytest.param(COMPLETE_UNDERSCORE_NAME, id="COMPLETE_UNDERSCORE_NAME"),
pytest.param(COMPLETE_DEPENDENCY_GROUPS, id="COMPLETE_DEPENDENCY_GROUPS"),
]
)
@pytest.mark.parametrize("show_diff", [True, False])
Expand Down Expand Up @@ -177,7 +187,7 @@ def test_check_extra_deprecation_warning(
),
pytest.param(
"[coverage]\nomit = 'demo.py'\n[flake8]\nselect = ['F401']",
"Unexpected top-level key 'coverage'. Only 'build-system', 'project' and 'tool' are allowed.",
"Unexpected top-level key 'coverage'. Only 'build-system', 'dependency-groups', 'project' and 'tool' are allowed.",
id="top-level",
),
pytest.param(
Expand Down
2 changes: 1 addition & 1 deletion tests/test_cli_/test_check_error_caught_top_level_.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
BadConfigError: Unexpected top-level key 'coverage'. Only 'build-system', 'project' and 'tool' are allowed.
BadConfigError: Unexpected top-level key 'coverage'. Only 'build-system', 'dependency-groups', 'project' and 'tool' are allowed.
Use '--traceback' to view the full traceback.
Aborted!
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Reformatting 'pyproject.toml'
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[build-system]
requires = [ "whey",]
build-backend = "whey"

[project]
name = "whey"
version = "2021.0.0"
description = "A simple Python wheel builder for simple projects."
keywords = [ "build", "distribution", "packaging", "pep517", "pep621", "sdist", "wheel",]
dependencies = [ 'django>2.1; os_name != "nt"', 'django>2.0; os_name == "nt"', "gidgethub[httpx]>4.0.0", "httpx",]
dynamic = [ "classifiers", "requires-python",]

[[project.authors]]
name = "Dominic Davis-Foster"
email = "[email protected]"

[project.urls]
Homepage = "https://whey.readthedocs.io/en/latest"
Documentation = "https://whey.readthedocs.io/en/latest"
"Issue Tracker" = "https://github.com/repo-helper/whey/issues"
"Source Code" = "https://github.com/repo-helper/whey"

[tool.whey]
base-classifiers = [ "Development Status :: 4 - Beta",]
python-versions = [ "3.6", "3.7", "3.8", "3.9", "3.10",]
python-implementations = [ "CPython", "PyPy",]
platforms = [ "Windows", "macOS", "Linux",]
license-key = "MIT"

[dependency-groups]
test = [ "pytest", "coverage",]
docs = [ "sphinx", "sphinx-rtd-theme",]
typing = [ "mypy", "types-requests",]
typing-test = [ { include-group = "typing" }, { include-group = "test" }, "useful-types",]
Loading

0 comments on commit eead325

Please sign in to comment.