[{"data":1,"prerenderedAt":1156},["ShallowReactive",2],{"page-\u002Fadvanced-pytest-architecture-configuration\u002Fadvanced-parametrization-techniques\u002Findirect-parametrization-with-fixtures\u002F":3},{"id":4,"title":5,"body":6,"description":1122,"extension":1123,"meta":1124,"navigation":97,"path":1152,"seo":1153,"stem":1154,"__hash__":1155},"content\u002Fadvanced-pytest-architecture-configuration\u002Fadvanced-parametrization-techniques\u002Findirect-parametrization-with-fixtures\u002Findex.md","Indirect Parametrization with pytest Fixtures",{"type":7,"value":8,"toc":1112},"minimark",[9,22,27,60,64,75,176,194,204,316,323,454,458,471,601,605,608,650,657,660,667,692,703,802,806,869,873,876,891,933,943,946,953,1022,1026,1032,1045,1065,1069,1102,1108],[10,11,12,16,17,21],"p",{},[13,14,15],"code",{},"@pytest.mark.parametrize"," hands its values straight to the test function, which is exactly wrong when the value needs to be ",[18,19,20],"em",{},"built"," into something first — a database seeded with that dataset, a client configured for that backend, a temporary file containing that fixture data. Doing the construction inside the test body means the teardown lives there too, and every parametrized case repeats it. Indirect parametrization routes the value through a fixture instead, so each parameter gets a real setup and teardown cycle.",[23,24,26],"h2",{"id":25},"prerequisites","Prerequisites",[28,29,30,42],"ul",{},[31,32,33,37,38,41],"li",{},[34,35,36],"strong",{},"pytest 7.0+","; ",[13,39,40],{},"indirect"," has behaved as described since 3.0.",[31,43,44,45,48,49,54,55,59],{},"Familiarity with fixtures and ",[13,46,47],{},"request"," from ",[50,51,53],"a",{"href":52},"\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002F","mastering pytest fixtures",", and with the direct forms in ",[50,56,58],{"href":57},"\u002Fadvanced-pytest-architecture-configuration\u002Fadvanced-parametrization-techniques\u002F","advanced parametrization techniques",".",[23,61,63],{"id":62},"solution","Solution",[10,65,66,67,70,71,74],{},"The fixture reads its value from ",[13,68,69],{},"request.param",", and the decorator names the ",[18,72,73],{},"fixture"," rather than a plain argument:",[76,77,82],"pre",{"className":78,"code":79,"language":80,"meta":81,"style":81},"language-python shiki shiki-themes github-light github-dark","import pytest\n\n@pytest.fixture\ndef api_client(request):\n    # request.param holds one value from the parametrize list, per test item.\n    backend = request.param\n    client = ApiClient(base_url=BACKENDS[backend], timeout=2)\n    client.connect()                       # real setup, once per parameter\n    yield client\n    client.close()                         # real teardown, once per parameter\n\n@pytest.mark.parametrize(\"api_client\", [\"staging\", \"sandbox\"], indirect=True)\ndef test_health_endpoint(api_client):\n    # The test receives the constructed client, never the string.\n    assert api_client.get(\"\u002Fhealth\").status_code == 200\n","python","",[13,83,84,92,99,105,111,117,123,129,135,141,147,152,158,164,170],{"__ignoreMap":81},[85,86,89],"span",{"class":87,"line":88},"line",1,[85,90,91],{},"import pytest\n",[85,93,95],{"class":87,"line":94},2,[85,96,98],{"emptyLinePlaceholder":97},true,"\n",[85,100,102],{"class":87,"line":101},3,[85,103,104],{},"@pytest.fixture\n",[85,106,108],{"class":87,"line":107},4,[85,109,110],{},"def api_client(request):\n",[85,112,114],{"class":87,"line":113},5,[85,115,116],{},"    # request.param holds one value from the parametrize list, per test item.\n",[85,118,120],{"class":87,"line":119},6,[85,121,122],{},"    backend = request.param\n",[85,124,126],{"class":87,"line":125},7,[85,127,128],{},"    client = ApiClient(base_url=BACKENDS[backend], timeout=2)\n",[85,130,132],{"class":87,"line":131},8,[85,133,134],{},"    client.connect()                       # real setup, once per parameter\n",[85,136,138],{"class":87,"line":137},9,[85,139,140],{},"    yield client\n",[85,142,144],{"class":87,"line":143},10,[85,145,146],{},"    client.close()                         # real teardown, once per parameter\n",[85,148,150],{"class":87,"line":149},11,[85,151,98],{"emptyLinePlaceholder":97},[85,153,155],{"class":87,"line":154},12,[85,156,157],{},"@pytest.mark.parametrize(\"api_client\", [\"staging\", \"sandbox\"], indirect=True)\n",[85,159,161],{"class":87,"line":160},13,[85,162,163],{},"def test_health_endpoint(api_client):\n",[85,165,167],{"class":87,"line":166},14,[85,168,169],{},"    # The test receives the constructed client, never the string.\n",[85,171,173],{"class":87,"line":172},15,[85,174,175],{},"    assert api_client.get(\"\u002Fhealth\").status_code == 200\n",[10,177,178,179,182,183,186,187,190,191,193],{},"Without ",[13,180,181],{},"indirect=True",", ",[13,184,185],{},"api_client"," would be the string ",[13,188,189],{},"\"staging\""," and the fixture would never run. With it, pytest creates one test item per value, sets ",[13,192,69],{}," for that item, and executes the fixture — including its teardown — around each one.",[10,195,196,199,200,203],{},[34,197,198],{},"Partial indirection"," is the form most real suites need: some arguments are built by fixtures, others are plain data. Pass a list of names instead of ",[13,201,202],{},"True",":",[76,205,207],{"className":78,"code":206,"language":80,"meta":81,"style":81},"import pytest\n\n@pytest.fixture\ndef seeded_db(request):\n    rows = request.param                   # only this argument is indirect\n    db = TestDatabase()\n    db.insert_many(rows)\n    yield db\n    db.truncate()\n\n@pytest.mark.parametrize(\n    \"seeded_db,expected_total\",\n    [\n        ([{\"amount\": 10}, {\"amount\": 5}], 15),\n        ([], 0),\n        ([{\"amount\": -3}], -3),\n    ],\n    indirect=[\"seeded_db\"],                # expected_total is passed through as-is\n)\ndef test_total(seeded_db, expected_total):\n    assert seeded_db.total() == expected_total\n",[13,208,209,213,217,221,226,231,236,241,246,251,255,260,265,270,275,280,286,292,298,304,310],{"__ignoreMap":81},[85,210,211],{"class":87,"line":88},[85,212,91],{},[85,214,215],{"class":87,"line":94},[85,216,98],{"emptyLinePlaceholder":97},[85,218,219],{"class":87,"line":101},[85,220,104],{},[85,222,223],{"class":87,"line":107},[85,224,225],{},"def seeded_db(request):\n",[85,227,228],{"class":87,"line":113},[85,229,230],{},"    rows = request.param                   # only this argument is indirect\n",[85,232,233],{"class":87,"line":119},[85,234,235],{},"    db = TestDatabase()\n",[85,237,238],{"class":87,"line":125},[85,239,240],{},"    db.insert_many(rows)\n",[85,242,243],{"class":87,"line":131},[85,244,245],{},"    yield db\n",[85,247,248],{"class":87,"line":137},[85,249,250],{},"    db.truncate()\n",[85,252,253],{"class":87,"line":143},[85,254,98],{"emptyLinePlaceholder":97},[85,256,257],{"class":87,"line":149},[85,258,259],{},"@pytest.mark.parametrize(\n",[85,261,262],{"class":87,"line":154},[85,263,264],{},"    \"seeded_db,expected_total\",\n",[85,266,267],{"class":87,"line":160},[85,268,269],{},"    [\n",[85,271,272],{"class":87,"line":166},[85,273,274],{},"        ([{\"amount\": 10}, {\"amount\": 5}], 15),\n",[85,276,277],{"class":87,"line":172},[85,278,279],{},"        ([], 0),\n",[85,281,283],{"class":87,"line":282},16,[85,284,285],{},"        ([{\"amount\": -3}], -3),\n",[85,287,289],{"class":87,"line":288},17,[85,290,291],{},"    ],\n",[85,293,295],{"class":87,"line":294},18,[85,296,297],{},"    indirect=[\"seeded_db\"],                # expected_total is passed through as-is\n",[85,299,301],{"class":87,"line":300},19,[85,302,303],{},")\n",[85,305,307],{"class":87,"line":306},20,[85,308,309],{},"def test_total(seeded_db, expected_total):\n",[85,311,313],{"class":87,"line":312},21,[85,314,315],{},"    assert seeded_db.total() == expected_total\n",[10,317,318,319,322],{},"The alternative — a fixture with ",[13,320,321],{},"params=[...]"," — produces the same cross-product behaviour but binds the values to the fixture definition, so every test using that fixture inherits them. Indirect parametrization keeps the values at the call site, which is what you want when different tests need different data through the same construction logic.",[324,325,328,450],"figure",{"className":326},[327],"diagram",[329,330,337,338,337,342,337,346,337,364,337,372,337,381,337,390,337,396,337,401,337,407,337,410,337,413,337,416,337,420,337,423,337,427,337,430,337,434,337,437,337,441,337,444],"svg",{"viewBox":331,"role":332,"ariaLabelledBy":333,"xmlns":336},"0 0 760 172","img",[334,335],"indirectflow-t","indirectflow-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[339,340,341],"title",{"id":334},"Where the parameter goes with and without indirect",[343,344,345],"desc",{"id":335},"A left-to-right comparison: without indirect, the parametrize value is passed straight into the test function; with indirect, the same value is set on request.param, the fixture builds an object from it, and the test receives the constructed object.",[347,348,349,350,337],"defs",{},"\n    ",[351,352,359],"marker",{"id":353,"viewBox":354,"refX":355,"refY":356,"markerWidth":357,"markerHeight":357,"orient":358},"indirectflow-a","0 0 10 10","9","5","7","auto-start-reverse",[360,361],"path",{"d":362,"fill":363},"M0 0 L10 5 L0 10 z","#81b29a",[365,366],"rect",{"x":367,"y":367,"width":368,"height":369,"rx":370,"fill":371},"0","760","172","14","#fffdf8",[373,374,341],"text",{"x":375,"y":376,"textAnchor":377,"fontSize":378,"fontWeight":379,"fill":380},"380","30","middle","15","700","#3d405b",[365,382],{"x":383,"y":384,"width":385,"height":386,"rx":387,"fill":388,"stroke":363,"strokeWidth":389},"26","62","142","76","12","#f4f1de","1.8",[373,391,395],{"x":392,"y":393,"textAnchor":377,"fontSize":394,"fontWeight":379,"fill":380},"97","96","12.5","parametrize value",[373,397,400],{"x":392,"y":398,"textAnchor":377,"fontSize":399,"fill":380},"113","11","a plain string",[87,402],{"x1":403,"y1":404,"x2":405,"y2":404,"stroke":363,"strokeWidth":389,"markerEnd":406},"176","100","208","url(#indirectflow-a)",[365,408],{"x":409,"y":384,"width":385,"height":386,"rx":387,"fill":371,"stroke":363,"strokeWidth":389},"214",[373,411,69],{"x":412,"y":393,"textAnchor":377,"fontSize":394,"fontWeight":379,"fill":380},"286",[373,414,415],{"x":412,"y":398,"textAnchor":377,"fontSize":399,"fill":380},"set per test item",[87,417],{"x1":418,"y1":404,"x2":419,"y2":404,"stroke":363,"strokeWidth":389,"markerEnd":406},"364","396",[365,421],{"x":422,"y":384,"width":385,"height":386,"rx":387,"fill":388,"stroke":363,"strokeWidth":389},"403",[373,424,426],{"x":425,"y":393,"textAnchor":377,"fontSize":394,"fontWeight":379,"fill":380},"474","fixture builds",[373,428,429],{"x":425,"y":398,"textAnchor":377,"fontSize":399,"fill":380},"setup and teardown",[87,431],{"x1":432,"y1":404,"x2":433,"y2":404,"stroke":363,"strokeWidth":389,"markerEnd":406},"552","584",[365,435],{"x":436,"y":384,"width":385,"height":386,"rx":387,"fill":371,"stroke":363,"strokeWidth":389},"592",[373,438,440],{"x":439,"y":393,"textAnchor":377,"fontSize":394,"fontWeight":379,"fill":380},"663","test receives",[373,442,443],{"x":439,"y":398,"textAnchor":377,"fontSize":399,"fill":380},"the built object",[373,445,449],{"x":375,"y":446,"textAnchor":377,"fontSize":447,"fontStyle":448,"fill":380},"164","11.5","italic","Without indirect=True the test would receive the raw string and the fixture would never run.",[451,452,453],"figcaption",{},"The middle two stages are what indirect adds: the value becomes fixture input rather than test input.",[23,455,457],{"id":456},"why-this-works","Why this works",[10,459,460,463,464,467,468,470],{},[13,461,462],{},"parametrize"," attaches a ",[13,465,466],{},"callspec"," to each generated test item, mapping argument names to values. During fixture resolution pytest checks whether each requested name is a fixture: if the name is marked indirect, the value is stashed on the fixture's ",[13,469,69],{}," and the fixture function runs to produce the actual argument. If it is not, the value is bound directly to the test's parameter. Because the fixture executes per item, its finalizer runs per item too — which is the entire reason to reach for indirection rather than building the object in the test body.",[324,472,474,598],{"className":473},[327],[329,475,337,480,337,483,337,486,337,504,337,507,337,509,337,516,337,520,337,526,337,529,337,531,337,534,337,537,337,541,337,544,337,550,337,555,337,559,337,565,337,570,337,575,337,582,337,586,337,590,337,594],{"viewBox":476,"role":332,"ariaLabelledBy":477,"xmlns":336},"0 0 760 430",[478,479],"indirectseq-t","indirectseq-d",[339,481,482],{"id":478},"The sequence for one indirect parameter",[343,484,485],{"id":479},"A sequence diagram with three lanes: the collector, the fixture and the test. The collector creates one item per parameter and sets request.param, the fixture builds the object and yields it, the test runs against the object, and the fixture finalizer tears it down before the next parameter.",[347,487,349,488,349,493,349,499,337],{},[351,489,491],{"id":490,"viewBox":354,"refX":355,"refY":356,"markerWidth":357,"markerHeight":357,"orient":358},"indirectseq-a",[360,492],{"d":362,"fill":380},[351,494,496],{"id":495,"viewBox":354,"refX":355,"refY":356,"markerWidth":357,"markerHeight":357,"orient":358},"indirectseq-c",[360,497],{"d":362,"fill":498},"#e07a5f",[351,500,502],{"id":501,"viewBox":354,"refX":355,"refY":356,"markerWidth":357,"markerHeight":357,"orient":358},"indirectseq-s",[360,503],{"d":362,"fill":363},[365,505],{"x":367,"y":367,"width":368,"height":506,"rx":370,"fill":371},"430",[373,508,482],{"x":375,"y":376,"textAnchor":377,"fontSize":378,"fontWeight":379,"fill":380},[365,510],{"x":511,"y":512,"width":513,"height":514,"rx":515,"fill":388,"stroke":380,"strokeWidth":389},"34","52","220","40","10",[373,517,519],{"x":518,"y":386,"textAnchor":377,"fontSize":387,"fontWeight":379,"fill":380},"144","collector",[87,521],{"x1":518,"y1":522,"x2":518,"y2":523,"stroke":380,"strokeWidth":524,"strokeDashArray":525},"98","414","1.2",[356,356],[365,527],{"x":528,"y":512,"width":513,"height":514,"rx":515,"fill":388,"stroke":380,"strokeWidth":389},"270",[373,530,73],{"x":375,"y":386,"textAnchor":377,"fontSize":387,"fontWeight":379,"fill":380},[87,532],{"x1":375,"y1":522,"x2":375,"y2":523,"stroke":380,"strokeWidth":524,"strokeDashArray":533},[356,356],[365,535],{"x":536,"y":512,"width":513,"height":514,"rx":515,"fill":388,"stroke":380,"strokeWidth":389},"506",[373,538,540],{"x":539,"y":386,"textAnchor":377,"fontSize":387,"fontWeight":379,"fill":380},"616","test",[87,542],{"x1":539,"y1":522,"x2":539,"y2":523,"stroke":380,"strokeWidth":524,"strokeDashArray":543},[356,356],[87,545],{"x1":546,"y1":547,"x2":548,"y2":547,"stroke":380,"strokeWidth":389,"markerEnd":549},"152","126","372","url(#indirectseq-a)",[373,551,554],{"x":552,"y":553,"textAnchor":377,"fontSize":399,"fill":380},"262","117","set request.param",[360,556],{"d":557,"fill":558,"stroke":380,"strokeWidth":389,"markerEnd":549},"M380 180 h 46 v 22 h -40","none",[373,560,564],{"x":561,"y":562,"textAnchor":563,"fontSize":399,"fill":380},"436","184","start","build the object",[87,566],{"x1":567,"y1":568,"x2":569,"y2":568,"stroke":380,"strokeWidth":389,"markerEnd":549},"388","234","608",[373,571,574],{"x":572,"y":573,"textAnchor":377,"fontSize":399,"fill":380},"498","225","yield the object",[87,576],{"x1":569,"y1":577,"x2":567,"y2":577,"stroke":363,"strokeWidth":389,"strokeDashArray":578,"markerEnd":581},"288",[579,580],"6","4","url(#indirectseq-s)",[373,583,585],{"x":572,"y":584,"textAnchor":377,"fontSize":399,"fill":380},"279","test finishes",[87,587],{"x1":548,"y1":588,"x2":546,"y2":588,"stroke":363,"strokeWidth":389,"strokeDashArray":589,"markerEnd":581},"342",[579,580],[373,591,593],{"x":552,"y":592,"textAnchor":377,"fontSize":399,"fill":380},"333","teardown, next param",[373,595,597],{"x":375,"y":596,"textAnchor":377,"fontSize":399,"fontStyle":448,"fill":380},"418","Each parametrize value produces a separate test item with its own fixture lifecycle.",[451,599,600],{},"Setup and teardown bracket each parameter individually, which a value passed directly to the test body cannot do.",[23,602,604],{"id":603},"verifying-the-setup-really-runs-per-parameter","Verifying the setup really runs per parameter",[10,606,607],{},"The whole reason for indirection is that the fixture's setup and teardown bracket each parameter. That is worth checking rather than assuming, because a fixture accidentally left at module scope produces the same passing tests with one shared object underneath.",[76,609,613],{"className":610,"code":611,"language":612,"meta":81,"style":81},"language-console shiki shiki-themes github-light github-dark","$ pytest tests\u002Ftest_api.py --setup-show -q\n        SETUP    F api_client[staging]\n        tests\u002Ftest_api.py::test_health_endpoint[staging] (fixtures used: api_client)\n        TEARDOWN F api_client[staging]\n        SETUP    F api_client[sandbox]\n        tests\u002Ftest_api.py::test_health_endpoint[sandbox] (fixtures used: api_client)\n        TEARDOWN F api_client[sandbox]\n","console",[13,614,615,620,625,630,635,640,645],{"__ignoreMap":81},[85,616,617],{"class":87,"line":88},[85,618,619],{},"$ pytest tests\u002Ftest_api.py --setup-show -q\n",[85,621,622],{"class":87,"line":94},[85,623,624],{},"        SETUP    F api_client[staging]\n",[85,626,627],{"class":87,"line":101},[85,628,629],{},"        tests\u002Ftest_api.py::test_health_endpoint[staging] (fixtures used: api_client)\n",[85,631,632],{"class":87,"line":107},[85,633,634],{},"        TEARDOWN F api_client[staging]\n",[85,636,637],{"class":87,"line":113},[85,638,639],{},"        SETUP    F api_client[sandbox]\n",[85,641,642],{"class":87,"line":119},[85,643,644],{},"        tests\u002Ftest_api.py::test_health_endpoint[sandbox] (fixtures used: api_client)\n",[85,646,647],{"class":87,"line":125},[85,648,649],{},"        TEARDOWN F api_client[sandbox]\n",[10,651,652,653,656],{},"Two setups, two teardowns, and the parameter visible in brackets after the fixture name: that is a correctly indirect fixture. A single ",[13,654,655],{},"SETUP M api_client"," followed by two tests means the fixture is module-scoped and both parameters are sharing one object — which for a stateful client means the second test is running against state the first one left behind.",[10,658,659],{},"The same output also shows the ordering guarantee that makes indirection useful for expensive resources. Because teardown runs before the next parameter's setup, two parameters that bind the same port, database name or temporary path do not collide — something a fixture parametrized at module scope cannot promise.",[10,661,662,663,666],{},"For a quick structural check without running anything, ",[13,664,665],{},"--collect-only -q"," lists the generated items and their ids:",[76,668,670],{"className":610,"code":669,"language":612,"meta":81,"style":81},"$ pytest tests\u002Ftest_totals.py --collect-only -q\ntests\u002Ftest_totals.py::test_total[seeded_db0-15]\ntests\u002Ftest_totals.py::test_total[seeded_db1-0]\ntests\u002Ftest_totals.py::test_total[seeded_db2--3]\n",[13,671,672,677,682,687],{"__ignoreMap":81},[85,673,674],{"class":87,"line":88},[85,675,676],{},"$ pytest tests\u002Ftest_totals.py --collect-only -q\n",[85,678,679],{"class":87,"line":94},[85,680,681],{},"tests\u002Ftest_totals.py::test_total[seeded_db0-15]\n",[85,683,684],{"class":87,"line":101},[85,685,686],{},"tests\u002Ftest_totals.py::test_total[seeded_db1-0]\n",[85,688,689],{"class":87,"line":107},[85,690,691],{},"tests\u002Ftest_totals.py::test_total[seeded_db2--3]\n",[10,693,694,695,698,699,59],{},"The ",[13,696,697],{},"seeded_db0"," style id is the signal that a value went through a fixture rather than being rendered directly — pytest cannot show the contents of a list it never passed to the test, so it numbers them. When that hurts readability, supply explicit ids at the call site, which is the subject of ",[50,700,702],{"href":701},"\u002Fadvanced-pytest-architecture-configuration\u002Fadvanced-parametrization-techniques\u002Fgenerating-readable-test-ids\u002F","generating readable test ids",[324,704,706,799],{"className":705},[327],[329,707,337,712,337,715,337,718,337,720,337,722,337,728,337,732,337,736,337,740,337,744,337,748,337,751,337,754,337,757,337,761,337,764,337,767,337,769,337,773,337,776,337,779,337,782,337,786,337,789,337,792,337,796],{"viewBox":708,"role":332,"ariaLabelledBy":709,"xmlns":336},"0 0 760 270",[710,711],"indirectcheck-t","indirectcheck-d",[339,713,714],{"id":710},"Reading --setup-show for an indirect fixture",[343,716,717],{"id":711},"A table mapping three setup-show patterns to what they mean: one setup per parameter with the parameter in brackets is correct, a single module-scoped setup means the object is shared, and a setup with no bracketed parameter means indirect was not applied.",[365,719],{"x":367,"y":367,"width":368,"height":528,"rx":370,"fill":371},[373,721,714],{"x":375,"y":376,"textAnchor":377,"fontSize":378,"fontWeight":379,"fill":380},[365,723],{"x":724,"y":512,"width":725,"height":514,"rx":515,"fill":726,"stroke":380,"strokeWidth":727},"24","712","#f2cc8f","1.5",[373,729,731],{"x":730,"y":386,"fontSize":387,"fontWeight":379,"fill":380},"38","Criterion",[373,733,735],{"x":734,"y":386,"textAnchor":377,"fontSize":387,"fontWeight":379,"fill":380},"390","Means",[373,737,739],{"x":738,"y":386,"textAnchor":377,"fontSize":387,"fontWeight":379,"fill":380},"620","Action",[365,741],{"x":724,"y":742,"width":725,"height":514,"fill":388,"stroke":380,"strokeWidth":743},"92","1",[373,745,747],{"x":730,"y":746,"fontSize":447,"fill":380},"116","SETUP F name[param] per case",[373,749,750],{"x":734,"y":746,"textAnchor":377,"fontSize":447,"fill":380},"correct indirection",[373,752,753],{"x":738,"y":746,"textAnchor":377,"fontSize":447,"fill":380},"nothing to do",[365,755],{"x":724,"y":756,"width":725,"height":514,"fill":371,"stroke":380,"strokeWidth":743},"132",[373,758,760],{"x":730,"y":759,"fontSize":447,"fill":380},"156","one SETUP M for all cases",[373,762,763],{"x":734,"y":759,"textAnchor":377,"fontSize":447,"fill":380},"object is shared",[373,765,766],{"x":738,"y":759,"textAnchor":377,"fontSize":447,"fill":380},"narrow the fixture scope",[365,768],{"x":724,"y":369,"width":725,"height":514,"fill":388,"stroke":380,"strokeWidth":743},[373,770,772],{"x":730,"y":771,"fontSize":447,"fill":380},"196","SETUP with no [param]",[373,774,775],{"x":734,"y":771,"textAnchor":377,"fontSize":447,"fill":380},"indirect not applied",[373,777,778],{"x":738,"y":771,"textAnchor":377,"fontSize":447,"fill":380},"check the argument name",[365,780],{"x":724,"y":781,"width":725,"height":514,"fill":371,"stroke":380,"strokeWidth":743},"212",[373,783,785],{"x":730,"y":784,"fontSize":447,"fill":380},"236","no SETUP line at all",[373,787,788],{"x":734,"y":784,"textAnchor":377,"fontSize":447,"fill":380},"fixture never ran",[373,790,791],{"x":738,"y":784,"textAnchor":377,"fontSize":447,"fill":380},"name mismatch in indirect",[87,793],{"x1":794,"y1":512,"x2":794,"y2":795,"stroke":380,"strokeWidth":743},"274","252",[87,797],{"x1":798,"y1":512,"x2":798,"y2":795,"stroke":380,"strokeWidth":743},"505",[451,800,801],{},"The bracketed parameter in the setup line is the proof that the value was routed through the fixture rather than into the test.",[23,803,805],{"id":804},"edge-cases-and-failure-modes","Edge cases and failure modes",[28,807,808,824,837,851,857],{},[31,809,810,815,816,819,820,823],{},[34,811,812,814],{},[13,813,69],{}," does not exist when the fixture is used without parametrization."," A fixture written for indirect use raises ",[13,817,818],{},"AttributeError"," if another test requests it plainly. Guard with ",[13,821,822],{},"getattr(request, \"param\", DEFAULT)"," when the fixture must serve both.",[31,825,826,829,830,833,834,836],{},[34,827,828],{},"The argument name must match the fixture name exactly."," ",[13,831,832],{},"indirect=[\"client\"]"," with a fixture called ",[13,835,185],{}," silently treats the value as direct — there is no error, and the test receives the raw string.",[31,838,839,846,847,850],{},[34,840,841,842,845],{},"Indirect and ",[13,843,844],{},"ids"," interact."," Because the test never sees the raw value, the default id is derived from it anyway, which is usually what you want; pass ",[13,848,849],{},"ids="," explicitly when the raw value is an unreadable structure.",[31,852,853,856],{},[34,854,855],{},"Stacking indirect parametrize decorators multiplies fixture setups."," Two indirect parameters with three values each produce nine items and nine full setup\u002Fteardown cycles, which is fine for in-memory objects and expensive for containers.",[31,858,859,862,863,866,867,59],{},[34,860,861],{},"Indirect values are computed at collection, fixtures at setup."," A parameter list built from something that only exists at runtime — a database query, an environment probe — belongs in ",[13,864,865],{},"pytest_generate_tests",", not in a decorator; see ",[50,868,58],{"href":57},[23,870,872],{"id":871},"choosing-between-indirect-and-a-parametrized-fixture","Choosing between indirect and a parametrized fixture",[10,874,875],{},"Two mechanisms produce the same visible result — one test item per value, with fixture setup per value — and picking the wrong one shows up as duplication months later.",[10,877,878,879,882,883,886,887,890],{},"A ",[34,880,881],{},"parametrized fixture"," (",[13,884,885],{},"@pytest.fixture(params=[...])",") binds the values to the fixture. Every test that requests it runs once per value, automatically, whether or not that test cares. This is the right tool when the parameter is a property of the ",[18,888,889],{},"environment",": run the whole suite against SQLite and Postgres, against both async backends, against two API versions.",[76,892,894],{"className":78,"code":893,"language":80,"meta":81,"style":81},"import pytest\n\n@pytest.fixture(params=[\"sqlite\", \"postgres\"])   # every consumer runs twice\ndef store(request):\n    backend = request.param\n    store = make_store(backend)\n    yield store\n    store.close()\n",[13,895,896,900,904,909,914,918,923,928],{"__ignoreMap":81},[85,897,898],{"class":87,"line":88},[85,899,91],{},[85,901,902],{"class":87,"line":94},[85,903,98],{"emptyLinePlaceholder":97},[85,905,906],{"class":87,"line":101},[85,907,908],{},"@pytest.fixture(params=[\"sqlite\", \"postgres\"])   # every consumer runs twice\n",[85,910,911],{"class":87,"line":107},[85,912,913],{},"def store(request):\n",[85,915,916],{"class":87,"line":113},[85,917,122],{},[85,919,920],{"class":87,"line":119},[85,921,922],{},"    store = make_store(backend)\n",[85,924,925],{"class":87,"line":125},[85,926,927],{},"    yield store\n",[85,929,930],{"class":87,"line":131},[85,931,932],{},"    store.close()\n",[10,934,935,938,939,942],{},[34,936,937],{},"Indirect parametrization"," keeps the values at the call site, so one test can run against three datasets while its neighbour runs against one. This is the right tool when the parameter is a property of the ",[18,940,941],{},"case",": the specific rows, payload or configuration that this test needs.",[10,944,945],{},"The failure mode of choosing wrongly is recognisable. A parametrized fixture used for case data forces every consumer through cases they do not care about, doubling suite runtime for no coverage; indirect parametrization used for an environment axis means every test file repeats the same list, and adding a third backend becomes a repository-wide edit.",[10,947,948,949,952],{},"When both apply — an environment axis ",[18,950,951],{},"and"," per-case data — they compose: the fixture supplies the backend, the indirect parameter supplies the dataset, and the generated items are the product of the two. Check that product before merging, because six cases per test across four backends is twenty-four items and a visibly slower suite.",[324,954,956,1019],{"className":955},[327],[329,957,337,962,337,965,337,968,337,971,337,973,337,975,337,977,337,980,337,983,337,985,337,987,337,990,337,993,337,995,337,998,337,1001,337,1004,337,1006,337,1009,337,1012,337,1015,337,1017],{"viewBox":958,"role":332,"ariaLabelledBy":959,"xmlns":336},"0 0 760 230",[960,961],"indvsfix-t","indvsfix-d",[339,963,964],{"id":960},"Indirect parametrization versus a parametrized fixture",[343,966,967],{"id":961},"A table comparing indirect parametrization and a parametrized fixture across where the values are declared, who runs the extra cases, and the kind of axis each suits.",[365,969],{"x":367,"y":367,"width":368,"height":970,"rx":370,"fill":371},"230",[373,972,964],{"x":375,"y":376,"textAnchor":377,"fontSize":378,"fontWeight":379,"fill":380},[365,974],{"x":724,"y":512,"width":725,"height":514,"rx":515,"fill":726,"stroke":380,"strokeWidth":727},[373,976,731],{"x":730,"y":386,"fontSize":387,"fontWeight":379,"fill":380},[373,978,979],{"x":734,"y":386,"textAnchor":377,"fontSize":387,"fontWeight":379,"fill":380},"Values declared",[373,981,982],{"x":738,"y":386,"textAnchor":377,"fontSize":387,"fontWeight":379,"fill":380},"Applies to",[365,984],{"x":724,"y":742,"width":725,"height":514,"fill":388,"stroke":380,"strokeWidth":743},[373,986,181],{"x":730,"y":746,"fontSize":447,"fill":380},[373,988,989],{"x":734,"y":746,"textAnchor":377,"fontSize":447,"fill":380},"at the call site",[373,991,992],{"x":738,"y":746,"textAnchor":377,"fontSize":447,"fill":380},"one test",[365,994],{"x":724,"y":756,"width":725,"height":514,"fill":371,"stroke":380,"strokeWidth":743},[373,996,997],{"x":730,"y":759,"fontSize":447,"fill":380},"fixture(params=)",[373,999,1000],{"x":734,"y":759,"textAnchor":377,"fontSize":447,"fill":380},"on the fixture",[373,1002,1003],{"x":738,"y":759,"textAnchor":377,"fontSize":447,"fill":380},"every consumer",[365,1005],{"x":724,"y":369,"width":725,"height":514,"fill":388,"stroke":380,"strokeWidth":743},[373,1007,1008],{"x":730,"y":771,"fontSize":447,"fill":380},"both together",[373,1010,1011],{"x":734,"y":771,"textAnchor":377,"fontSize":447,"fill":380},"case and environment",[373,1013,1014],{"x":738,"y":771,"textAnchor":377,"fontSize":447,"fill":380},"the product of the two",[87,1016],{"x1":794,"y1":512,"x2":794,"y2":781,"stroke":380,"strokeWidth":743},[87,1018],{"x1":798,"y1":512,"x2":798,"y2":781,"stroke":380,"strokeWidth":743},[451,1020,1021],{},"Per-case data belongs at the call site; an environment axis belongs on the fixture — mixing them is what makes a suite slow.",[23,1023,1025],{"id":1024},"frequently-asked-questions","Frequently Asked Questions",[10,1027,1028,1031],{},[34,1029,1030],{},"When do I need indirect=True instead of a plain parametrize?","\nWhen the parameter must be processed by a fixture before the test sees it — building a client, seeding a database, or constructing an object whose setup and teardown belong in the fixture rather than in the test body.",[10,1033,1034,1037,1038,1040,1041,1044],{},[34,1035,1036],{},"Can I parametrize only some arguments indirectly?","\nYes. Pass a list of argument names to ",[13,1039,40],{},", such as ",[13,1042,1043],{},"indirect=[\"backend\"]",", and pytest routes those names through their fixtures while the remaining arguments are passed to the test directly.",[10,1046,1047,1050,1051,1053,1054,1057,1058,1061,1062,1064],{},[34,1048,1049],{},"Why does request.param raise AttributeError?","\nBecause the fixture was requested without a parameter. ",[13,1052,69],{}," only exists when the fixture is parametrized, so guard with ",[13,1055,1056],{},"getattr(request, \"param\", default)"," if the fixture must also work un-parametrized.\n",[34,1059,1060],{},"Does indirect parametrization work with fixtures defined in a plugin?","\nYes — the mechanism only requires that the argument name resolves to a fixture, wherever it is defined. That makes indirection a good fit for shared infrastructure fixtures: the plugin owns the construction logic, and each test supplies the data it needs through the decorator. The one caveat is that the plugin's fixture must read ",[13,1063,69],{}," defensively, since other tests will request it without a parameter.",[23,1066,1068],{"id":1067},"related","Related",[28,1070,1071,1080,1089,1096],{},[31,1072,1073,1076,1077,1079],{},[50,1074,1075],{"href":57},"Advanced parametrization techniques"," — the direct forms, ",[13,1078,865],{},", and id control.",[31,1081,1082,1085,1086,1088],{},[50,1083,1084],{"href":52},"Mastering pytest fixtures"," — how ",[13,1087,47],{}," and fixture finalization work underneath this.",[31,1090,1091,1095],{},[50,1092,1094],{"href":1093},"\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Ffixing-scopemismatch-errors-in-pytest\u002F","Fixing ScopeMismatch errors in pytest"," — what happens when an indirect fixture is widened past its dependencies.",[31,1097,1098,1101],{},[50,1099,1100],{"href":701},"Generating readable test ids"," — keeping nine generated items identifiable in CI output.",[10,1103,1104,1105],{},"← Back to ",[50,1106,1107],{"href":57},"Advanced Parametrization Techniques",[1109,1110,1111],"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":81,"searchDepth":94,"depth":94,"links":1113},[1114,1115,1116,1117,1118,1119,1120,1121],{"id":25,"depth":94,"text":26},{"id":62,"depth":94,"text":63},{"id":456,"depth":94,"text":457},{"id":603,"depth":94,"text":604},{"id":804,"depth":94,"text":805},{"id":871,"depth":94,"text":872},{"id":1024,"depth":94,"text":1025},{"id":1067,"depth":94,"text":1068},"Use pytest indirect parametrization to pass parameters into fixtures: when indirect=True is required, partial indirection, and how request.param is resolved.","md",{"slug":1125,"type":1126,"breadcrumb":1127,"datePublished":1128,"dateModified":1128,"faq":1129,"howto":1136},"indirect-parametrization-with-fixtures","article","Indirect Parametrization","2026-08-01",[1130,1132,1134],{"q":1030,"a":1131},"When the parameter must be processed by a fixture before the test sees it — building a client, seeding a database, or constructing an object whose setup and teardown belong in the fixture rather than in the test body.",{"q":1036,"a":1133},"Yes. Pass a list of argument names to indirect, such as indirect=['backend'], and pytest routes those names through their fixtures while the remaining arguments are passed to the test directly.",{"q":1049,"a":1135},"Because the fixture was requested without a parameter. request.param only exists when the fixture is parametrized, so guard with getattr(request, 'param', default) if the fixture must also work un-parametrized.",{"name":1137,"description":1138,"steps":1139},"How to use indirect parametrization in pytest","Route parametrize values through a fixture so setup and teardown happen per parameter.",[1140,1143,1146,1149],{"name":1141,"text":1142},"Write a fixture that reads request.param","The fixture receives the value through the request object and returns the constructed object.",{"name":1144,"text":1145},"Parametrize with indirect=True","Name the fixture as the argument so pytest routes the values to it instead of to the test.",{"name":1147,"text":1148},"Use partial indirection for mixed arguments","Pass a list of names to indirect so only those arguments go through fixtures.",{"name":1150,"text":1151},"Confirm with --setup-show","Check that setup and teardown run once per parameter, not once for the whole set.","\u002Fadvanced-pytest-architecture-configuration\u002Fadvanced-parametrization-techniques\u002Findirect-parametrization-with-fixtures",{"title":5,"description":1122},"advanced-pytest-architecture-configuration\u002Fadvanced-parametrization-techniques\u002Findirect-parametrization-with-fixtures\u002Findex","W7g8ISVUx81BqpMhKqTpnIQ-13LnlU_jZPR5O8OqIa0",1785613404346]