Avoid suppressing an exception accidentally inside a contextmanager
Catching an exception around yield without re-raising it makes the caller appear to have succeeded. In a generator decorated with @contextmanager, an exception from the with body is injected at the yield expression. If the generator catches that exception and then ends normally, the context manager signals that it handled the failure.
This fixture runs identical body code—raise Sentinel("sentinel")—against two exit implementations. logging_only records the failure but falls off the end of its except block, so run returns "returned". That is useful evidence of accidental suppression, not evidence that the operation succeeded. logging_and_reraise records the same kind of event and uses bare raise, preserving the original exception for the caller; run therefore returns "raised".
Use this structure when logging is observational rather than an explicit recovery policy: catch the narrow exception you intend to record, write the record, and re-raise. A context manager may intentionally suppress an error, but that decision should be visible in its name and contract. The assertions only establish propagation behavior for this synthetic sentinel; they do not test a logger, cleanup failures, or an application’s error policy.
contextmanager is available in modern Python versions; the documented generator exception behavior is described in the Python contextmanager documentation.
AI assistance disclosure: This article was drafted with AI assistance and checked using the deterministic fixture below.
from contextlib import contextmanager
class Sentinel(Exception):
pass
@contextmanager
def logging_only(events):
try:
yield
except Sentinel:
events.append("logged")
@contextmanager
def logging_and_reraise(events):
try:
yield
except Sentinel:
events.append("recorded")
raise
def run(manager, events):
try:
with manager(events):
raise Sentinel("sentinel")
except Sentinel:
return "raised"
return "returned"
logged_events = []
assert run(logging_only, logged_events) == "returned"
assert logged_events == ["logged"]
recorded_events = []
assert run(logging_and_reraise, recorded_events) == "raised"
assert recorded_events == ["recorded"]
print("logging-only: returned")
print("logging-only events: logged")
print("re-raising: raised")
print("re-raising events: recorded")
logging-only: returned
logging-only events: logged
re-raising: raised
re-raising events: recorded