gh-143387: Raise an exception instead of returning None when metadata… · python/cpython@f5d47fc · GitHub
Skip to content

Commit f5d47fc

Browse files
authored
gh-143387: Raise an exception instead of returning None when metadata file is missing. (#146234)
1 parent 1114d7f commit f5d47fc

4 files changed

Lines changed: 159 additions & 12 deletions

File tree

Lib/importlib/metadata/__init__.py

Lines changed: 27 additions & 8 deletions

Lib/importlib/metadata/_context.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
from __future__ import annotations
2+
3+
import functools
4+
import operator
5+
6+
7+
# from jaraco.context 6.1
8+
class ExceptionTrap:
9+
"""
10+
A context manager that will catch certain exceptions and provide an
11+
indication they occurred.
12+
13+
>>> with ExceptionTrap() as trap:
14+
... raise Exception()
15+
>>> bool(trap)
16+
True
17+
18+
>>> with ExceptionTrap() as trap:
19+
... pass
20+
>>> bool(trap)
21+
False
22+
23+
>>> with ExceptionTrap(ValueError) as trap:
24+
... raise ValueError("1 + 1 is not 3")
25+
>>> bool(trap)
26+
True
27+
>>> trap.value
28+
ValueError('1 + 1 is not 3')
29+
>>> trap.tb
30+
<traceback object at ...>
31+
32+
>>> with ExceptionTrap(ValueError) as trap:
33+
... raise Exception()
34+
Traceback (most recent call last):
35+
...
36+
Exception
37+
38+
>>> bool(trap)
39+
False
40+
"""
41+
42+
exc_info = None, None, None
43+
44+
def __init__(self, exceptions=(Exception,)):
45+
self.exceptions = exceptions
46+
47+
def __enter__(self):
48+
return self
49+
50+
@property
51+
def type(self):
52+
return self.exc_info[0]
53+
54+
@property
55+
def value(self):
56+
return self.exc_info[1]
57+
58+
@property
59+
def tb(self):
60+
return self.exc_info[2]
61+
62+
def __exit__(self, *exc_info):
63+
type = exc_info[0]
64+
matches = type and issubclass(type, self.exceptions)
65+
if matches:
66+
self.exc_info = exc_info
67+
return matches
68+
69+
def __bool__(self):
70+
return bool(self.type)
71+
72+
def raises(self, func, *, _test=bool):
73+
"""
74+
Wrap func and replace the result with the truth
75+
value of the trap (True if an exception occurred).
76+
77+
First, give the decorator an alias to support Python 3.8
78+
Syntax.
79+
80+
>>> raises = ExceptionTrap(ValueError).raises
81+
82+
Now decorate a function that always fails.
83+
84+
>>> @raises
85+
... def fail():
86+
... raise ValueError('failed')
87+
>>> fail()
88+
True
89+
"""
90+
91+
@functools.wraps(func)
92+
def wrapper(*args, **kwargs):
93+
with ExceptionTrap(self.exceptions) as trap:
94+
func(*args, **kwargs)
95+
return _test(trap)
96+
97+
return wrapper
98+
99+
def passes(self, func):
100+
"""
101+
Wrap func and replace the result with the truth
102+
value of the trap (True if no exception).
103+
104+
First, give the decorator an alias to support Python 3.8
105+
Syntax.
106+
107+
>>> passes = ExceptionTrap(ValueError).passes
108+
109+
Now decorate a function that always fails.
110+
111+
>>> @passes
112+
... def fail():
113+
... raise ValueError('failed')
114+
115+
>>> fail()
116+
False
117+
"""
118+
return self.raises(func, _test=operator.not_)

Lib/test/test_importlib/metadata/test_main.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from importlib.metadata import (
1313
Distribution,
1414
EntryPoint,
15+
MetadataNotFound,
1516
PackageNotFoundError,
1617
_unique,
1718
distributions,
@@ -159,13 +160,15 @@ def test_valid_dists_preferred(self):
159160

160161
def test_missing_metadata(self):
161162
"""
162-
Dists with a missing metadata file should return None.
163+
Dists with a missing metadata file should raise ``MetadataNotFound``.
163164
164-
Ref python/importlib_metadata#493.
165+
Ref python/importlib_metadata#493 and python/cpython#143387.
165166
"""
166167
fixtures.build_files(self.make_pkg('foo-4.3', files={}), self.site_dir)
167-
assert Distribution.from_name('foo').metadata is None
168-
assert metadata('foo') is None
168+
with self.assertRaises(MetadataNotFound):
169+
Distribution.from_name('foo').metadata
170+
with self.assertRaises(MetadataNotFound):
171+
metadata('foo')
169172

170173

171174
class NonASCIITests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase):
Lines changed: 7 additions & 0 deletions

0 commit comments

Comments
 (0)