Skip to content

Add parser for [dependency-groups] #78

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading