[{"data":1,"prerenderedAt":872},["ShallowReactive",2],{"page-\u002Fadvanced-pytest-architecture-configuration\u002Ftest-layout-and-import-modes\u002Fusing-importmode-importlib-without-init-files\u002F":3},{"id":4,"title":5,"body":6,"description":835,"extension":836,"meta":837,"navigation":156,"path":868,"seo":869,"stem":870,"__hash__":871},"content\u002Fadvanced-pytest-architecture-configuration\u002Ftest-layout-and-import-modes\u002Fusing-importmode-importlib-without-init-files\u002Findex.md","Using importmode=importlib Without __init__.py Files",{"type":7,"value":8,"toc":825},"minimark",[9,21,24,29,59,63,66,106,135,169,240,243,408,412,429,439,443,501,505,508,529,538,545,548,559,634,638,649,662,669,672,736,743,747,762,771,783,787,816,821],[10,11,12,16,17,20],"p",{},[13,14,15],"code",{},"--import-mode=importlib"," is the import mode pytest's own documentation recommends for new projects, and the one that makes ",[13,18,19],{},"import file mismatch"," impossible. Switching an existing suite to it is a short, mechanical change, with one genuine piece of work — relocating any helpers that test files import from each other — and one verification step that turns the switch from a hope into a fact.",[10,22,23],{},"The payoff is a test tree that is no longer a Python package, needs no marker files, cannot collide with other distributions' module names, and behaves identically whichever directory pytest is launched from. For most suites the whole change fits in an afternoon, and the cross-test import cleanup it forces is an improvement independent of the import mode.",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,32,43,56],"ul",{},[33,34,35,38,39,42],"li",{},[13,36,37],{},"pytest >= 8.0",", where ",[13,40,41],{},"importlib"," mode handles namespace packages and rootdir-relative names reliably.",[33,44,45,46,49,50,55],{},"The package under test installed with ",[13,47,48],{},"pip install -e .",", so it is importable without path manipulation — see ",[51,52,54],"a",{"href":53},"\u002Fadvanced-pytest-architecture-configuration\u002Ftest-layout-and-import-modes\u002F","test layout and import modes",".",[33,57,58],{},"A green suite to start from, so any failure after the switch is attributable to the switch.",[25,60,62],{"id":61},"solution","Solution",[10,64,65],{},"Record the baseline, switch the mode, fix what breaks, then remove the marker files.",[67,68,73],"pre",{"className":69,"code":70,"language":71,"meta":72,"style":72},"language-bash shiki shiki-themes github-light github-dark","# 1. Baseline: exactly which tests are collected today.\npytest --collect-only -q > \u002Ftmp\u002Fbefore.txt\n","bash","",[13,74,75,84],{"__ignoreMap":72},[76,77,80],"span",{"class":78,"line":79},"line",1,[76,81,83],{"class":82},"sJ8bj","# 1. Baseline: exactly which tests are collected today.\n",[76,85,87,91,95,98,102],{"class":78,"line":86},2,[76,88,90],{"class":89},"sScJk","pytest",[76,92,94],{"class":93},"sj4cs"," --collect-only",[76,96,97],{"class":93}," -q",[76,99,101],{"class":100},"szBVR"," >",[76,103,105],{"class":104},"sZZnC"," \u002Ftmp\u002Fbefore.txt\n",[67,107,111],{"className":108,"code":109,"language":110,"meta":72,"style":72},"language-toml shiki shiki-themes github-light github-dark","# 2. The switch, in configuration so IDEs and CI inherit it.\n[tool.pytest.ini_options]\naddopts = \"--import-mode=importlib\"\ntestpaths = [\"tests\"]\n","toml",[13,112,113,118,123,129],{"__ignoreMap":72},[76,114,115],{"class":78,"line":79},[76,116,117],{},"# 2. The switch, in configuration so IDEs and CI inherit it.\n",[76,119,120],{"class":78,"line":86},[76,121,122],{},"[tool.pytest.ini_options]\n",[76,124,126],{"class":78,"line":125},3,[76,127,128],{},"addopts = \"--import-mode=importlib\"\n",[76,130,132],{"class":78,"line":131},4,[76,133,134],{},"testpaths = [\"tests\"]\n",[67,136,140],{"className":137,"code":138,"language":139,"meta":72,"style":72},"language-python shiki shiki-themes github-light github-dark","# 3. BEFORE: a test importing a sibling by bare name — breaks under importlib.\nfrom test_helpers import make_order          # tests\u002F is no longer on sys.path\n\n# AFTER: helpers live in a real, importable module.\nfrom tests_support.orders import make_order  # installed, or found via rootdir\n","python",[13,141,142,147,152,158,163],{"__ignoreMap":72},[76,143,144],{"class":78,"line":79},[76,145,146],{},"# 3. BEFORE: a test importing a sibling by bare name — breaks under importlib.\n",[76,148,149],{"class":78,"line":86},[76,150,151],{},"from test_helpers import make_order          # tests\u002F is no longer on sys.path\n",[76,153,154],{"class":78,"line":125},[76,155,157],{"emptyLinePlaceholder":156},true,"\n",[76,159,160],{"class":78,"line":131},[76,161,162],{},"# AFTER: helpers live in a real, importable module.\n",[76,164,166],{"class":78,"line":165},5,[76,167,168],{},"from tests_support.orders import make_order  # installed, or found via rootdir\n",[67,170,172],{"className":69,"code":171,"language":71,"meta":72,"style":72},"# 4. Once green, remove the markers and compare collection.\nfind tests -name \"__init__.py\" -delete\npytest --collect-only -q > \u002Ftmp\u002Fafter.txt\ndiff \u003C(sort \u002Ftmp\u002Fbefore.txt) \u003C(sort \u002Ftmp\u002Fafter.txt) && echo \"collection unchanged\"\n",[13,173,174,179,196,209],{"__ignoreMap":72},[76,175,176],{"class":78,"line":79},[76,177,178],{"class":82},"# 4. Once green, remove the markers and compare collection.\n",[76,180,181,184,187,190,193],{"class":78,"line":86},[76,182,183],{"class":89},"find",[76,185,186],{"class":104}," tests",[76,188,189],{"class":93}," -name",[76,191,192],{"class":104}," \"__init__.py\"",[76,194,195],{"class":93}," -delete\n",[76,197,198,200,202,204,206],{"class":78,"line":125},[76,199,90],{"class":89},[76,201,94],{"class":93},[76,203,97],{"class":93},[76,205,101],{"class":100},[76,207,208],{"class":104}," \u002Ftmp\u002Fafter.txt\n",[76,210,211,214,217,220,223,225,227,230,234,237],{"class":78,"line":131},[76,212,213],{"class":89},"diff",[76,215,216],{"class":104}," \u003C(",[76,218,219],{"class":89},"sort",[76,221,222],{"class":104}," \u002Ftmp\u002Fbefore.txt)",[76,224,216],{"class":104},[76,226,219],{"class":89},[76,228,229],{"class":104}," \u002Ftmp\u002Fafter.txt)",[76,231,233],{"class":232},"sVt8B"," && ",[76,235,236],{"class":93},"echo",[76,238,239],{"class":104}," \"collection unchanged\"\n",[10,241,242],{},"An empty diff is the verification. The same tests, collected under the same ids, with none of the marker files and none of the collision risk.",[244,245,248,404],"figure",{"className":246},[247],"diagram",[249,250,257,258,257,262,257,266,257,284,257,292,257,301,257,310,257,316,257,321,257,325,257,331,257,337,257,341,257,345,257,348,257,352,257,357,257,361,257,365,257,369,257,373,257,379,257,383,257,387,257,391,257,399],"svg",{"viewBox":251,"role":252,"ariaLabelledBy":253,"xmlns":256},"0 0 820 262","img",[254,255],"iml-t","iml-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[259,260,261],"title",{"id":254},"Four steps to switch import mode safely",[263,264,265],"desc",{"id":255},"A sequence of four steps. Record the baseline collection. Set importlib mode in configuration. Fix the cross-test imports that break by moving helpers into a proper module. Delete the init files and compare collection against the baseline, expecting no difference.",[267,268,269,270,257],"defs",{},"\n    ",[271,272,279],"marker",{"id":273,"viewBox":274,"refX":275,"refY":276,"markerWidth":277,"markerHeight":277,"orient":278},"iml-a","0 0 10 10","9","5","7","auto-start-reverse",[280,281],"path",{"d":282,"fill":283},"M0 0 L10 5 L0 10 z","#3d405b",[285,286],"rect",{"x":287,"y":287,"width":288,"height":289,"rx":290,"fill":291},"0","820","262","14","#fffdf8",[293,294,300],"text",{"x":295,"y":296,"textAnchor":297,"fontSize":298,"fontWeight":299,"fill":283},"410","28","middle","16","700","Baseline first, so the switch can be proven",[285,302],{"x":303,"y":304,"width":305,"height":306,"rx":307,"fill":308,"stroke":283,"strokeWidth":309},"22","62","180","120","11","#f4f1de","1.6",[293,311,315],{"x":312,"y":313,"textAnchor":297,"fontSize":314,"fontWeight":299,"fill":283},"112","88","12","1 · baseline",[293,317,320],{"x":318,"y":319,"fontSize":307,"fill":283},"36","116","--collect-only -q",[293,322,324],{"x":318,"y":323,"fontSize":307,"fill":283},"138","saved to a file",[78,326],{"x1":327,"y1":328,"x2":329,"y2":328,"stroke":283,"strokeWidth":309,"markerEnd":330},"206","122","222","url(#iml-a)",[285,332],{"x":333,"y":304,"width":305,"height":306,"rx":307,"fill":334,"stroke":335,"strokeWidth":336},"228","#f7f0da","#f2cc8f","2",[293,338,340],{"x":339,"y":313,"textAnchor":297,"fontSize":314,"fontWeight":299,"fill":283},"318","2 · switch",[293,342,344],{"x":343,"y":319,"fontSize":307,"fill":283},"242","addopts in",[293,346,347],{"x":343,"y":323,"fontSize":307,"fill":283},"pyproject.toml",[78,349],{"x1":350,"y1":328,"x2":351,"y2":328,"stroke":283,"strokeWidth":309,"markerEnd":330},"412","428",[285,353],{"x":354,"y":304,"width":305,"height":306,"rx":307,"fill":355,"stroke":356,"strokeWidth":336},"434","#fbe9e3","#e07a5f",[293,358,360],{"x":359,"y":313,"textAnchor":297,"fontSize":314,"fontWeight":299,"fill":283},"524","3 · fix imports",[293,362,364],{"x":363,"y":319,"fontSize":307,"fill":283},"448","bare-name helpers",[293,366,368],{"x":363,"y":323,"fontSize":307,"fill":367},"#8f3d22","move to a module",[78,370],{"x1":371,"y1":328,"x2":372,"y2":328,"stroke":283,"strokeWidth":309,"markerEnd":330},"618","634",[285,374],{"x":375,"y":304,"width":376,"height":306,"rx":307,"fill":377,"stroke":378,"strokeWidth":336},"640","158","#e6f0ea","#81b29a",[293,380,382],{"x":381,"y":313,"textAnchor":297,"fontSize":314,"fontWeight":299,"fill":283},"719","4 · verify",[293,384,386],{"x":385,"y":319,"fontSize":307,"fill":283},"654","delete markers",[293,388,390],{"x":385,"y":323,"fontSize":307,"fill":389},"#2a5f49","diff is empty",[285,392],{"x":303,"y":393,"width":394,"height":395,"rx":396,"fill":291,"stroke":397,"strokeWidth":398},"200","776","44","10","rgba(61,64,91,0.35)","1.5",[293,400,403],{"x":295,"y":401,"textAnchor":297,"fontSize":402,"fill":283},"227","11.5","Step three is the only real work, and it improves the suite whatever the import mode.",[405,406,407],"figcaption",{},"Without the baseline, \"the suite still passes\" could mean that some tests silently stopped being collected. The diff rules that out.",[25,409,411],{"id":410},"why-this-works","Why this works",[10,413,414,415,418,419,422,423,425,426,428],{},"Under ",[13,416,417],{},"prepend",", pytest makes test files importable by manipulating ",[13,420,421],{},"sys.path"," and deriving dotted names from the directory structure, which is why the marker files matter and why same-named files collide. Under ",[13,424,41],{},", pytest uses the import system's file-location machinery to load each file directly and assigns it a name derived from its path relative to the rootdir, which is unique by construction. Nothing is inserted into ",[13,427,421],{},", so nothing in the test tree shadows anything else, and the marker files have no role left to play.",[10,430,431,432,434,435,438],{},"The one behavioural difference users notice is the one that breaks cross-test imports. Because the tests directory is no longer on ",[13,433,421],{},", ",[13,436,437],{},"import test_helpers"," from a sibling test file has nowhere to resolve. That is not a limitation to work around; it is a sign the helper was in the wrong place, since a module that other modules import is not a test file.",[25,440,442],{"id":441},"edge-cases-and-failure-modes","Edge cases and failure modes",[30,444,445,455,465,473,485],{},[33,446,447,451,452,55],{},[448,449,450],"strong",{},"Helpers imported by bare name."," The one change that genuinely breaks. Move them into a test-support module, or expose them as fixtures from ",[13,453,454],{},"conftest.py",[33,456,457,460,461,464],{},[448,458,459],{},"Doctests in the test tree."," ",[13,462,463],{},"--doctest-modules"," imports modules the same way; doctests in test helpers need the same relocation.",[33,466,467,472],{},[448,468,469,470,55],{},"Plugins that assume ",[13,471,417],{}," A small number of older plugins compute module names themselves. Pin and upgrade them, or report the incompatibility; most have been updated.",[33,474,475,481,482,484],{},[448,476,477,480],{},[13,478,479],{},"rootdir"," changes."," Module names under ",[13,483,41],{}," are derived relative to rootdir, so moving the configuration file changes node ids. Keep the configuration at the repository root.",[33,486,487,490,491,493,494,496,497,500],{},[448,488,489],{},"Partial rollout."," Using ",[13,492,41],{}," locally and ",[13,495,417],{}," in CI, or the reverse, produces failures that appear in one place only. Put the flag in ",[13,498,499],{},"addopts",", not in a CI script.",[25,502,504],{"id":503},"where-the-shared-helpers-should-live","Where the shared helpers should live",[10,506,507],{},"The helper relocation is the part that needs a decision, and there are two good homes depending on what the helper is.",[10,509,510,513,514,517,518,521,522,524,525,528],{},[448,511,512],{},"Behaviour that tests call"," — builders, assertion helpers, small utilities — belongs in a regular Python package that is importable by name. The lightest version is a ",[13,515,516],{},"tests_support\u002F"," directory next to ",[13,519,520],{},"tests\u002F",", declared as a package in ",[13,523,347],{}," under an optional ",[13,526,527],{},"testing"," extra and installed with the editable install. Tests then import from it exactly as they import from the application.",[10,530,531,534,535,537],{},[448,532,533],{},"Setup that tests request"," — objects built per test, resources with teardown — belongs in a fixture in ",[13,536,454],{},", where pytest's dependency injection makes it available without any import at all. Converting a helper function that returned a configured client into a fixture is usually a two-line change and removes the import entirely.",[67,539,543],{"className":540,"code":542,"language":293,"meta":72},[541],"language-text","repository\u002F\n├── src\u002Fmyapp\u002F                 # the application\n├── tests_support\u002F             # importable helpers: builders, assertions\n│   ├── __init__.py\n│   └── orders.py\n└── tests\u002F                     # no __init__.py anywhere\n    ├── conftest.py            # fixtures: setup with teardown\n    └── billing\u002Ftest_invoice.py\n",[13,544,542],{"__ignoreMap":72},[10,546,547],{},"The split mirrors a distinction that is worth having regardless of import mode: helpers are code, fixtures are lifecycle. Mixing them in test files is what made the bare-name imports necessary in the first place.",[10,549,550,551,554,555,558],{},"In practice the relocation is smaller than it sounds. Running the suite once under the new mode lists every broken import as a collection error, each naming the file and the missing module. Most suites have a handful — a shared ",[13,552,553],{},"helpers.py",", a ",[13,556,557],{},"factories.py",", occasionally a module of constants — and moving them is a matter of creating the support package, moving the files, and updating the import lines, which a single search-and-replace usually handles. The collection errors themselves are the checklist, and the job is done when the list is empty. Nothing needs to be found by reading code or guessing at which modules import which.",[244,560,562,631],{"className":561},[247],[249,563,257,568,257,571,257,574,257,578,257,583,257,589,257,594,257,598,257,602,257,606,257,610,257,613,257,617,257,621,257,624,257,627],{"viewBox":564,"role":252,"ariaLabelledBy":565,"xmlns":256},"0 0 800 236",[566,567],"hlp-t","hlp-d",[259,569,570],{"id":566},"Where shared test code belongs under importlib mode",[263,572,573],{"id":567},"Two destinations for code that tests share. Callable behaviour such as builders and assertion helpers goes into an importable test-support package. Setup with a lifecycle such as clients and resources goes into fixtures in conftest.py, which tests request without importing. Test files contain only tests.",[285,575],{"x":287,"y":287,"width":576,"height":577,"rx":290,"fill":291},"800","236",[293,579,582],{"x":580,"y":296,"textAnchor":297,"fontSize":581,"fontWeight":299,"fill":283},"400","15.5","Helpers are code; fixtures are lifecycle",[285,584],{"x":585,"y":586,"width":587,"height":588,"rx":314,"fill":377,"stroke":378,"strokeWidth":336},"26","50","360","164",[293,590,593],{"x":327,"y":591,"textAnchor":297,"fontSize":592,"fontWeight":299,"fill":283},"76","12.5","tests_support package",[293,595,597],{"x":395,"y":596,"fontSize":307,"fill":283},"104","builders: an_order(), a_line()",[293,599,601],{"x":395,"y":600,"fontSize":307,"fill":283},"126","assertion helpers",[293,603,605],{"x":395,"y":604,"fontSize":307,"fill":283},"148","pure utilities",[293,607,609],{"x":395,"y":608,"fontSize":307,"fontWeight":299,"fill":389},"186","imported by name, like app code",[285,611],{"x":612,"y":586,"width":587,"height":588,"rx":314,"fill":334,"stroke":335,"strokeWidth":336},"414",[293,614,616],{"x":615,"y":591,"textAnchor":297,"fontSize":592,"fontWeight":299,"fill":283},"594","conftest.py fixtures",[293,618,620],{"x":619,"y":596,"fontSize":307,"fill":283},"432","clients, sessions, servers",[293,622,623],{"x":619,"y":600,"fontSize":307,"fill":283},"anything with teardown",[293,625,626],{"x":619,"y":604,"fontSize":307,"fill":283},"scoped resources",[293,628,630],{"x":619,"y":608,"fontSize":307,"fontWeight":299,"fill":629},"#8a5a00","requested, never imported",[405,632,633],{},"Test files end up containing only tests, which is the arrangement that makes a bare-name import unnecessary in the first place.",[25,635,637],{"id":636},"how-node-ids-change-and-why-that-matters","How node ids change, and why that matters",[10,639,640,641,643,644,648],{},"The switch is invisible to most tests but not to everything that reads test identifiers. Under ",[13,642,417],{},", a node id reflects the path from the rootdir as before, and that does not change. What can change is the ",[645,646,647],"em",{},"module name"," pytest assigns internally, which appears in a few places people rely on without realising.",[10,650,651,652,655,656,658,659,661],{},"Tooling that records test history by module name — some flake trackers, some coverage-per-test plugins, a handful of custom report hooks — may see every test as new after the switch, because ",[13,653,654],{},"tests.billing.test_invoice"," under ",[13,657,417],{}," with markers becomes a path-derived name under ",[13,660,41],{},". The node ids that pytest prints and that JUnit XML records are path-based and stay stable, so dashboards keyed on them are unaffected. It is the rarer, module-name-keyed tooling that needs a one-time acknowledgement that history restarts.",[10,663,664,665,668],{},"Pickled objects and cached artefacts are the other place module names surface. A test cache that stored objects referencing test-module classes by qualified name will fail to unpickle after the switch. Clearing ",[13,666,667],{},".pytest_cache"," and any custom caches once, as part of the change, avoids a confusing failure on the first run afterwards.",[10,670,671],{},"Neither issue is a reason to avoid the switch, but both are worth mentioning in the change description, so that whoever notices a flake tracker reporting a hundred \"new\" tests the next morning knows why and does not spend an hour on it.",[244,673,675,733],{"className":674},[247],[249,676,257,680,257,683,257,686,257,688,257,691,257,693,257,696,257,699,257,702,257,705,257,709,257,713,257,715,257,718,257,721,257,724,257,727,257,730],{"viewBox":564,"role":252,"ariaLabelledBy":677,"xmlns":256},[678,679],"nid-t","nid-d",[259,681,682],{"id":678},"What stays stable and what may reset after the switch",[263,684,685],{"id":679},"Two groups. Path-based node ids, JUnit test names, test selection with k and pytest cache keys stay stable across the switch. Tooling keyed on internal module names, pickled artefacts referencing test classes, and some custom report hooks may see history reset and should be noted in the change.",[285,687],{"x":287,"y":287,"width":576,"height":577,"rx":290,"fill":291},[293,689,690],{"x":580,"y":296,"textAnchor":297,"fontSize":581,"fontWeight":299,"fill":283},"Mostly invisible, with two exceptions worth announcing",[285,692],{"x":585,"y":586,"width":587,"height":588,"rx":314,"fill":377,"stroke":378,"strokeWidth":336},[293,694,695],{"x":327,"y":591,"textAnchor":297,"fontSize":592,"fontWeight":299,"fill":283},"unchanged",[293,697,698],{"x":395,"y":596,"fontSize":307,"fill":283},"• node ids printed by pytest",[293,700,701],{"x":395,"y":600,"fontSize":307,"fill":283},"• JUnit XML test names",[293,703,704],{"x":395,"y":604,"fontSize":307,"fill":283},"• -k selection and markers",[293,706,708],{"x":395,"y":707,"fontSize":307,"fill":283},"170","• conftest discovery",[293,710,712],{"x":395,"y":711,"fontSize":307,"fill":389},"198","dashboards keep their history",[285,714],{"x":612,"y":586,"width":587,"height":588,"rx":314,"fill":334,"stroke":335,"strokeWidth":336},[293,716,717],{"x":615,"y":591,"textAnchor":297,"fontSize":592,"fontWeight":299,"fill":283},"may reset",[293,719,720],{"x":619,"y":596,"fontSize":307,"fill":283},"• tools keyed on module names",[293,722,723],{"x":619,"y":600,"fontSize":307,"fill":283},"• pickles of test-module classes",[293,725,726],{"x":619,"y":604,"fontSize":307,"fill":283},"• some custom report hooks",[293,728,729],{"x":619,"y":707,"fontSize":307,"fill":283},"• stale .pytest_cache entries",[293,731,732],{"x":619,"y":711,"fontSize":307,"fill":629},"clear caches; note it in the change",[405,734,735],{},"The left column covers what nearly every team relies on. The right column is rare, but when it applies it produces a confusing morning unless someone was told.",[10,737,738,739,742],{},"A clean way to handle all of this is to make the switch its own pull request, separate from any test changes, with the before-and-after collection diff attached. Reviewers can then see at a glance that nothing about ",[645,740,741],{},"which"," tests run has changed, and the only behavioural differences are the cross-test imports that the same change relocated. That separation also makes the switch trivially revertible if an incompatible plugin turns up late, which in practice is the main risk and a small one.",[25,744,746],{"id":745},"frequently-asked-questions","Frequently Asked Questions",[10,748,749,752,753,755,756,758,759,761],{},[448,750,751],{},"Where do shared test helpers go under importlib mode?","\nInto a module that is importable by name through normal means — a small installed test-support package, or a helpers module exposed through fixtures in ",[13,754,454],{},". Under ",[13,757,41],{}," the tests directory is not on ",[13,760,421],{},", so one test file importing another by bare name no longer resolves.",[10,763,764,767,768,770],{},[448,765,766],{},"Does importlib mode change how conftest.py files are found?","\nNo. ",[13,769,454],{}," discovery follows the directory hierarchy exactly as before, and conftest files are imported by path under unique names in every mode. Fixtures defined in them remain available to tests beneath them.",[10,772,773,776,777,779,780,782],{},[448,774,775],{},"Is importlib mode slower?","\nNot measurably. It skips the ",[13,778,421],{}," manipulation ",[13,781,417],{}," mode performs and imports each file directly. Collection time is dominated by what the modules do at import, not by how pytest locates them.",[25,784,786],{"id":785},"related","Related",[30,788,789,795,802,809],{},[33,790,791,794],{},[51,792,793],{"href":53},"Test Layout & Import Modes"," — the layout decisions that make this switch straightforward.",[33,796,797,801],{},[51,798,800],{"href":799},"\u002Fadvanced-pytest-architecture-configuration\u002Ftest-layout-and-import-modes\u002Ffixing-import-file-mismatch-errors-in-pytest\u002F","Fixing import file mismatch Errors in pytest"," — the error this mode eliminates.",[33,803,804,808],{},[51,805,807],{"href":806},"\u002Fadvanced-pytest-architecture-configuration\u002Fmanaging-conftest-hierarchies\u002Fsharing-fixtures-without-conftest-py\u002F","Sharing Fixtures Without conftest.py"," — packaging fixtures for reuse across repositories.",[33,810,811,815],{},[51,812,814],{"href":813},"\u002Fadvanced-pytest-architecture-configuration\u002Fpytest-configuration-best-practices\u002Fpyproject-toml-vs-pytest-ini\u002F","pyproject.toml vs pytest.ini"," — keeping the setting where every runner reads it.",[10,817,818,819],{},"← Back to ",[51,820,793],{"href":53},[822,823,824],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":72,"searchDepth":86,"depth":86,"links":826},[827,828,829,830,831,832,833,834],{"id":27,"depth":86,"text":28},{"id":61,"depth":86,"text":62},{"id":410,"depth":86,"text":411},{"id":441,"depth":86,"text":442},{"id":503,"depth":86,"text":504},{"id":636,"depth":86,"text":637},{"id":745,"depth":86,"text":746},{"id":785,"depth":86,"text":786},"Adopt pytest's importlib import mode across a suite: removing __init__.py, relocating shared helpers, conftest behaviour, plugin compatibility, and verifying the switch.","md",{"slug":838,"type":839,"breadcrumb":840,"datePublished":841,"dateModified":841,"faq":842,"howto":849},"using-importmode-importlib-without-init-files","article","importlib mode","2026-09-18",[843,845,847],{"q":751,"a":844},"Into a module that is importable by name through normal means — a small installed test-support package, or a helpers module exposed through fixtures in conftest.py. Under importlib the tests directory is not on sys.path, so one test file importing another by bare name no longer resolves.",{"q":766,"a":846},"No. conftest.py discovery follows the directory hierarchy exactly as before, and conftest files are imported by path under unique names in every mode. Fixtures defined in them remain available to tests beneath them.",{"q":775,"a":848},"Not measurably. It skips the sys.path manipulation prepend mode performs and imports each file directly. Collection time is dominated by what the modules do at import, not by how pytest locates them.",{"name":850,"description":851,"steps":852},"How to switch a suite to importlib import mode","Set the mode in configuration, move cross-test imports into proper modules, delete the marker files, and verify collection is unchanged.",[853,856,859,862,865],{"name":854,"text":855},"Record the current collection","Save the output of pytest --collect-only -q so the switch can be verified against it.",{"name":857,"text":858},"Set the mode in configuration","Add --import-mode=importlib to addopts so every invocation uses it.",{"name":860,"text":861},"Fix cross-test imports","Move helpers that tests import by bare name into a test-support module importable by package name.",{"name":863,"text":864},"Delete __init__.py from the test tree","Remove the marker files once the suite is green under the new mode.",{"name":866,"text":867},"Compare collection","Re-run collect-only and confirm the same tests are found.","\u002Fadvanced-pytest-architecture-configuration\u002Ftest-layout-and-import-modes\u002Fusing-importmode-importlib-without-init-files",{"title":5,"description":835},"advanced-pytest-architecture-configuration\u002Ftest-layout-and-import-modes\u002Fusing-importmode-importlib-without-init-files\u002Findex","NF6QjI5OHku7uttM4If2IEtUSozuR-nvlbQpqNnBlGs",1789718768821]