Fixture Scope and Dependencies
The scope is how often the declared fixture runs: the function (the default, the once per test), the class, the module, the package, and the session (the once per run) - the @pytest.fixture(scope="module") sets it, and the rule is the no wider scope may request a narrower one (the module fixture cannot take the function fixture, the scope must widen or match). The dependency is the fixture-on-fixture: the fixture requests another by the name, and the graph's setup is the depth-first (the dependency first), the teardown the reverse (the dependent first). This chapter covers the five scopes (the scope argument, the session the expensive setup), the scope rule (the no narrower request), the dependency graph (the order), and the tmp_path_factory the session-scoped temp (the tmp_path the function's).
The Five Scopes
# the SCOPE: the the how-often (the run's the
# the cost vs the isolation)
import pytest
# the function (the DEFAULT): the the once per test,
# the the fresh, the no cross-test state
@pytest.fixture
def fresh_text():
return "a fresh string per test"
# the module: the the once per test FILE (the
# the shared across the the file's the tests, the the
# the re-created the the next file)
@pytest.fixture(scope="module")
def module_corpus():
return open("corpus.txt").read() # the
# the read ONCE
# the session: the the once per RUN (the
# the expensive: the DB, the server,
# the model load)
@pytest.fixture(scope="session")
def app_db():
db = create_test_db() # the once
yield db
drop_test_db(db) # the end
def test_a(module_corpus): ... # the module
def test_b(module_corpus): ... # the SAME value
def test_c(app_db): ... # the session
The scope is the scope argument, and the five values are the cost/isolation trade. The function (the default) is the once-per-test - the fresh state, the no cross-test leak, the safe default. The module is the once-per-test-file - the module_corpus read once for the file's tests, re-created for the next file - the shared data the file's tests all use. The session is the once-per-run - the app_db created before the first test that requests it, dropped after the last, the expensive setup (the database, the server, the model load) the scope exists for. The class and the package are the middle (the per-class, the per-package-directory). The rule is the isolation: the wider the scope, the more tests share the state, and the shared state must be read-only (the module_corpus the string, the app_db the the test's transaction rolled back) or the cross-test leak is the flaky suite - the next section's scope rule is the compiler's check of the same.
The Scope Rule: No Narrower Request
# the the RULE: the WIDER scope CANNOT request
# the NARROWER (the module the the
# the function, the session the module)
# the error is the collection's (the the
# the "ScopeMismatch", the no run)
@pytest.fixture(scope="module")
def bad_shared():
return per_test_value() # the OK (the the
# the plain, the no request)
@pytest.fixture(scope="module")
def bad_wide_requesting_narrow(fresh_text):
# the fresh_text is the function,
# the module CANNOT take it (the
# the "ScopeMismatch" the collection)
return fresh_text.upper()
# the FIX: the WIDEN the
# the dependency (the fresh_text the
# module), or the NARROW the
# requester (the bad_wide the function)
@pytest.fixture
def fixed_wide_requesting_narrow(fresh_text):
return fresh_text.upper() # the function
# takes the the function: OK
The rule is the direction: the wider scope cannot request the narrower (the module cannot take the function, the session cannot take the module), because the wider fixture outlives the narrower's teardown - the fresh_text would be torn down after the first test, but the module fixture still holds the reference. The check is at collection (the ScopeMismatch error, the no run - the suite fails fast on the graph error), and the fix is the widen the dependency (the fresh_text to the module) or the narrow the requester (the bad_wide to the function) - the two directions, the graph's scopes must be non-increasing from the dependency to the requester. The rule is the same as the import direction: the flow is one way, and the violation is the error before the run.
The Dependency Graph: The Order
# the the GRAPH: the fixture ON the the fixture
# (the test the requests the the
# the derived, the derived the base)
import pytest
@pytest.fixture
def base_text():
return "the quick brown fox"
@pytest.fixture
def upper_text(base_text):
return base_text.upper()
@pytest.fixture
def reversed_upper(upper_text):
return upper_text[::-1]
def test_chain(reversed_upper):
assert reversed_upper == "XOF NRWOB KCIUQ EHT"
# the the SETUP: the depth-first (the
# base_text -> the the upper_text -> the the
# reversed_upper), the each ONCE (the the
# the no re-create the requested twice
# in the same test)
# the the TEARDOWN: the REVERSE (the
# reversed_upper first, the base_text last),
# the yield's the after the the
# the test
The dependency is the fixture-on-fixture: the reversed_upper requests the upper_text, which requests the base_text, and the test requests only the reversed_upper (the chain resolves). The setup is the depth-first (the base_text first, the reversed_upper last), each dependency created once per the requested scope (the no re-create when two fixtures in the same test request the same base), and the teardown is the reverse (the reversed_upper first, the base_text last) - the stack order, the yield fixture's after-block runs on the pop. The graph is the DAG: the cycle (the a requests the b, the b requests the a) is the collection error (the RecursionError the cycle detected), and the request fixture in the chain gives the metadata the level needs. The tmp_path (the function's temp, the next chapter) and the tmp_path_factory (the session's, the shared across the the tests) are the same graph applied to the filesystem - the factory is the session-scoped parent, the path the function-scoped child the test requests.
The tmp_path_factory: The Session's Temp
# the the tmp_path (the the FUNCTION): the fresh
# dir per test (the auto-cleanup, the
# the mkdtemp the base)
# the the tmp_path_factory (the the SESSION): the
# the shared temp the tests
# share (the corpus the
# the built ONCE)
import pytest
@pytest.fixture(scope="session")
def corpus_dir(tmp_path_factory):
d = tmp_path_factory.mktemp("corpus")
(d / "sample.txt").write_text("the shared corpus")
return d
@pytest.fixture
def corpus_file(corpus_dir):
return corpus_dir / "sample.txt"
def test_read(corpus_file):
assert corpus_file.read_text() == "the shared corpus"
# the corpus_dir the SESSION (the the
# the built ONCE), the corpus_file the
# FUNCTION (the path the per test, the
# the no cost, the
# the)
The tmp_path is the function-scoped temp directory (the fresh per test, the auto-cleanup), and the tmp_path_factory is the session-scoped parent (the mktemp the subdirectory, the shared across the tests) - the same scope rule applied: the corpus_dir (the session) builds the corpus once, and the corpus_file (the function) wraps the path per test (the no cost, the reference to the shared directory). The pattern is the split: the expensive build is the session factory (the mktemp plus the write once), the per-test handle is the function fixture (the path, the open file, the connection) - the scope and the dependency together are the architecture the suite's setup follows, and the next chapter's tmp_path detail (the retention, the keep) is the filesystem half of the same.
The Working Rules
- The
scopeis the how-often, thefunctionthe default. Themodulethe per-file, thesessionthe per-run (the expensive: the DB, the server, the model) - the wider the scope, the more tests share, and the shared state must be read-only or the leak is the flaky suite. - The no wider requests the narrower (the
ScopeMismatch). Themodulecannot take thefunction, the check is at collection (the fail-fast), and the fix is the widen the dependency or the narrow the requester - the graph's scopes non-increasing from the dependency to the requester. - The setup is the depth-first, the teardown the reverse. The dependency first, each once per the requested scope, the
yieldafter-block on the pop - the stack order, and the cycle is the collection error. - The factory is the session, the child the function. The
tmp_path_factory(themktemponce) the expensive build, thetmp_path/the child fixture (the path, the handle) the per-test - the split the scope plus the dependency make.
Summary
The scope is the scope argument: the function (the default, the once-per-test the fresh), the class, the module (the per-file the shared corpus), the package, and the session (the per-run the expensive: the DB, the server, the model load) - the wider the scope, the more tests share, and the shared state must be read-only. The rule is the no wider requests the narrower (the ScopeMismatch at collection, the fail-fast, the fix the widen the dependency or the narrow the requester). The dependency graph is the fixture-on-fixture: the setup the depth-first (the base first), the teardown the reverse (the stack order, the yield after on the pop), each dependency once per the requested scope, and the cycle the collection error. The tmp_path (the function's temp) and the tmp_path_factory (the session's, the mktemp once) are the same graph on the filesystem - the factory the expensive build, the child the per-test handle. The next chapter details the tmp_path itself (the retention, the keep) and the temporary file the test manipulates.