Skillversitas

Temporary Files and Directories

The tmp_path fixture is the built-in temporary directory: the fresh pathlib.Path per test (the function scope), the under the system temp (the pytest-of-user the test_name0), and the auto-cleanup by the retention policy (the last three kept, the older deleted). The tmp_path_factory (the session) is the parent the shared temp, and the tempfile module is the no-pytest fallback (the TemporaryDirectory, the NamedTemporaryFile) for the code under test that manages its own temp. This chapter covers the tmp_path (the create, the write, the mktemp the subdirectory), the retention (the --basetemp, the retention policy), the tmp_path_factory (the session's mktemp), and the tempfile for the non-fixture code.

The tmp_path: The Per-Test Directory

# the tmp_path: the built-in (the function scope,
# the fresh per test, the Path, the auto-cleanup)
import pytest

def test_write_read(tmp_path):
    p = tmp_path / "note.txt"
    p.write_text("the hello from the test")
    assert p.read_text() == "the hello from the test"
    # the tmp_path is like /tmp/pytest-of-user/
    #   pytest-subdir/test_write_read0/ (the test name
    #   + the counter; the re-run gets 1/ - no collide)

def test_subdir(tmp_path):
    d = tmp_path / "data"
    d.mkdir()
    (d / "a.txt").write_text("a")
    (d / "b.txt").write_text("b")
    assert sorted(x.name for x in d.iterdir()) == [
        "a.txt", "b.txt"]

def test_mktemp(tmp_path):
    # the mktemp: the NESTED temp (the unique name,
    #   the sub-test's scratch)
    d = tmp_path.mktemp("work")
    (d / "w.txt").write_text("w")
    assert d.joinpath("w.txt").read_text() == "w"

The tmp_path is the function-scoped pathlib.Path the test requests: the fresh directory per test (the test_write_read0, the name plus the counter so the re-run does not collide), the under the basetemp (the /tmp/pytest-of-user/ the pytest-subdir), and the the test's scratch (the write_text, the mkdir, the iterdir - the pathlib API, the no manual cleanup). The mktemp makes the nested unique subdirectory (the work the work0), and the cleanup is the retention policy: the pytest keeps the last few (the default three) basetemp directories per test name, deletes the older, and the --basetemp overrides the location (the /tmp/my-run the CI's). The rule is the one: the test that touches the filesystem uses the tmp_path (the isolated, the cleaned), never the ./output the cwd (the re-run hits the stale file, the suite leaks state) - the next section's factory is the session's half.

The Retention and the basetemp

# the RETENTION: the cleanup policy (the disk's bound)
#   pytest keeps the LAST N (the default 3) basetemp
#   dirs per test name, deletes the older

# the --basetemp: the LOCATION override (the CI's
#   unique per-run, the debug's the inspect)
pytest --basetemp=/tmp/run-123
# the tmp_path now lives under /tmp/run-123/ (the run's
#   scratch, the CI's the artifact the keep)

The retention is the cleanup policy: the pytest keeps the last three basetemp directories per test name (the default), deletes the older, so the /tmp/pytest-of-user/ does not grow unbounded across the runs. The --basetemp overrides the location: the /tmp/run-123 the CI's unique per-run (the artifact the pipeline keeps for the failure inspect), and the debug's the local (the --basetemp the path the developer opens in the file manager to see the test's files). The rule is the one: the tmp_path is the scratch the test owns, the retention is the framework's cleanup, and the --basetemp is the location the run controls - the three that make the temp predictable (the location, the lifetime, the cleanup) without the test managing its own directory.

The tmp_path_factory and the tempfile

# the tmp_path_factory: the SESSION's temp (the shared)
#   the mktemp the subdir, the the tests share the data
import pytest

@pytest.fixture(scope="session")
def shared_data(tmp_path_factory):
    d = tmp_path_factory.mktemp("data")
    (d / "corpus.txt").write_text("the shared corpus")
    return d

def test_uses_shared(shared_data):
    assert (shared_data / "corpus.txt").read_text() != ""

# the tempfile: the NO-pytest fallback (the code under
#   test that manages its own temp, the the test that
#   calls the function with the tempdir arg)
import tempfile, os

def test_module_own_temp():
    from texttools import export_words
    with tempfile.TemporaryDirectory() as d:
        out = export_words("a b c", d)   # the the func
                                         # writes in d
        assert os.path.exists(os.path.join(d, out))
    # the d auto-removed on the with-exit (the context)

The tmp_path_factory is the session-scoped parent (the mktemp the subdirectory, the shared across the tests - the shared_data corpus built once, the many tests read it), and the tempfile module is the fallback for the code under test that manages its own temp (the TemporaryDirectory the context manager, the NamedTemporaryFile the file) - the test that calls the export_words with the tempdir argument uses the tempfile because the tmp_path is the test's scratch, not the function's parameter. The split is the one: the tmp_path (the function) and the tmp_path_factory (the session) are the pytest's temp, and the tempfile is the code's temp (the tested function's own management) - the two coexist, and the next chapter's parametrization applies the same isolation to the input the test varies.

The Working Rules

  • The filesystem test uses the tmp_path, never the cwd. The fresh per-test Path, the auto-cleanup by the retention, the write_text/mkdir/iterdir the pathlib API - the ./output the cwd is the stale re-run and the leaked state.
  • The retention is the framework's, the --basetemp the run's location. The last three kept per test name (the older deleted), and the --basetemp the CI's unique per-run (the artifact the failure inspect opens) or the debug's local path.
  • The mktemp is the nested unique, the factory the session's. The tmp_path.mktemp the subdirectory, the tmp_path_factory (the mktemp) the shared across the tests - the expensive build once (the session), the per-test handle the function.
  • The tempfile is the code's temp, not the test's. The TemporaryDirectory the context manager for the function that manages its own temp (the export_words the tempdir arg) - the tmp_path is the test's scratch, the tempfile the tested code's.

Summary

The tmp_path is the built-in temporary directory: the fresh pathlib.Path per test (the function scope, the test_name0 the counter the re-run's no-collide), the under the basetemp, and the auto-cleanup by the retention policy (the last three kept per test name, the older deleted). The mktemp makes the nested unique subdirectory, the --basetemp overrides the location (the CI's unique per-run the artifact, the debug's local), and the tmp_path_factory is the session-scoped parent (the mktemp the shared across the tests - the expensive build once, the per-test handle the function). The tempfile module is the fallback for the code under test that manages its own temp (the TemporaryDirectory the context manager, the NamedTemporaryFile). The rule is the one: the test that touches the filesystem uses the tmp_path (the isolated, the cleaned), never the cwd, and the next chapter's parametrization applies the same isolation to the input the test varies - the tmp_path the where, the parametrize the what.