Skip to content

Commit 4cf313d

Browse files
fix #544: Correctly pass StopIteration trough wrappers
Raising a StopIteration in a generator triggers a RuntimeError. If the RuntimeError of a generator has the passed in StopIteration as cause resume with that StopIteration as normal exception instead of failing with the RuntimeError.
1 parent 145eea8 commit 4cf313d

File tree

3 files changed

+41
-1
lines changed

3 files changed

+41
-1
lines changed

changelog/544.bugfix.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Correctly pass StopIteration trough wrappers.
2+
3+
Raising a StopIteration in a generator triggers a RuntimeError.
4+
If the RuntimeError of a generator has the passed in StopIteration as cause
5+
resume with that StopIteration as normal exception instead of failing with the RuntimeError.

src/pluggy/_callers.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,17 @@ def _multicall(
119119
for teardown in reversed(teardowns):
120120
try:
121121
if exception is not None:
122-
teardown.throw(exception) # type: ignore[union-attr]
122+
try:
123+
teardown.throw(exception) # type: ignore[union-attr]
124+
except RuntimeError as re:
125+
if (
126+
isinstance(exception, StopIteration)
127+
and re.__cause__ is exception
128+
):
129+
teardown.close() # type: ignore[union-attr]
130+
continue
131+
else:
132+
raise
123133
else:
124134
teardown.send(result) # type: ignore[union-attr]
125135
# Following is unreachable for a well behaved hook wrapper.

testing/test_multicall.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,31 @@ def m2():
416416
]
417417

418418

419+
def test_wrapper_stopiteration_passtrough():
420+
out = []
421+
422+
@hookimpl(wrapper=True)
423+
def wrap():
424+
out.append("wrap")
425+
try:
426+
yield
427+
finally:
428+
out.append("wrap done")
429+
430+
@hookimpl
431+
def stop():
432+
out.append("stop")
433+
raise StopIteration
434+
435+
with pytest.raises(StopIteration):
436+
try:
437+
MC([stop, wrap], {})
438+
finally:
439+
out.append("finally")
440+
441+
assert out == ["wrap", "stop", "wrap done", "finally"]
442+
443+
419444
def test_suppress_inner_wrapper_teardown_exc() -> None:
420445
out = []
421446

0 commit comments

Comments
 (0)