Skip to content
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

Correctly use the ExceptionGroup shim only when needed #736

Merged
merged 5 commits into from Oct 18, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Expand Up @@ -10,6 +10,8 @@ Changelog
* Fix errors when trying to access the ``description_content_type``, ``keywords``,
and ``requires_python`` attributes on ``metadata.Metadata`` when those values
have not been provided (:issue:`733`)
* Fix a bug preventing the use of the built in ``ExceptionGroup`` on versions of
Python that support it (:issue:`725`)

23.2 - 2023-10-01
~~~~~~~~~~~~~~~~~
Expand Down
18 changes: 11 additions & 7 deletions src/packaging/metadata.py
Expand Up @@ -41,10 +41,10 @@ def __init_subclass__(*_args, **_kwargs):


try:
ExceptionGroup = __builtins__.ExceptionGroup # type: ignore[attr-defined]
except AttributeError:
ExceptionGroup
except NameError: # pragma: no cover

class ExceptionGroup(Exception): # type: ignore[no-redef] # noqa: N818
class ExceptionGroup(Exception): # noqa: N818
"""A minimal implementation of :external:exc:`ExceptionGroup` from Python 3.11.

If :external:exc:`ExceptionGroup` is already defined by Python itself,
Expand All @@ -61,6 +61,9 @@ def __init__(self, message: str, exceptions: List[Exception]) -> None:
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.message!r}, {self.exceptions!r})"

else: # pragma: no cover
ExceptionGroup = ExceptionGroup


class InvalidMetadata(ValueError):
"""A metadata field contains invalid data."""
Expand Down Expand Up @@ -672,7 +675,7 @@ def from_raw(cls, data: RawMetadata, *, validate: bool = True) -> "Metadata":
ins._raw = data.copy() # Mutations occur due to caching enriched values.

if validate:
exceptions: List[InvalidMetadata] = []
exceptions: List[Exception] = []
try:
metadata_version = ins.metadata_version
metadata_age = _VALID_METADATA_VERSIONS.index(metadata_version)
Expand Down Expand Up @@ -727,10 +730,10 @@ def from_email(
If *validate* is true, the metadata will be validated. All exceptions
related to validation will be gathered and raised as an :class:`ExceptionGroup`.
"""
exceptions: list[InvalidMetadata] = []
raw, unparsed = parse_email(data)

if validate:
exceptions: list[Exception] = []
for unparsed_key in unparsed:
if unparsed_key in _EMAIL_TO_RAW_MAPPING:
message = f"{unparsed_key!r} has invalid data"
Expand All @@ -744,8 +747,9 @@ def from_email(
try:
return cls.from_raw(raw, validate=validate)
except ExceptionGroup as exc_group:
exceptions.extend(exc_group.exceptions)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was never going to do anything, because exceptions was always going to be an empty list, and it was making my editor sad because ExceptionGroup.exceptions has an incompatible (with list[InvalidMetdata]) type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, guess fixing this also made MyPy just generally unhappy.

raise ExceptionGroup("invalid or unparsed metadata", exceptions) from None
raise ExceptionGroup(
"invalid or unparsed metadata", exc_group.exceptions
) from None

metadata_version: _Validator[_MetadataVersion] = _Validator()
""":external:ref:`core-metadata-metadata-version`
Expand Down
2 changes: 1 addition & 1 deletion tests/test_metadata.py
Expand Up @@ -251,7 +251,7 @@ def test_attributes(self):
individual_exception = Exception("not important")
exc = metadata.ExceptionGroup("message", [individual_exception])
assert exc.message == "message"
assert exc.exceptions == [individual_exception]
assert list(exc.exceptions) == [individual_exception]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The built in ExceptionGroup.exceptions is a tuple instead of a list.


def test_repr(self):
individual_exception = RuntimeError("not important")
Expand Down