Skip to content

Commit feeee28

Browse files
Merge pull request #1586 from nicoddemus/issue-1461-merge-yield-fixture
Make normal fixtures work with "yield"
2 parents 9232389 + bdc2996 commit feeee28

File tree

7 files changed

+138
-230
lines changed

7 files changed

+138
-230
lines changed

CHANGELOG.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@
3636

3737
**Changes**
3838

39+
* Fixtures marked with ``@pytest.fixture`` can now use ``yield`` statements exactly like
40+
those marked with the ``@pytest.yield_fixture`` decorator. This change renders
41+
``@pytest.yield_fixture`` deprecated and makes ``@pytest.fixture`` with ``yield`` statements
42+
the preferred way to write teardown code (`#1461`_).
43+
Thanks `@csaftoiu`_ for bringing this to attention and `@nicoddemus`_ for the PR.
44+
3945
* Fix (`#1351`_):
4046
explicitly passed parametrize ids do not get escaped to ascii.
4147
Thanks `@ceridwen`_ for the PR.
@@ -58,6 +64,7 @@
5864
*
5965

6066
.. _@milliams: https://github.com/milliams
67+
.. _@csaftoiu: https://github.com/csaftoiu
6168
.. _@novas0x2a: https://github.com/novas0x2a
6269
.. _@kalekundert: https://github.com/kalekundert
6370
.. _@tareqalayan: https://github.com/tareqalayan
@@ -72,6 +79,7 @@
7279
.. _#1441: https://github.com/pytest-dev/pytest/pull/1441
7380
.. _#1454: https://github.com/pytest-dev/pytest/pull/1454
7481
.. _#1351: https://github.com/pytest-dev/pytest/issues/1351
82+
.. _#1461: https://github.com/pytest-dev/pytest/pull/1461
7583
.. _#1468: https://github.com/pytest-dev/pytest/pull/1468
7684
.. _#1474: https://github.com/pytest-dev/pytest/pull/1474
7785
.. _#1502: https://github.com/pytest-dev/pytest/pull/1502

_pytest/python.py

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,10 @@ def safe_getattr(object, name, default):
116116

117117

118118
class FixtureFunctionMarker:
119-
def __init__(self, scope, params,
120-
autouse=False, yieldctx=False, ids=None, name=None):
119+
def __init__(self, scope, params, autouse=False, ids=None, name=None):
121120
self.scope = scope
122121
self.params = params
123122
self.autouse = autouse
124-
self.yieldctx = yieldctx
125123
self.ids = ids
126124
self.name = name
127125

@@ -166,6 +164,10 @@ def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
166164
to resolve this is to name the decorated function
167165
``fixture_<fixturename>`` and then use
168166
``@pytest.fixture(name='<fixturename>')``.
167+
168+
Fixtures can optionally provide their values to test functions using a ``yield`` statement,
169+
instead of ``return``. In this case, the code block after the ``yield`` statement is executed
170+
as teardown code regardless of the test outcome. A fixture function must yield exactly once.
169171
"""
170172
if callable(scope) and params is None and autouse == False:
171173
# direct decoration
@@ -175,22 +177,19 @@ def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
175177
params = list(params)
176178
return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
177179

178-
def yield_fixture(scope="function", params=None, autouse=False, ids=None):
179-
""" (return a) decorator to mark a yield-fixture factory function
180-
(EXPERIMENTAL).
181180

182-
This takes the same arguments as :py:func:`pytest.fixture` but
183-
expects a fixture function to use a ``yield`` instead of a ``return``
184-
statement to provide a fixture. See
185-
http://pytest.org/en/latest/yieldfixture.html for more info.
181+
def yield_fixture(scope="function", params=None, autouse=False, ids=None, name=None):
182+
""" (return a) decorator to mark a yield-fixture factory function.
183+
184+
.. deprecated:: 1.10
185+
Use :py:func:`pytest.fixture` directly instead.
186186
"""
187-
if callable(scope) and params is None and autouse == False:
187+
if callable(scope) and params is None and not autouse:
188188
# direct decoration
189189
return FixtureFunctionMarker(
190-
"function", params, autouse, yieldctx=True)(scope)
190+
"function", params, autouse, ids=ids, name=name)(scope)
191191
else:
192-
return FixtureFunctionMarker(scope, params, autouse,
193-
yieldctx=True, ids=ids)
192+
return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
194193

