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 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Changelog

* Do specifier matching correctly when the specifier contains an epoch number
and has more components than the version (:issue:`683`)
* 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
Original file line number Diff line number Diff line change
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 @@ -677,7 +680,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 @@ -732,10 +735,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 @@ -749,8 +752,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
Original file line number Diff line number Diff line change
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