Skip to content

Commit 7440cec

Browse files
authored
Merge pull request #5589 from graingert/fixture-yield-exit-stack
de-emphasize request.addfinalizer Fixes #5587
2 parents f133287 + a96710d commit 7440cec

File tree

1 file changed

+48
-18
lines changed

1 file changed

+48
-18
lines changed

doc/en/fixture.rst

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,29 @@ The ``smtp_connection`` connection will be closed after the test finished
379379
execution because the ``smtp_connection`` object automatically closes when
380380
the ``with`` statement ends.
381381

382+
Using the contextlib.ExitStack context manager finalizers will always be called
383+
regardless if the fixture *setup* code raises an exception. This is handy to properly
384+
close all resources created by a fixture even if one of them fails to be created/acquired:
385+
386+
.. code-block:: python
387+
388+
# content of test_yield3.py
389+
390+
import contextlib
391+
392+
import pytest
393+
394+
from .utils import connect
395+
396+
397+
@pytest.fixture
398+
def equipments():
399+
with contextlib.ExitStack() as stack:
400+
yield [stack.enter_context(connect(port)) for port in ("C1", "C3", "C28")]
401+
402+
In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still
403+
be properly closed.
404+
382405
Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the
383406
*teardown* code (after the ``yield``) will not be called.
384407

@@ -407,27 +430,34 @@ Here's the ``smtp_connection`` fixture changed to use ``addfinalizer`` for clean
407430
return smtp_connection # provide the fixture value
408431
409432
410-
Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test
411-
ends, but ``addfinalizer`` has two key differences over ``yield``:
433+
Here's the ``equipments`` fixture changed to use ``addfinalizer`` for cleanup:
412434

413-
1. It is possible to register multiple finalizer functions.
435+
.. code-block:: python
414436
415-
2. Finalizers will always be called regardless if the fixture *setup* code raises an exception.
416-
This is handy to properly close all resources created by a fixture even if one of them
417-
fails to be created/acquired::
437+
# content of test_yield3.py
418438
419-
@pytest.fixture
420-
def equipments(request):
421-
r = []
422-
for port in ('C1', 'C3', 'C28'):
423-
equip = connect(port)
424-
request.addfinalizer(equip.disconnect)
425-
r.append(equip)
426-
return r
427-
428-
In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still
429-
be properly closed. Of course, if an exception happens before the finalize function is
430-
registered then it will not be executed.
439+
import contextlib
440+
import functools
441+
442+
import pytest
443+
444+
from .utils import connect
445+
446+
447+
@pytest.fixture
448+
def equipments(request):
449+
r = []
450+
for port in ("C1", "C3", "C28"):
451+
cm = connect(port)
452+
equip = cm.__enter__()
453+
request.addfinalizer(functools.partial(cm.__exit__, None, None, None))
454+
r.append(equip)
455+
return r
456+
457+
458+
Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test
459+
ends. Of course, if an exception happens before the finalize function is registered then it
460+
will not be executed.
431461

432462

433463
.. _`request-context`:

0 commit comments

Comments
 (0)