195194
defaultfuncargprefixmarker = fixture()
196195

@@ -2287,7 +2286,6 @@ def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
22872286
assert not name.startswith(self._argprefix)
22882287
fixturedef = FixtureDef(self, nodeid, name, obj,
22892288
marker.scope, marker.params,
2290-
yieldctx=marker.yieldctx,
22912289
unittest=unittest, ids=marker.ids)
22922290
faclist = self._arg2fixturedefs.setdefault(name, [])
22932291
if fixturedef.has_location:
@@ -2325,38 +2323,30 @@ def fail_fixturefunc(fixturefunc, msg):
23252323
pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location,
23262324
pytrace=False)
23272325

2328-
def call_fixture_func(fixturefunc, request, kwargs, yieldctx):
2326+
def call_fixture_func(fixturefunc, request, kwargs):
2327+
yieldctx = is_generator(fixturefunc)
23292328
if yieldctx:
2330-
if not is_generator(fixturefunc):
2331-
fail_fixturefunc(fixturefunc,
2332-
msg="yield_fixture requires yield statement in function")
2333-
iter = fixturefunc(**kwargs)
2334-
next = getattr(iter, "__next__", None)
2335-
if next is None:
2336-
next = getattr(iter, "next")
2337-
res = next()
2329+
it = fixturefunc(**kwargs)
2330+
res = next(it)
2331+
23382332
def teardown():
23392333
try:
2340-
next()
2334+
next(it)
23412335
except StopIteration:
23422336
pass
23432337
else:
23442338
fail_fixturefunc(fixturefunc,
23452339
"yield_fixture function has more than one 'yield'")
2340+
23462341
request.addfinalizer(teardown)
23472342
else:
2348-
if is_generator(fixturefunc):
2349-
fail_fixturefunc(fixturefunc,
2350-
msg="pytest.fixture functions cannot use ``yield``. "
2351-
"Instead write and return an inner function/generator "
2352-
"and let the consumer call and iterate over it.")
23532343
res = fixturefunc(**kwargs)
23542344
return res
23552345

