gh-143387: Raise an exception instead of returning None when metadata file is missing. by jaraco · Pull Request #146234 · python/cpython · GitHub
Skip to content
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
35 changes: 27 additions & 8 deletions Lib/importlib/metadata/__init__.py
118 changes: 118 additions & 0 deletions Lib/importlib/metadata/_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
from __future__ import annotations

import functools
import operator


# from jaraco.context 6.1
class ExceptionTrap:
"""
A context manager that will catch certain exceptions and provide an
indication they occurred.

>>> with ExceptionTrap() as trap:
... raise Exception()
>>> bool(trap)
True

>>> with ExceptionTrap() as trap:
... pass
>>> bool(trap)
False

>>> with ExceptionTrap(ValueError) as trap:
... raise ValueError("1 + 1 is not 3")
>>> bool(trap)
True
>>> trap.value
ValueError('1 + 1 is not 3')
>>> trap.tb
<traceback object at ...>

>>> with ExceptionTrap(ValueError) as trap:
... raise Exception()
Traceback (most recent call last):
...
Exception

>>> bool(trap)
False
"""

exc_info = None, None, None

def __init__(self, exceptions=(Exception,)):
self.exceptions = exceptions

def __enter__(self):
return self

@property
def type(self):
return self.exc_info[0]

@property
def value(self):
return self.exc_info[1]

@property
def tb(self):
return self.exc_info[2]

def __exit__(self, *exc_info):
type = exc_info[0]
matches = type and issubclass(type, self.exceptions)
if matches:
self.exc_info = exc_info
return matches

def __bool__(self):
return bool(self.type)

def raises(self, func, *, _test=bool):
"""
Wrap func and replace the result with the truth
value of the trap (True if an exception occurred).

First, give the decorator an alias to support Python 3.8
Syntax.

>>> raises = ExceptionTrap(ValueError).raises

Now decorate a function that always fails.

>>> @raises
... def fail():
... raise ValueError('failed')
>>> fail()
True
"""

@functools.wraps(func)
def wrapper(*args, **kwargs):
with ExceptionTrap(self.exceptions) as trap:
func(*args, **kwargs)
return _test(trap)

return wrapper

def passes(self, func):
"""
Wrap func and replace the result with the truth
value of the trap (True if no exception).

First, give the decorator an alias to support Python 3.8
Syntax.

>>> passes = ExceptionTrap(ValueError).passes

Now decorate a function that always fails.

>>> @passes
... def fail():
... raise ValueError('failed')

>>> fail()
False
"""
return self.raises(func, _test=operator.not_)
11 changes: 7 additions & 4 deletions Lib/test/test_importlib/metadata/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from importlib.metadata import (
Distribution,
EntryPoint,
MetadataNotFound,
PackageNotFoundError,
_unique,
distributions,
Expand Down Expand Up @@ -159,13 +160,15 @@ def test_valid_dists_preferred(self):

def test_missing_metadata(self):
"""
Dists with a missing metadata file should return None.
Dists with a missing metadata file should raise ``MetadataNotFound``.

Ref python/importlib_metadata#493.
Ref python/importlib_metadata#493 and python/cpython#143387.
"""
fixtures.build_files(self.make_pkg('foo-4.3', files={}), self.site_dir)
assert Distribution.from_name('foo').metadata is None
assert metadata('foo') is None
with self.assertRaises(MetadataNotFound):
Distribution.from_name('foo').metadata
with self.assertRaises(MetadataNotFound):
metadata('foo')


class NonASCIITests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase):
Expand Down
Loading