File: errors.py

package info (click to toggle)
pyproject-metadata 0.10.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 344 kB
  • sloc: python: 2,704; makefile: 5
file content (135 lines) | stat: -rw-r--r-- 3,845 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# SPDX-License-Identifier: MIT

"""
This module defines exceptions and error handling utilities. It is the
recommend path to access ``ConfiguratonError``, ``ConfigurationWarning``, and
``ExceptionGroup``. For backward compatibility, ``ConfigurationError`` is
re-exported in the top-level package.
"""

from __future__ import annotations

import builtins
import contextlib
import dataclasses
import sys
import typing
import warnings

__all__ = [
    "ConfigurationError",
    "ConfigurationWarning",
    "ExceptionGroup",
]


def __dir__() -> list[str]:
    return __all__


class ConfigurationError(Exception):
    """
    Error in the backend metadata.

    Has an optional key attribute, which will be non-None if the error is
    related to a single key in the pyproject.toml file.
    """

    def __init__(self, msg: str, *, key: str | None = None) -> None:
        """
        Create a new error with a key (can be None).
        """
        super().__init__(msg)
        self._key = key

    @property
    def key(self) -> str | None:  # pragma: no cover
        """
        Return the stored key.
        """
        return self._key


class ConfigurationWarning(UserWarning):
    """Warnings about backend metadata."""


if sys.version_info >= (3, 11):
    ExceptionGroup = builtins.ExceptionGroup
else:

    class ExceptionGroup(Exception):  # noqa: N818
        """A minimal implementation of `ExceptionGroup` from Python 3.11.

        Users can replace this with a more complete implementation, such as from
        the exceptiongroup backport package, if better error messages and
        integration with tooling is desired and the addition of a dependency is
        acceptable.
        """

        message: str
        exceptions: list[Exception]

        def __init__(self, message: str, exceptions: list[Exception]) -> None:
            """
            Create a new group with a message and a list of exceptions.
            """
            self.message = message
            self.exceptions = exceptions

        def __repr__(self) -> str:
            """
            Return a repr similar to the stdlib ExceptionGroup.
            """
            return f"{self.__class__.__name__}({self.message!r}, {self.exceptions!r})"


@dataclasses.dataclass
class ErrorCollector:
    """
    Collect errors and raise them as a group at the end (if collect_errors is True),
    otherwise raise them immediately.
    """

    collect_errors: bool
    errors: list[Exception] = dataclasses.field(default_factory=list)

    def config_error(
        self,
        msg: str,
        *,
        key: str | None = None,
        got: object = None,
        got_type: type[typing.Any] | None = None,
        warn: bool = False,
        **kwargs: object,
    ) -> None:
        """Raise a configuration error, or add it to the error list."""
        msg = msg.format(key=f'"{key}"', **kwargs)
        if got is not None:
            msg = f"{msg} (got {got!r})"
        if got_type is not None:
            msg = f"{msg} (got {got_type.__name__})"

        if warn:
            warnings.warn(msg, ConfigurationWarning, stacklevel=3)
        elif self.collect_errors:
            self.errors.append(ConfigurationError(msg, key=key))
        else:
            raise ConfigurationError(msg, key=key)

    def finalize(self, msg: str) -> None:
        """Raise a group exception if there are any errors."""
        if self.errors:
            raise ExceptionGroup(msg, self.errors)

    @contextlib.contextmanager
    def collect(self) -> typing.Generator[None, None, None]:
        """Support nesting; add any grouped errors to the error list."""
        if self.collect_errors:
            try:
                yield
            except ExceptionGroup as error:
                self.errors.extend(error.exceptions)
        else:
            yield