23562346
class FixtureDef:
23572347
""" A container for a factory definition. """
23582348
def __init__(self, fixturemanager, baseid, argname, func, scope, params,
2359-
yieldctx, unittest=False, ids=None):
2349+
unittest=False, ids=None):
23602350
self._fixturemanager = fixturemanager
23612351
self.baseid = baseid or ''
23622352
self.has_location = baseid is not None
@@ -2367,7 +2357,6 @@ def __init__(self, fixturemanager, baseid, argname, func, scope, params,
23672357
self.params = params
23682358
startindex = unittest and 1 or None
23692359
self.argnames = getfuncargnames(func, startindex=startindex)
2370-
self.yieldctx = yieldctx
23712360
self.unittest = unittest
23722361
self.ids = ids
23732362
self._finalizer = []
@@ -2428,8 +2417,7 @@ def execute(self, request):
24282417
fixturefunc = fixturefunc.__get__(request.instance)
24292418

24302419
try:
2431-
result = call_fixture_func(fixturefunc, request, kwargs,
2432-
self.yieldctx)
2420+
result = call_fixture_func(fixturefunc, request, kwargs)
24332421
except Exception:
24342422
self.cached_result = (None, my_cache_key, sys.exc_info())
24352423
raise

doc/en/example/costlysetup/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
@pytest.fixture("session")
55
def setup(request):
66
setup = CostlySetup()
7-
request.addfinalizer(setup.finalize)
8-
return setup
7+
yield setup
8+
setup.finalize()
99

1010
class CostlySetup:
1111
def __init__(self):

doc/en/example/simple.rst

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -648,15 +648,14 @@ here is a little example implemented via a local plugin::
648648

649649
@pytest.fixture
650650
def something(request):
651-
def fin():
652-
# request.node is an "item" because we use the default
653-
# "function" scope
654-
if request.node.rep_setup.failed:
655-
print ("setting up a test failed!", request.node.nodeid)
656-
elif request.node.rep_setup.passed:
657-
if request.node.rep_call.failed:
658-
print ("executing test failed", request.node.nodeid)
659-
request.addfinalizer(fin)
651+
yield
652+
# request.node is an "item" because we use the default
653+
# "function" scope
654+
if request.node.rep_setup.failed:
655+
print ("setting up a test failed!", request.node.nodeid)
656+
elif request.node.rep_setup.passed:
657+
if request.node.rep_call.failed:
658+
print ("executing test failed", request.node.nodeid)
660659

661660

662661
if you then have failing tests::

doc/en/fixture.rst

Lines changed: 69 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,6 @@ both styles, moving incrementally from classic to new style, as you
3434
prefer. You can also start out from existing :ref:`unittest.TestCase
3535
style <unittest.TestCase>` or :ref:`nose based <nosestyle>` projects.
3636

37-
.. note::
38-
39-
pytest-2.4 introduced an additional :ref:`yield fixture mechanism
40-
<yieldfixture>` for easier context manager integration and more linear
41-
writing of teardown code.
4237

4338
.. _`funcargs`:
4439
.. _`funcarg mechanism`:
@@ -247,9 +242,8 @@ Fixture finalization / executing teardown code
247242
-------------------------------------------------------------
248243

249244
pytest supports execution of fixture specific finalization code
250-
when the fixture goes out of scope. By accepting a ``request`` object
251-
into your fixture function you can call its ``request.addfinalizer`` one
252-
or multiple times::
245+
when the fixture goes out of scope. By using a ``yield`` statement instead of ``return``, all
246+
the code after the *yield* statement serves as the teardown code.::
253247

254248
# content of conftest.py
255249

@@ -259,14 +253,12 @@ or multiple times::
259253
@pytest.fixture(scope="module")
260254
def smtp(request):
261255
smtp = smtplib.SMTP("smtp.gmail.com")
262-
def fin():
263-
print ("teardown smtp")
264-
smtp.close()
265-
request.addfinalizer(fin)
266-
return smtp # provide the fixture value
256+
yield smtp # provide the fixture value
257+
print("teardown smtp")
258+
smtp.close()
267259

268-
The ``fin`` function will execute when the last test using
269-
the fixture in the module has finished execution.
260+
The ``print`` and ``smtp.close()`` statements will execute when the last test using
261+
the fixture in the module has finished execution, regardless of the exception status of the tests.
270262

271263
Let's execute it::
272264

@@ -282,14 +274,55 @@ occur around each single test. In either case the test
282274
module itself does not need to change or know about these details
283275
of fixture setup.
284276

277+
Note that we can also seamlessly use the ``yield`` syntax with ``with`` statements::
285278

286-
Finalization/teardown with yield fixtures
287-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
279+
# content of test_yield2.py
288280

289-
Another alternative to the *request.addfinalizer()* method is to use *yield
290-
fixtures*. All the code after the *yield* statement serves as the teardown
291-
code. See the :ref:`yield fixture documentation <yieldfixture>`.
281+
import pytest
282+
283+
@pytest.fixture
284+
def passwd():
285+
with open("/etc/passwd") as f:
286+
yield f.readlines()
292287

288+
def test_has_lines(passwd):
289+
assert len(passwd) >= 1
290+
291+
The file ``f`` will be closed after the test finished execution
292+
because the Python ``file`` object supports finalization when
293+
the ``with`` statement ends.
294+
295+
296+
.. note::
297+
Prior to version 2.10, in order to use a ``yield`` statement to execute teardown code one
298+
had to mark a fixture using the ``yield_fixture`` marker. From 2.10 onward, normal
299+
fixtures can use ``yield`` directly so the ``yield_fixture`` decorator is no longer needed
300+
and considered deprecated.
301+
302+
.. note::
303+
As historical note, another way to write teardown code is
304+
by accepting a ``request`` object into your fixture function and can call its
305+
``request.addfinalizer`` one or multiple times::
306+
307+
# content of conftest.py
308+
309+
import smtplib
310+
import pytest
311+
312+
@pytest.fixture(scope="module")
313+
def smtp(request):
314+
smtp = smtplib.SMTP("smtp.gmail.com")
315+
def fin():
316+
print ("teardown smtp")
317+
smtp.close()
318+
request.addfinalizer(fin)
319+
return smtp # provide the fixture value
320+
321+
The ``fin`` function will execute when the last test using
322+
the fixture in the module has finished execution.
323+
324+
This method is still fully supported, but ``yield`` is recommended from 2.10 onward because
325+
it is considered simpler and better describes the natural code flow.
293326

294327
.. _`request-context`:
295328

@@ -309,12 +342,9 @@ read an optional server URL from the test module which uses our fixture::
309342
def smtp(request):
310343
server = getattr(request.module, "smtpserver", "smtp.gmail.com")
311344
smtp = smtplib.SMTP(server)
312-
313-
def fin():
314-
print ("finalizing %s (%s)" % (smtp, server))
315-
smtp.close()
316-
request.addfinalizer(fin)
317-
return smtp
345+
yield smtp
346+
print ("finalizing %s (%s)" % (smtp, server))
347+
smtp.close()
318348

319349
We use the ``request.module`` attribute to optionally obtain an
320350
``smtpserver`` attribute from the test module. If we just execute
@@ -351,7 +381,7 @@ from the module namespace.
351381

352382
.. _`fixture-parametrize`:
353383

354-
Parametrizing a fixture
384+
Parametrizing fixtures
355385
-----------------------------------------------------------------
356386

357387
Fixture functions can be parametrized in which case they will be called
@@ -374,11 +404,9 @@ through the special :py:class:`request <FixtureRequest>` object::
374404
params=["smtp.gmail.com", "mail.python.org"])
375405
def smtp(request):
376406
smtp = smtplib.SMTP(request.param)
377-
def fin():
378-
print ("finalizing %s" % smtp)
379-
smtp.close()
380-
request.addfinalizer(fin)
381-
return smtp
407+
yield smtp
408+
print ("finalizing %s" % smtp)
409+
smtp.close()
382410

