Skip to content

Commit cbd044e

Browse files
committed
Re-use ExceptionTrap for trapping exceptions.
1 parent ede3fcc commit cbd044e

File tree

3 files changed

+123
-5
lines changed

3 files changed

+123
-5
lines changed

importlib_metadata/__init__.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
NullFinder,
3636
install,
3737
)
38+
from ._context import ExceptionTrap
3839
from ._functools import method_cache, noop, pass_none, passthrough
3940
from ._itertools import always_iterable, bucket, unique_everseen
4041
from ._meta import PackageMetadata, SimplePath
@@ -496,11 +497,9 @@ def _prefer_valid(dists: Iterable[Distribution]) -> Iterable[Distribution]:
496497
Ref python/importlib_resources#489.
497498
"""
498499

499-
def has_metadata(dist: Distribution) -> bool:
500-
with suppress(MetadataNotFound):
501-
dist.metadata
502-
return True
503-
return False
500+
has_metadata = ExceptionTrap(MetadataNotFound).passes(
501+
lambda dist: dist.metadata
502+
)
504503

505504
buckets = bucket(dists, has_metadata)
506505
return itertools.chain(buckets[True], buckets[False])

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_)

newsfragments/+.removal.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
- Removed the internal ``md_none`` typing helper since ``Distribution.metadata`` now always returns ``PackageMetadata`` and raises ``MetadataNotFound`` when absent (python/cpython#143387).
2+
- Vendored ``ExceptionTrap`` from ``jaraco.context`` (as ``_context``) and now rely on its ``passes`` helper when checking for missing metadata, keeping behavior aligned without adding dependencies.

0 commit comments

Comments
 (0)