[{"data":1,"prerenderedAt":1043},["ShallowReactive",2],{"page-\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Ffixing-scopemismatch-errors-in-pytest\u002F":3},{"id":4,"title":5,"body":6,"description":1009,"extension":1010,"meta":1011,"navigation":72,"path":1039,"seo":1040,"stem":1041,"__hash__":1042},"content\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Ffixing-scopemismatch-errors-in-pytest\u002Findex.md","Fixing ScopeMismatch Errors in pytest",{"type":7,"value":8,"toc":998},"minimark",[9,18,23,43,47,50,110,139,175,191,197,280,283,454,458,461,555,559,565,615,633,742,746,749,764,804,817,826,830,892,896,917,920,927,931,937,943,956,960,988,994],[10,11,12,13,17],"p",{},"A test run stops at collection with ",[14,15,16],"code",{},"ScopeMismatch: You tried to access the function scoped fixture tmp_path with a session scoped request object",". Nothing in the test changed; a fixture was widened to make the suite faster, and pytest refused the resulting graph. The error is not a bug in your test — it is pytest enforcing the one rule that keeps fixture lifetimes coherent: a fixture may only depend on fixtures whose scope is at least as wide as its own.",[19,20,22],"h2",{"id":21},"prerequisites","Prerequisites",[24,25,26,34],"ul",{},[27,28,29,33],"li",{},[30,31,32],"strong",{},"pytest 7.0+"," (the error text below is from 7.x; the rule itself is unchanged since 3.0).",[27,35,36,37,42],{},"Familiarity with fixture scopes and the resolution order covered in ",[38,39,41],"a",{"href":40},"\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002F","mastering pytest fixtures",".",[19,44,46],{"id":45},"solution","Solution",[10,48,49],{},"The error names both sides of the illegal edge. Read them out, decide which side is wrong, then apply one of exactly three repairs.",[51,52,57],"pre",{"className":53,"code":54,"language":55,"meta":56,"style":56},"language-python shiki shiki-themes github-light github-dark","import pytest\n\n# The failing arrangement: a session fixture reaching for a function-scoped one.\n@pytest.fixture(scope=\"session\")\ndef config_file(tmp_path):          # tmp_path is function-scoped -> ScopeMismatch\n    path = tmp_path \u002F \"app.toml\"\n    path.write_text(\"[app]\\nname = 'demo'\\n\")\n    return path\n","python","",[14,58,59,67,74,80,86,92,98,104],{"__ignoreMap":56},[60,61,64],"span",{"class":62,"line":63},"line",1,[60,65,66],{},"import pytest\n",[60,68,70],{"class":62,"line":69},2,[60,71,73],{"emptyLinePlaceholder":72},true,"\n",[60,75,77],{"class":62,"line":76},3,[60,78,79],{},"# The failing arrangement: a session fixture reaching for a function-scoped one.\n",[60,81,83],{"class":62,"line":82},4,[60,84,85],{},"@pytest.fixture(scope=\"session\")\n",[60,87,89],{"class":62,"line":88},5,[60,90,91],{},"def config_file(tmp_path):          # tmp_path is function-scoped -> ScopeMismatch\n",[60,93,95],{"class":62,"line":94},6,[60,96,97],{},"    path = tmp_path \u002F \"app.toml\"\n",[60,99,101],{"class":62,"line":100},7,[60,102,103],{},"    path.write_text(\"[app]\\nname = 'demo'\\n\")\n",[60,105,107],{"class":62,"line":106},8,[60,108,109],{},"    return path\n",[10,111,112,115,116,119,120,123,124,119,127,130,131,134,135,138],{},[30,113,114],{},"Repair one — widen the inner fixture."," Several built-in fixtures ship a session-scoped sibling precisely for this case: ",[14,117,118],{},"tmp_path"," has ",[14,121,122],{},"tmp_path_factory",", and ",[14,125,126],{},"capsys",[14,128,129],{},"capsysbinary"," only at function scope but ",[14,132,133],{},"capfd"," is available at any scope through ",[14,136,137],{},"pytestconfig",". When a factory exists, this is the correct fix, because the value really is shareable.",[51,140,142],{"className":53,"code":141,"language":55,"meta":56,"style":56},"import pytest\n\n@pytest.fixture(scope=\"session\")\ndef config_file(tmp_path_factory):          # the session-scoped factory\n    path = tmp_path_factory.mktemp(\"cfg\") \u002F \"app.toml\"\n    path.write_text(\"[app]\\nname = 'demo'\\n\")\n    return path                              # one file, shared by the whole run\n",[14,143,144,148,152,156,161,166,170],{"__ignoreMap":56},[60,145,146],{"class":62,"line":63},[60,147,66],{},[60,149,150],{"class":62,"line":69},[60,151,73],{"emptyLinePlaceholder":72},[60,153,154],{"class":62,"line":76},[60,155,85],{},[60,157,158],{"class":62,"line":82},[60,159,160],{},"def config_file(tmp_path_factory):          # the session-scoped factory\n",[60,162,163],{"class":62,"line":88},[60,164,165],{},"    path = tmp_path_factory.mktemp(\"cfg\") \u002F \"app.toml\"\n",[60,167,168],{"class":62,"line":94},[60,169,103],{},[60,171,172],{"class":62,"line":100},[60,173,174],{},"    return path                              # one file, shared by the whole run\n",[10,176,177,180,181,184,185,184,187,190],{},[30,178,179],{},"Repair two — narrow the requester."," If the inner fixture is function-scoped because it must be (",[14,182,183],{},"monkeypatch",", ",[14,186,118],{},[14,188,189],{},"caplog","), and the outer fixture genuinely needs it, the outer fixture is the one in the wrong scope. Narrowing costs setup time, and that cost is the price of the isolation the inner fixture provides.",[10,192,193,196],{},[30,194,195],{},"Repair three — split the fixture."," The most common real answer: separate the expensive, immutable part from the per-test part. The expensive half stays session-scoped; a thin function-scoped wrapper adds whatever must not be shared.",[51,198,200],{"className":53,"code":199,"language":55,"meta":56,"style":56},"import pytest\n\n@pytest.fixture(scope=\"session\")\ndef db_engine():                             # expensive, immutable, shared\n    engine = create_engine(\"postgresql:\u002F\u002F\u002Ftest\")\n    yield engine\n    engine.dispose()\n\n@pytest.fixture                              # cheap, per-test, isolated\ndef db_session(db_engine):\n    connection = db_engine.connect()\n    transaction = connection.begin()\n    yield Session(bind=connection)\n    transaction.rollback()                   # every test starts from the same state\n    connection.close()\n",[14,201,202,206,210,214,219,224,229,234,238,244,250,256,262,268,274],{"__ignoreMap":56},[60,203,204],{"class":62,"line":63},[60,205,66],{},[60,207,208],{"class":62,"line":69},[60,209,73],{"emptyLinePlaceholder":72},[60,211,212],{"class":62,"line":76},[60,213,85],{},[60,215,216],{"class":62,"line":82},[60,217,218],{},"def db_engine():                             # expensive, immutable, shared\n",[60,220,221],{"class":62,"line":88},[60,222,223],{},"    engine = create_engine(\"postgresql:\u002F\u002F\u002Ftest\")\n",[60,225,226],{"class":62,"line":94},[60,227,228],{},"    yield engine\n",[60,230,231],{"class":62,"line":100},[60,232,233],{},"    engine.dispose()\n",[60,235,236],{"class":62,"line":106},[60,237,73],{"emptyLinePlaceholder":72},[60,239,241],{"class":62,"line":240},9,[60,242,243],{},"@pytest.fixture                              # cheap, per-test, isolated\n",[60,245,247],{"class":62,"line":246},10,[60,248,249],{},"def db_session(db_engine):\n",[60,251,253],{"class":62,"line":252},11,[60,254,255],{},"    connection = db_engine.connect()\n",[60,257,259],{"class":62,"line":258},12,[60,260,261],{},"    transaction = connection.begin()\n",[60,263,265],{"class":62,"line":264},13,[60,266,267],{},"    yield Session(bind=connection)\n",[60,269,271],{"class":62,"line":270},14,[60,272,273],{},"    transaction.rollback()                   # every test starts from the same state\n",[60,275,277],{"class":62,"line":276},15,[60,278,279],{},"    connection.close()\n",[10,281,282],{},"That shape — a session-scoped resource plus a function-scoped transaction — is the standard solution for databases, HTTP clients, browser instances and anything else whose construction is slow but whose per-test state must be clean.",[284,285,288,450],"figure",{"className":286},[287],"diagram",[289,290,297,298,297,302,297,306,297,342,297,350,297,358,297,364,297,369,297,373,297,378,297,385,297,392,297,397,297,400,297,404,297,408,297,412,297,415,297,418,297,421,297,424,297,428,297,433,297,436,297,439,297,442,297,445],"svg",{"viewBox":291,"role":292,"ariaLabelledBy":293,"xmlns":296},"0 0 760 332","img",[294,295],"scopefix-t","scopefix-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[299,300,301],"title",{"id":294},"Choosing the repair for a ScopeMismatch",[303,304,305],"desc",{"id":295},"A decision diagram: if a wider-scoped variant of the requested fixture exists, widen it; if the requested fixture must stay per-test and the value is mutable, narrow the requester; otherwise split the fixture into a shared immutable half and a per-test wrapper.",[307,308,309,310,309,324,309,330,309,336,297],"defs",{},"\n    ",[311,312,319],"marker",{"id":313,"viewBox":314,"refX":315,"refY":316,"markerWidth":317,"markerHeight":317,"orient":318},"scopefix-a","0 0 10 10","9","5","7","auto-start-reverse",[320,321],"path",{"d":322,"fill":323},"M0 0 L10 5 L0 10 z","#3d405b",[311,325,327],{"id":326,"viewBox":314,"refX":315,"refY":316,"markerWidth":317,"markerHeight":317,"orient":318},"scopefix-c",[320,328],{"d":322,"fill":329},"#e07a5f",[311,331,333],{"id":332,"viewBox":314,"refX":315,"refY":316,"markerWidth":317,"markerHeight":317,"orient":318},"scopefix-s",[320,334],{"d":322,"fill":335},"#81b29a",[311,337,339],{"id":338,"viewBox":314,"refX":315,"refY":316,"markerWidth":317,"markerHeight":317,"orient":318},"scopefix-g",[320,340],{"d":322,"fill":341},"#f2cc8f",[343,344],"rect",{"x":345,"y":345,"width":346,"height":347,"rx":348,"fill":349},"0","760","332","14","#fffdf8",[351,352,301],"text",{"x":353,"y":354,"textAnchor":355,"fontSize":356,"fontWeight":357,"fill":323},"380","30","middle","15","700",[359,360],"polygon",{"points":361,"fill":362,"stroke":323,"strokeWidth":363},"380,50 590,108 380,166 170,108","#f4f1de","1.8",[351,365,368],{"x":353,"y":366,"textAnchor":355,"fontSize":367,"fontWeight":357,"fill":323},"104","12","Is the requested fixture",[351,370,372],{"x":353,"y":371,"textAnchor":355,"fontSize":367,"fontWeight":357,"fill":323},"121","safe to share?",[320,374],{"d":375,"fill":376,"stroke":335,"strokeWidth":363,"markerEnd":377},"M380 166 L380 192 L133 192 L133 206","none","url(#scopefix-s)",[351,379,384],{"x":380,"y":381,"textAnchor":355,"fontSize":382,"fontWeight":357,"fill":383},"133","196","11","#2a5f49","a wide variant exists",[343,386],{"x":387,"y":388,"width":389,"height":390,"rx":367,"fill":349,"stroke":335,"strokeWidth":391},"24","214","217","84","2",[351,393,396],{"x":380,"y":394,"textAnchor":355,"fontSize":395,"fontWeight":357,"fill":323},"245","12.5","widen it",[351,398,122],{"x":380,"y":399,"textAnchor":355,"fontSize":382,"fill":323},"262",[351,401,403],{"x":380,"y":402,"textAnchor":355,"fontSize":382,"fill":323},"277","same value, wider life",[320,405],{"d":406,"fill":376,"stroke":329,"strokeWidth":363,"markerEnd":407},"M380 166 L380 192 L380 192 L380 206","url(#scopefix-c)",[351,409,411],{"x":353,"y":381,"textAnchor":355,"fontSize":382,"fontWeight":357,"fill":410},"#8f3d22","it must stay per-test",[343,413],{"x":414,"y":388,"width":389,"height":390,"rx":367,"fill":349,"stroke":329,"strokeWidth":391},"271",[351,416,417],{"x":353,"y":394,"textAnchor":355,"fontSize":395,"fontWeight":357,"fill":323},"narrow the requester",[351,419,420],{"x":353,"y":399,"textAnchor":355,"fontSize":382,"fill":323},"pay the setup cost",[351,422,423],{"x":353,"y":402,"textAnchor":355,"fontSize":382,"fill":323},"isolation preserved",[320,425],{"d":426,"fill":376,"stroke":341,"strokeWidth":363,"markerEnd":427},"M380 166 L380 192 L627 192 L627 206","url(#scopefix-g)",[351,429,432],{"x":430,"y":381,"textAnchor":355,"fontSize":382,"fontWeight":357,"fill":431},"627","#8a5a00","partly shareable",[343,434],{"x":435,"y":388,"width":389,"height":390,"rx":367,"fill":349,"stroke":341,"strokeWidth":391},"519",[351,437,438],{"x":430,"y":394,"textAnchor":355,"fontSize":395,"fontWeight":357,"fill":323},"split in two",[351,440,441],{"x":430,"y":399,"textAnchor":355,"fontSize":382,"fill":323},"session resource",[351,443,444],{"x":430,"y":402,"textAnchor":355,"fontSize":382,"fill":323},"function-scoped wrapper",[351,446,449],{"x":353,"y":447,"textAnchor":355,"fontSize":382,"fontStyle":448,"fill":323},"320","italic","Widening a mutable fixture trades a loud error for silent cross-test leakage.",[451,452,453],"figcaption",{},"The third branch is the usual answer for databases and clients: share the connection, isolate the transaction.",[19,455,457],{"id":456},"why-this-works","Why this works",[10,459,460],{},"pytest builds a directed graph of fixture dependencies before running anything, and each node carries a scope with a known lifetime. A session fixture is instantiated once and finalised after the last test; a function fixture is finalised after each test. An edge from wider to narrower would mean the session fixture holding a reference to an object that has already been torn down for every test after the first — so pytest rejects the graph at collection rather than producing a use-after-teardown at runtime. The check is purely structural, which is why it fires even when the specific test that triggered it would have worked.",[284,462,464,552],{"className":463},[287],[289,465,297,470,297,473,297,476,297,483,297,486,297,488,297,494,297,499,297,503,297,509,297,511,297,515,297,518,297,522,297,525,297,529,297,532,297,536,297,539,297,543,297,547],{"viewBox":466,"role":292,"ariaLabelledBy":467,"xmlns":296},"0 0 760 172",[468,469],"scopelife-t","scopelife-d",[299,471,472],{"id":468},"Why the illegal edge cannot work",[303,474,475],{"id":469},"A left-to-right timeline showing a session fixture created once, then three tests each creating and destroying their own function-scoped fixture, with the session fixture still holding a reference to the first one after it has been torn down.",[307,477,309,478,297],{},[311,479,481],{"id":480,"viewBox":314,"refX":315,"refY":316,"markerWidth":317,"markerHeight":317,"orient":318},"scopelife-a",[320,482],{"d":322,"fill":329},[343,484],{"x":345,"y":345,"width":346,"height":485,"rx":348,"fill":349},"172",[351,487,472],{"x":353,"y":354,"textAnchor":355,"fontSize":356,"fontWeight":357,"fill":323},[343,489],{"x":490,"y":491,"width":492,"height":493,"rx":367,"fill":362,"stroke":329,"strokeWidth":363},"26","62","142","76",[351,495,498],{"x":496,"y":497,"textAnchor":355,"fontSize":395,"fontWeight":357,"fill":323},"97","96","session setup",[351,500,502],{"x":496,"y":501,"textAnchor":355,"fontSize":382,"fill":323},"113","built once",[62,504],{"x1":505,"y1":506,"x2":507,"y2":506,"stroke":329,"strokeWidth":363,"markerEnd":508},"176","100","208","url(#scopelife-a)",[343,510],{"x":388,"y":491,"width":492,"height":493,"rx":367,"fill":349,"stroke":329,"strokeWidth":363},[351,512,514],{"x":513,"y":497,"textAnchor":355,"fontSize":395,"fontWeight":357,"fill":323},"286","test 1",[351,516,517],{"x":513,"y":501,"textAnchor":355,"fontSize":382,"fill":323},"function fixture lives",[62,519],{"x1":520,"y1":506,"x2":521,"y2":506,"stroke":329,"strokeWidth":363,"markerEnd":508},"364","396",[343,523],{"x":524,"y":491,"width":492,"height":493,"rx":367,"fill":362,"stroke":329,"strokeWidth":363},"403",[351,526,528],{"x":527,"y":497,"textAnchor":355,"fontSize":395,"fontWeight":357,"fill":323},"474","test 1 teardown",[351,530,531],{"x":527,"y":501,"textAnchor":355,"fontSize":382,"fill":323},"that fixture is gone",[62,533],{"x1":534,"y1":506,"x2":535,"y2":506,"stroke":329,"strokeWidth":363,"markerEnd":508},"552","584",[343,537],{"x":538,"y":491,"width":492,"height":493,"rx":367,"fill":349,"stroke":329,"strokeWidth":363},"592",[351,540,542],{"x":541,"y":497,"textAnchor":355,"fontSize":395,"fontWeight":357,"fill":323},"663","test 2",[351,544,546],{"x":541,"y":501,"textAnchor":355,"fontSize":545,"fill":323},"10.5","session holds a dead ref",[351,548,551],{"x":353,"y":549,"textAnchor":355,"fontSize":550,"fontStyle":448,"fill":323},"164","11.5","pytest validates the graph at collection, before any fixture is built.",[451,553,554],{},"By the second test the session fixture would be holding an object that was finalised at the end of the first — the error prevents exactly that.",[19,556,558],{"id":557},"verification","Verification",[10,560,561,564],{},[14,562,563],{},"--setup-show"," prints every setup and teardown with its scope letter, which makes the repaired lifetimes visible rather than assumed:",[51,566,570],{"className":567,"code":568,"language":569,"meta":56,"style":56},"language-console shiki shiki-themes github-light github-dark","$ pytest --setup-show tests\u002Ftest_orders.py -q\nSETUP    S db_engine\n        SETUP    F db_session (fixtures used: db_engine)\n        tests\u002Ftest_orders.py::test_create (fixtures used: db_engine, db_session)\n        TEARDOWN F db_session\n        SETUP    F db_session (fixtures used: db_engine)\n        tests\u002Ftest_orders.py::test_cancel (fixtures used: db_engine, db_session)\n        TEARDOWN F db_session\nTEARDOWN S db_engine\n","console",[14,571,572,577,582,587,592,597,601,606,610],{"__ignoreMap":56},[60,573,574],{"class":62,"line":63},[60,575,576],{},"$ pytest --setup-show tests\u002Ftest_orders.py -q\n",[60,578,579],{"class":62,"line":69},[60,580,581],{},"SETUP    S db_engine\n",[60,583,584],{"class":62,"line":76},[60,585,586],{},"        SETUP    F db_session (fixtures used: db_engine)\n",[60,588,589],{"class":62,"line":82},[60,590,591],{},"        tests\u002Ftest_orders.py::test_create (fixtures used: db_engine, db_session)\n",[60,593,594],{"class":62,"line":88},[60,595,596],{},"        TEARDOWN F db_session\n",[60,598,599],{"class":62,"line":94},[60,600,586],{},[60,602,603],{"class":62,"line":100},[60,604,605],{},"        tests\u002Ftest_orders.py::test_cancel (fixtures used: db_engine, db_session)\n",[60,607,608],{"class":62,"line":106},[60,609,596],{},[60,611,612],{"class":62,"line":240},[60,613,614],{},"TEARDOWN S db_engine\n",[10,616,617,618,621,622,625,626,629,630,632],{},"One ",[14,619,620],{},"SETUP S"," at the top, one ",[14,623,624],{},"TEARDOWN S"," at the bottom, and a matched ",[14,627,628],{},"F"," pair around each test is the shape you want. A second ",[14,631,620],{}," in the middle means the fixture is being rebuilt — usually because it is parametrized — and a missing teardown means an exception escaped the fixture body.",[284,634,636,739],{"className":635},[287],[289,637,297,642,297,645,297,648,297,651,297,653,297,660,297,664,297,668,297,672,297,676,297,680,297,684,297,687,297,689,297,691,297,694,297,698,297,701,297,703,297,705,297,707,297,710,297,712,297,714,297,716,297,719,297,723,297,725,297,727,297,729,297,733,297,736],{"viewBox":638,"role":292,"ariaLabelledBy":639,"xmlns":296},"0 0 760 270",[640,641],"scopepairs-t","scopepairs-d",[299,643,644],{"id":640},"Which fixture may depend on which",[303,646,647],{"id":641},"A grid showing legal fixture dependencies: a function-scoped fixture may request any scope, a class fixture may request class, module and session, a module fixture may request module and session, and a session fixture may only request session-scoped fixtures.",[343,649],{"x":345,"y":345,"width":346,"height":650,"rx":348,"fill":349},"270",[351,652,644],{"x":353,"y":354,"textAnchor":355,"fontSize":356,"fontWeight":357,"fill":323},[343,654],{"x":387,"y":655,"width":656,"height":657,"rx":658,"fill":341,"stroke":323,"strokeWidth":659},"52","712","40","10","1.5",[351,661,663],{"x":662,"y":493,"fontSize":367,"fontWeight":357,"fill":323},"38","Criterion",[351,665,667],{"x":666,"y":493,"textAnchor":355,"fontSize":367,"fontWeight":357,"fill":323},"351","function",[351,669,671],{"x":670,"y":493,"textAnchor":355,"fontSize":367,"fontWeight":357,"fill":323},"505","module",[351,673,675],{"x":674,"y":493,"textAnchor":355,"fontSize":367,"fontWeight":357,"fill":323},"659","session",[343,677],{"x":387,"y":678,"width":656,"height":657,"fill":362,"stroke":323,"strokeWidth":679},"92","1",[351,681,683],{"x":662,"y":682,"fontSize":550,"fill":323},"116","function-scoped requester",[351,685,686],{"x":666,"y":682,"textAnchor":355,"fontSize":550,"fill":383},"ok",[351,688,686],{"x":670,"y":682,"textAnchor":355,"fontSize":550,"fill":383},[351,690,686],{"x":674,"y":682,"textAnchor":355,"fontSize":550,"fill":383},[343,692],{"x":387,"y":693,"width":656,"height":657,"fill":349,"stroke":323,"strokeWidth":679},"132",[351,695,697],{"x":662,"y":696,"fontSize":550,"fill":323},"156","class-scoped requester",[351,699,700],{"x":666,"y":696,"textAnchor":355,"fontSize":550,"fill":410},"error",[351,702,686],{"x":670,"y":696,"textAnchor":355,"fontSize":550,"fill":383},[351,704,686],{"x":674,"y":696,"textAnchor":355,"fontSize":550,"fill":383},[343,706],{"x":387,"y":485,"width":656,"height":657,"fill":362,"stroke":323,"strokeWidth":679},[351,708,709],{"x":662,"y":381,"fontSize":550,"fill":323},"module-scoped requester",[351,711,700],{"x":666,"y":381,"textAnchor":355,"fontSize":550,"fill":410},[351,713,686],{"x":670,"y":381,"textAnchor":355,"fontSize":550,"fill":383},[351,715,686],{"x":674,"y":381,"textAnchor":355,"fontSize":550,"fill":383},[343,717],{"x":387,"y":718,"width":656,"height":657,"fill":349,"stroke":323,"strokeWidth":679},"212",[351,720,722],{"x":662,"y":721,"fontSize":550,"fill":323},"236","session-scoped requester",[351,724,700],{"x":666,"y":721,"textAnchor":355,"fontSize":550,"fill":410},[351,726,700],{"x":670,"y":721,"textAnchor":355,"fontSize":550,"fill":410},[351,728,686],{"x":674,"y":721,"textAnchor":355,"fontSize":550,"fill":383},[62,730],{"x1":731,"y1":655,"x2":731,"y2":732,"stroke":323,"strokeWidth":679},"274","252",[62,734],{"x1":735,"y1":655,"x2":735,"y2":732,"stroke":323,"strokeWidth":679},"428",[62,737],{"x1":738,"y1":655,"x2":738,"y2":732,"stroke":323,"strokeWidth":679},"582",[451,740,741],{},"The rule is one-directional: a fixture may depend only on fixtures that live at least as long as it does.",[19,743,745],{"id":744},"reading-the-error-when-the-edge-is-implicit","Reading the error when the edge is implicit",[10,747,748],{},"The error text is precise when you requested the fixture by name. It is much harder to read when the edge came from somewhere you did not write — an autouse fixture, a plugin, or a fixture that requests another fixture two levels down.",[10,750,751,752,755,756,759,760,763],{},"Three commands resolve it. ",[14,753,754],{},"pytest --fixtures -v"," lists every visible fixture with its scope and the file that defines it, which immediately identifies plugin-provided fixtures you did not know were function-scoped. ",[14,757,758],{},"pytest --setup-plan"," walks the intended setup order without executing anything, so a graph that fails to build is reported with the full chain rather than one pair. And ",[14,761,762],{},"--fixtures-per-test"," prints, for each test, exactly which fixtures it will use and where each comes from:",[51,765,767],{"className":567,"code":766,"language":569,"meta":56,"style":56},"$ pytest tests\u002Ftest_reports.py --fixtures-per-test\n------------------------ fixtures used by test_monthly_report ------------------------\ntmp_path -- ...\u002F_pytest\u002Ftmpdir.py:241\n    Return a temporary directory path object ...\nreport_dir -- tests\u002Fconftest.py:18\n    (no docstring)\ndb_engine -- tests\u002Fconftest.py:31\n",[14,768,769,774,779,784,789,794,799],{"__ignoreMap":56},[60,770,771],{"class":62,"line":63},[60,772,773],{},"$ pytest tests\u002Ftest_reports.py --fixtures-per-test\n",[60,775,776],{"class":62,"line":69},[60,777,778],{},"------------------------ fixtures used by test_monthly_report ------------------------\n",[60,780,781],{"class":62,"line":76},[60,782,783],{},"tmp_path -- ...\u002F_pytest\u002Ftmpdir.py:241\n",[60,785,786],{"class":62,"line":82},[60,787,788],{},"    Return a temporary directory path object ...\n",[60,790,791],{"class":62,"line":88},[60,792,793],{},"report_dir -- tests\u002Fconftest.py:18\n",[60,795,796],{"class":62,"line":94},[60,797,798],{},"    (no docstring)\n",[60,800,801],{"class":62,"line":100},[60,802,803],{},"db_engine -- tests\u002Fconftest.py:31\n",[10,805,806,807,810,811,813,814,816],{},"With the chain visible, the offending edge is usually one hop deeper than the error suggested: ",[14,808,809],{},"report_dir"," is session-scoped, it requests ",[14,812,118],{},", and nothing in the failing test mentions ",[14,815,118],{}," at all.",[10,818,819,820,822,823,825],{},"A last diagnostic worth knowing: the scope of a fixture defined in a plugin cannot be changed from your conftest. Requesting ",[14,821,189],{}," or ",[14,824,183],{}," from a wider fixture will always fail, and the fix is always on your side of the boundary — narrow the requester, or replicate the small amount of behaviour you needed with a module you control. Overriding the fixture name locally to widen it works, but it also replaces the plugin's implementation everywhere in that directory, which is rarely what anyone intends.",[19,827,829],{"id":828},"edge-cases-and-failure-modes","Edge cases and failure modes",[24,831,832,848,857,872,881],{},[27,833,834,837,838,841,842,844,845,847],{},[30,835,836],{},"Parametrized fixtures multiply the setup, not the scope."," A session fixture with ",[14,839,840],{},"params=[...]"," is built once per parameter, so ",[14,843,563],{}," legitimately shows several ",[14,846,620],{}," lines. That is not a scope error, but it does mean any state you assumed was singular is not.",[27,849,850,856],{},[30,851,852,855],{},[14,853,854],{},"autouse"," does not change scope rules."," An autouse function-scoped fixture requested implicitly by a session fixture raises the same error, and the traceback is harder to read because the edge is invisible in the source.",[27,858,859,862,863,866,867,871],{},[30,860,861],{},"Async fixtures add a second scope to keep aligned."," With ",[14,864,865],{},"pytest-asyncio",", the event loop has its own scope, and a session fixture on a function-scoped loop fails at teardown rather than at collection — see ",[38,868,870],{"href":869},"\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Fhow-to-scope-pytest-fixtures-for-async-tests\u002F","how to scope pytest fixtures for async tests"," for that variant.",[27,873,874,880],{},[30,875,876,879],{},[14,877,878],{},"request.getfixturevalue"," bypasses the static check."," Fetching a narrower fixture dynamically inside a wider one compiles and runs, then fails unpredictably at teardown. The static error exists for a reason; do not route around it.",[27,882,883,886,887,891],{},[30,884,885],{},"Widening to fix a slow suite often backfires."," A session-scoped fixture holding mutable state turns test order into a dependency, and the resulting failures appear in whichever test happens to run second. Measure with ",[38,888,890],{"href":889},"\u002Fsystematic-debugging-performance-profiling\u002Fcpu-profiling-with-cprofile-and-py-spy\u002F","--durations and a profiler"," before assuming the fixture is the bottleneck.",[19,893,895],{"id":894},"preventing-the-next-one","Preventing the next one",[10,897,898,899,902,903,906,907,909,910,912,913,916],{},"Two conventions stop the error recurring as the suite grows. The first is to name fixtures by their lifetime when the lifetime is load-bearing: ",[14,900,901],{},"session_engine"," and ",[14,904,905],{},"db_session"," read differently at a glance, and a reviewer notices ",[14,908,901],{}," requesting ",[14,911,118],{}," in a way they would not notice ",[14,914,915],{},"engine"," doing it.",[10,918,919],{},"The second is to keep the widening decision in one place. A module of shared fixtures where every session-scoped definition sits together makes the constraint visible — everything in that block may only depend on other things in that block. When a new fixture needs to join it, the question \"is this immutable between tests?\" is asked once, at the point where the answer matters, instead of being rediscovered from an error message weeks later.",[10,921,922,923,926],{},"Neither convention is enforceable by a tool, which is why the third safeguard is worth adding to CI: run ",[14,924,925],{},"pytest --setup-plan -q"," as a fast job. It builds the entire fixture graph for every test without executing a single fixture body, so a scope error introduced anywhere in the suite fails in seconds rather than after the slow tests have run.",[19,928,930],{"id":929},"frequently-asked-questions","Frequently Asked Questions",[10,932,933,936],{},[30,934,935],{},"What does ScopeMismatch actually mean in pytest?","\nIt means a wider-scoped fixture requested a narrower-scoped one. A session fixture is built once and must outlive every function-scoped fixture, so pytest refuses to bind a value that will be torn down before the requester is finished with it.",[10,938,939,942],{},[30,940,941],{},"Can I fix ScopeMismatch by making everything session-scoped?","\nOnly if the fixture is genuinely immutable between tests. Widening a mutable fixture removes the error and replaces it with cross-test state leakage, which is harder to diagnose than the original exception.",[10,944,945,948,949,951,952,955],{},[30,946,947],{},"Why do tmp_path and monkeypatch cause ScopeMismatch?","\nBoth are function-scoped by design and cannot be widened. Use ",[14,950,122],{}," for a session-scoped directory and set environment variables with ",[14,953,954],{},"os.environ"," plus explicit cleanup, or keep the requesting fixture function-scoped.",[19,957,959],{"id":958},"related","Related",[24,961,962,968,974,981],{},[27,963,964,967],{},[38,965,966],{"href":40},"Mastering pytest fixtures"," — the resolution order and scope semantics this error enforces.",[27,969,970,973],{},[38,971,972],{"href":869},"How to scope pytest fixtures for async tests"," — the async variant, which fails at teardown instead of collection.",[27,975,976,980],{},[38,977,979],{"href":978},"\u002Fadvanced-pytest-architecture-configuration\u002Fmanaging-conftest-hierarchies\u002F","Managing conftest hierarchies"," — where a shadowed fixture of the same name can change the scope you thought you had.",[27,982,983,987],{},[38,984,986],{"href":985},"\u002Fadvanced-pytest-architecture-configuration\u002Foptimizing-test-discovery\u002F","Optimizing test discovery"," — the other half of a slow suite, usually a larger win than widening fixture scope.",[10,989,990,991],{},"← Back to ",[38,992,993],{"href":40},"Mastering Pytest Fixtures",[995,996,997],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":56,"searchDepth":69,"depth":69,"links":999},[1000,1001,1002,1003,1004,1005,1006,1007,1008],{"id":21,"depth":69,"text":22},{"id":45,"depth":69,"text":46},{"id":456,"depth":69,"text":457},{"id":557,"depth":69,"text":558},{"id":744,"depth":69,"text":745},{"id":828,"depth":69,"text":829},{"id":894,"depth":69,"text":895},{"id":929,"depth":69,"text":930},{"id":958,"depth":69,"text":959},"Fix pytest ScopeMismatch: why a session fixture cannot request a function-scoped one, the three legal repairs, and how to verify the fix with --setup-show.","md",{"slug":1012,"type":1013,"breadcrumb":1014,"datePublished":1015,"dateModified":1015,"faq":1016,"howto":1023},"fixing-scopemismatch-errors-in-pytest","article","ScopeMismatch","2026-08-01",[1017,1019,1021],{"q":935,"a":1018},"It means a wider-scoped fixture requested a narrower-scoped one. A session fixture is built once and must outlive every function-scoped fixture, so pytest refuses to bind a value that will be torn down before the requester is finished with it.",{"q":941,"a":1020},"Only if the fixture is genuinely immutable between tests. Widening a mutable fixture removes the error and replaces it with cross-test state leakage, which is harder to diagnose than the original exception.",{"q":947,"a":1022},"Both are function-scoped by design and cannot be widened. Use tmp_path_factory for a session-scoped directory and set environment variables with os.environ plus explicit cleanup, or keep the requesting fixture function-scoped.",{"name":1024,"description":1025,"steps":1026},"How to fix a pytest ScopeMismatch error","Identify the offending fixture pair and apply the narrowest repair that keeps isolation intact.",[1027,1030,1033,1036],{"name":1028,"text":1029},"Read the pair out of the error","The traceback names the requesting fixture and the narrower fixture it asked for; those two names are the whole problem.",{"name":1031,"text":1032},"Decide which side is wrong","Ask whether the requested fixture is safe to share. If it is immutable, widen it; if not, narrow the requester.",{"name":1034,"text":1035},"Apply the repair","Widen the inner fixture, narrow the outer one, or split the fixture into a shared immutable part and a per-test wrapper.",{"name":1037,"text":1038},"Verify with --setup-show","Confirm the setup and teardown order matches the intended lifetimes before moving on.","\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Ffixing-scopemismatch-errors-in-pytest",{"title":5,"description":1009},"advanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Ffixing-scopemismatch-errors-in-pytest\u002Findex","7gzFeald5xB6uejo73uCqElBabt_AZqddJMgcqIDJrs",1785613404335]