383411
The main change is the declaration of ``params`` with
384412
:py:func:`@pytest.fixture <_pytest.python.fixture>`, a list of values
@@ -586,19 +614,15 @@ to show the setup/teardown flow::
586614
def modarg(request):
587615
param = request.param
588616
print (" SETUP modarg %s" % param)
589-
def fin():
590-
print (" TEARDOWN modarg %s" % param)
591-
request.addfinalizer(fin)
592-
return param
617+
yield param
618+
print (" TEARDOWN modarg %s" % param)
593619

594620
@pytest.fixture(scope="function", params=[1,2])
595621
def otherarg(request):
596622
param = request.param
597623
print (" SETUP otherarg %s" % param)
598-
def fin():
599-
print (" TEARDOWN otherarg %s" % param)
600-
request.addfinalizer(fin)
601-
return param
624+
yield param
625+
print (" TEARDOWN otherarg %s" % param)
602626

603627
def test_0(otherarg):
604628
print (" RUN test0 with otherarg %s" % otherarg)
@@ -777,7 +801,8 @@ self-contained implementation of this idea::
777801
@pytest.fixture(autouse=True)
778802
def transact(self, request, db):
779803
db.begin(request.function.__name__)
780-
request.addfinalizer(db.rollback)
804+
yield
805+
db.rollback()
781806

782807
def test_method1(self, db):
783808
assert db.intransaction == ["test_method1"]
@@ -817,10 +842,11 @@ active. The canonical way to do that is to put the transact definition
817842
into a conftest.py file **without** using ``autouse``::
818843

819844
# content of conftest.py
820-
@pytest.fixture()
845+
@pytest.fixture
821846
def transact(self, request, db):
822847
db.begin()
823-
request.addfinalizer(db.rollback)
848+
yield
849+
db.rollback()
824850

825851
and then e.g. have a TestClass using it by declaring the need::
826852

0 commit comments

Comments
 (0)