py.test: Temporary folder for the session scope

Unfortunately there is currently no way (2014) of doing this nicely. In the future py.test will introduce a new "any" scope or something similar for this, but that's the future.

Right now you have to do this manually yourself. However as you note you lose quite a few nice features: symlinks in /tmp to the last test, auto cleanup after a few test runs, sensibly named directories etc. If the directory is not too expensive I usually combine a session and function scoped fixture in the following way:

@pytest.fixture(scope='session')
def session_dir(request):
    temp_dir = py.path.local(tempfile.mkdtemp())
    request.addfinalizer(lambda: folder.remove(rec=1))
    # Any extra setup here
    return temp_dir

@pytest.fixture
def temp_dir(session_dir, tmpdir):
    session_dir.copy(tmpdir)
    return tmpdir

This creates a temporary directory which gets cleaned up after a test run, however for each test which actually needs it (by requesting temp_dir) gets a copy which is saved with the tmpdir semantics.

If tests actually need to share state via this directory then the finalizer of temp_dir would have to copy things back to the session_dir. This is however not a very good idea since it makes the tests reliant on the execution order and would also cause problems when using pytest-xdist.


I add a finalizer when I want to delete all temporary folders created in session.

_tmp_factory = None
@pytest.fixture(scope="session")
def tmp_factory(request, tmpdir_factory):
    global _tmp_factory
    if _tmp_factory is None:
        _tmp_factory = tmpdir_factory
        request.addfinalizer(cleanup)
    return _tmp_factory

def cleanup():
    root = _tmp_factory.getbasetemp().strpath
    print "Cleaning all temporary folders from %s" % root
    shutil.rmtree(root)

def test_deleting_temp(tmp_factory):
    root_a = tmp_factory.mktemp('A')
    root_a.join('foo.txt').write('hello world A')

    root_b = tmp_factory.mktemp('B')
    root_b.join('bar.txt').write('hello world B')

    for root, _, files in os.walk(tmp_factory.getbasetemp().strpath):
        for name in files:
            print(os.path.join(root, name))

The output should be like:

/tmp/pytest-of-agp/pytest-0/.lock
/tmp/pytest-of-agp/pytest-0/A0/foo.txt
/tmp/pytest-of-agp/pytest-0/B0/bar.txt
Cleaning all temporary folders from /tmp/pytest-of-agp/pytest-0

Since pytest release 2.8 and above the session-scoped tmpdir_factory fixture is available. See the example below from the documentation.

# contents of conftest.py
import pytest

@pytest.fixture(scope='session')
def image_file(tmpdir_factory):
    img = compute_expensive_image()
    fn = tmpdir_factory.mktemp('data').join('img.png')
    img.save(str(fn))
    return fn

# contents of test_image.py
def test_histogram(image_file):
    img = load_image(image_file)
    # compute and test histogram

Tags:

Python

Pytest