[{"data":1,"prerenderedAt":1314},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-api-architecture\u002Fqgis-python-version-compatibility-guide":3},{"id":4,"title":5,"body":6,"description":1303,"extension":1304,"meta":1305,"navigation":383,"path":1310,"seo":1311,"stem":1312,"__hash__":1313},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-api-architecture\u002Fqgis-python-version-compatibility-guide\u002Findex.md","QGIS Python Version Compatibility Guide",{"type":7,"value":8,"toc":1288},"minimark",[9,13,39,56,247,252,255,306,310,319,338,342,349,520,537,541,549,645,656,660,663,776,780,783,865,869,872,994,997,1014,1018,1027,1117,1122,1159,1163,1181,1185,1197,1214,1229,1242,1256,1260,1284],[10,11,5],"h1",{"id":12},"qgis-python-version-compatibility-guide",[14,15,16,17,21,22,26,27,30,31,34,35,38],"p",{},"QGIS ships with a strictly version-locked Python interpreter. The bundled Python minor version changes with each major release, and ",[18,19,20],"strong",{},"you cannot safely swap it"," for a system Python, a conda environment, or a standalone installer without breaking the compiled ",[23,24,25],"code",{},"qgis.core"," and ",[23,28,29],{},"qgis.gui"," bindings. If you have ever seen ",[23,32,33],{},"ImportError: DLL load failed"," or ",[23,36,37],{},"ModuleNotFoundError: qgis"," right after installing a package, you have hit this wall. This guide explains why the lock exists, how to read the compatibility matrix, and the exact steps to install dependencies and recover from an ABI mismatch — always matching external packages to the precise minor version QGIS provides.",[14,40,41,44,45,48,49,48,52,55],{},[18,42,43],{},"Rule of thumb:"," Never assume a Python wheel built for your operating system's system Python will work inside QGIS. Verify that the ABI tag (",[23,46,47],{},"cp39",", ",[23,50,51],{},"cp310",[23,53,54],{},"cp312",") matches the QGIS runtime before installing any C-extension package.",[57,58,63,67,71,99,106,114,120,127,134,139,142,145,148,151,155,158,164,171,174,178,184,189,196,201,206,212,216,221,224,228,232,236,238,243],"svg",{"viewBox":59,"role":60,"ariaLabel":61,"xmlns":62},"0 0 760 360","img","How a QGIS release pins the bundled Python version, the ABI tag, and which wheels load","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[64,65,66],"title",{},"The QGIS version-lock chain",[68,69,70],"desc",{},"A QGIS release pins one bundled Python minor version, which pins one C-extension ABI tag such as cp312. A wheel tagged cp312 matches the runtime and imports cleanly, while a cp311 wheel built for a system Python breaks the chain with ImportError: DLL load failed.",[72,73,74,87,93],"defs",{},[75,76,82],"marker",{"id":77,"viewBox":78,"refX":79,"refY":80,"markerWidth":79,"markerHeight":79,"orient":81},"vcTeal","0 0 10 10","8","5","auto",[83,84],"path",{"d":85,"fill":86},"M0 0 L10 5 L0 10 z","#0f766e",[75,88,90],{"id":89,"viewBox":78,"refX":79,"refY":80,"markerWidth":79,"markerHeight":79,"orient":81},"vcGreen",[83,91],{"d":85,"fill":92},"#15803d",[75,94,96],{"id":95,"viewBox":78,"refX":79,"refY":80,"markerWidth":79,"markerHeight":79,"orient":81},"vcRed",[83,97],{"d":85,"fill":98},"#b91c1c",[100,101],"rect",{"x":102,"y":102,"width":103,"height":104,"fill":105},"0","760","360","#f6f3ea",[107,108,66],"text",{"x":109,"y":110,"style":111,"fill":112,"textAnchor":113},"380","28","text-anchor:middle;font-family:sans-serif;font-size:16px;font-weight:bold","#17211d","middle",[107,115,119],{"x":109,"y":116,"style":117,"fill":118,"textAnchor":113},"46","text-anchor:middle;font-family:sans-serif;font-size:12px","#2f3b35","One release pins one Python minor, which pins one ABI tag, which decides which wheels load",[100,121],{"x":122,"y":123,"width":124,"height":125,"rx":79,"fill":126},"24","64","214","56","#26322d",[107,128,133],{"x":129,"y":130,"style":131,"fill":132,"textAnchor":113},"131","88","text-anchor:middle;font-family:sans-serif;font-size:14px;font-weight:bold","#d9f99d","QGIS release",[107,135,138],{"x":129,"y":136,"style":117,"fill":137,"textAnchor":113},"107","#e8e4d8","e.g. 3.34 \u002F 3.44 LTR",[100,140],{"x":141,"y":123,"width":124,"height":125,"rx":79,"fill":126},"273",[107,143,144],{"x":109,"y":130,"style":131,"fill":132,"textAnchor":113},"Bundled Python",[107,146,147],{"x":109,"y":136,"style":117,"fill":137,"textAnchor":113},"3.12.x, version-locked",[100,149],{"x":150,"y":123,"width":124,"height":125,"rx":79,"fill":126},"522",[107,152,154],{"x":153,"y":130,"style":131,"fill":132,"textAnchor":113},"629","C-extension ABI",[107,156,54],{"x":153,"y":136,"style":157,"fill":137,"textAnchor":113},"text-anchor:middle;font-family:monospace;font-size:13px",[107,159,163],{"x":160,"y":161,"style":162,"fill":86,"textAnchor":113},"255","84","text-anchor:middle;font-family:sans-serif;font-size:11px","pins",[165,166],"line",{"x1":167,"y1":168,"x2":169,"y2":168,"stroke":86,"style":170},"238","92","271","stroke-width:3;marker-end:url(#vcTeal)",[107,172,163],{"x":173,"y":161,"style":162,"fill":86,"textAnchor":113},"504",[165,175],{"x1":176,"y1":168,"x2":177,"y2":168,"stroke":86,"style":170},"487","520",[165,179],{"x1":122,"y1":180,"x2":181,"y2":180,"stroke":182,"style":183},"140","736","#c9c2ac","stroke-width:1",[107,185,188],{"x":109,"y":186,"style":187,"fill":112,"textAnchor":113},"160","text-anchor:middle;font-family:sans-serif;font-size:13px;font-weight:bold","Which wheel loads inside this runtime?",[100,190],{"x":122,"y":191,"width":192,"height":193,"rx":79,"fill":194,"stroke":92,"style":195},"172","330","50","#fffdf7","stroke-width:2",[107,197,200],{"x":198,"y":199,"style":187,"fill":112,"textAnchor":113},"189","192","Wheel tagged cp312",[107,202,205],{"x":198,"y":203,"style":204,"fill":118,"textAnchor":113},"210","text-anchor:middle;font-family:monospace;font-size:12px","matches Python 3.12",[165,207],{"x1":208,"y1":209,"x2":210,"y2":209,"stroke":92,"style":211},"354","197","402","stroke-width:3;marker-end:url(#vcGreen)",[100,213],{"x":214,"y":191,"width":215,"height":193,"rx":79,"fill":194,"stroke":92,"style":195},"404","332",[107,217,220],{"x":218,"y":219,"style":187,"fill":92,"textAnchor":113},"570","202","✓ Loads — qgis.core imports",[100,222],{"x":122,"y":223,"width":192,"height":193,"rx":79,"fill":194,"stroke":98,"style":195},"236",[107,225,227],{"x":198,"y":226,"style":187,"fill":112,"textAnchor":113},"256","Wheel tagged cp311",[107,229,231],{"x":198,"y":230,"style":204,"fill":118,"textAnchor":113},"274","built for system Python 3.11",[165,233],{"x1":208,"y1":234,"x2":210,"y2":234,"stroke":98,"style":235},"261","stroke-width:3;marker-end:url(#vcRed)",[100,237],{"x":214,"y":223,"width":215,"height":193,"rx":79,"fill":194,"stroke":98,"style":195},[107,239,242],{"x":218,"y":240,"style":241,"fill":98,"textAnchor":113},"266","text-anchor:middle;font-family:monospace;font-size:12px;font-weight:bold","✗ ImportError: DLL load failed",[107,244,246],{"x":109,"y":245,"style":162,"fill":118,"textAnchor":113},"318","Pure-Python wheels carry no ABI tag and load across any minor version — only C-extensions are locked.",[248,249,251],"h2",{"id":250},"prerequisites","Prerequisites",[14,253,254],{},"Before working through the recipes below, make sure you have:",[256,257,258,271,285,300],"ul",{},[259,260,261,264,265,270],"li",{},[18,262,263],{},"A working QGIS install"," — this guide covers the 3.16 through 4.0 range. If PyQGIS is not yet importable on your machine, start with ",[266,267,269],"a",{"href":268},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Fhow-to-install-qgis-python-bindings-on-windows\u002F","How to Install QGIS Python Bindings on Windows",".",[259,272,273,276,277,280,281,284],{},[18,274,275],{},"Access to the QGIS Python Console"," (",[23,278,279],{},"Plugins > Python Console","), or the bundled shell launcher (",[23,282,283],{},"python-qgis-ltr.bat"," on Windows).",[259,286,287,290,291,295,296,299],{},[18,288,289],{},"A basic grasp of the binding layer"," — the ",[266,292,294],{"href":293},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-api-architecture\u002F","QGIS API Architecture"," that generates ",[23,297,298],{},"qgis._core"," from C++ via SIP. That article explains the compiled layer this whole compatibility story rests on.",[259,301,302,305],{},[18,303,304],{},"Knowing where your QGIS user profile lives",", because ABI recovery involves clearing manually copied packages from it.",[248,307,309],{"id":308},"why-version-locking-matters","Why Version Locking Matters",[14,311,312,313,315,316,318],{},"The QGIS API relies on SIP-generated Python bindings compiled against a specific Python ABI and a specific Qt\u002FPyQt build. The Application Binary Interface (ABI) is the low-level contract — struct layouts, symbol names, reference-counting behaviour — between compiled ",[23,314,298],{}," objects and the CPython interpreter that loads them. When Python's minor version changes (say 3.9 to 3.12), that contract changes too, and the pre-compiled C-extensions inside ",[23,317,298],{}," become binary-incompatible with the interpreter trying to import them.",[14,320,321,322,324,325,328,329,333,334,337],{},"That is why mixing an external Python installation with QGIS produces errors like ",[23,323,33],{}," (Windows) or ",[23,326,327],{},"ImportError: cannot import name 'QgsApplication'"," (any OS). The Python ",[330,331,332],"em",{},"source"," looks identical; the ",[330,335,336],{},"binary"," does not line up. Treat the bundled interpreter as an immovable part of the runtime rather than a component you can upgrade independently.",[248,339,341],{"id":340},"verify-your-active-environment","Verify Your Active Environment",[14,343,344,345,348],{},"The first concrete step is always to confirm which interpreter is actually running. Paste this into the ",[18,346,347],{},"QGIS Python Console"," to print the alignment between the interpreter, the QGIS release, and the on-disk binding module:",[350,351,356],"pre",{"className":352,"code":353,"language":354,"meta":355,"style":355},"language-python shiki shiki-themes github-dark","import sys\nimport qgis.core\n\nprint(f\"QGIS Python: {sys.version_info.major}.{sys.version_info.minor}\")\nprint(f\"QGIS version: {qgis.core.Qgis.QGIS_VERSION}\")\nprint(f\"QGIS Core Path: {qgis.core.__file__}\")\n\nif sys.version_info \u003C (3, 9):\n    raise RuntimeError(\"Python \u003C 3.9 is incompatible with QGIS 3.16+\")\n","python","",[23,357,358,370,378,385,426,450,474,479,504],{"__ignoreMap":355},[359,360,362,366],"span",{"class":165,"line":361},1,[359,363,365],{"class":364},"snl16","import",[359,367,369],{"class":368},"s95oV"," sys\n",[359,371,373,375],{"class":165,"line":372},2,[359,374,365],{"class":364},[359,376,377],{"class":368}," qgis.core\n",[359,379,381],{"class":165,"line":380},3,[359,382,384],{"emptyLinePlaceholder":383},true,"\n",[359,386,388,392,395,398,402,405,408,411,413,415,418,420,423],{"class":165,"line":387},4,[359,389,391],{"class":390},"sDLfK","print",[359,393,394],{"class":368},"(",[359,396,397],{"class":364},"f",[359,399,401],{"class":400},"sU2Wk","\"QGIS Python: ",[359,403,404],{"class":390},"{",[359,406,407],{"class":368},"sys.version_info.major",[359,409,410],{"class":390},"}",[359,412,270],{"class":400},[359,414,404],{"class":390},[359,416,417],{"class":368},"sys.version_info.minor",[359,419,410],{"class":390},[359,421,422],{"class":400},"\"",[359,424,425],{"class":368},")\n",[359,427,429,431,433,435,438,440,443,446,448],{"class":165,"line":428},5,[359,430,391],{"class":390},[359,432,394],{"class":368},[359,434,397],{"class":364},[359,436,437],{"class":400},"\"QGIS version: ",[359,439,404],{"class":390},[359,441,442],{"class":368},"qgis.core.Qgis.",[359,444,445],{"class":390},"QGIS_VERSION}",[359,447,422],{"class":400},[359,449,425],{"class":368},[359,451,453,455,457,459,462,464,467,470,472],{"class":165,"line":452},6,[359,454,391],{"class":390},[359,456,394],{"class":368},[359,458,397],{"class":364},[359,460,461],{"class":400},"\"QGIS Core Path: ",[359,463,404],{"class":390},[359,465,466],{"class":368},"qgis.core.",[359,468,469],{"class":390},"__file__}",[359,471,422],{"class":400},[359,473,425],{"class":368},[359,475,477],{"class":165,"line":476},7,[359,478,384],{"emptyLinePlaceholder":383},[359,480,482,485,488,491,493,496,498,501],{"class":165,"line":481},8,[359,483,484],{"class":364},"if",[359,486,487],{"class":368}," sys.version_info ",[359,489,490],{"class":364},"\u003C",[359,492,276],{"class":368},[359,494,495],{"class":390},"3",[359,497,48],{"class":368},[359,499,500],{"class":390},"9",[359,502,503],{"class":368},"):\n",[359,505,507,510,513,515,518],{"class":165,"line":506},9,[359,508,509],{"class":364},"    raise",[359,511,512],{"class":390}," RuntimeError",[359,514,394],{"class":368},[359,516,517],{"class":400},"\"Python \u003C 3.9 is incompatible with QGIS 3.16+\"",[359,519,425],{"class":368},[14,521,522,523,48,526,529,530,533,534,270],{},"The minor version reported here — ",[23,524,525],{},"3.9",[23,527,528],{},"3.10",", or ",[23,531,532],{},"3.12"," — is the number every C-extension you install must target. Note it down before touching ",[23,535,536],{},"pip",[248,538,540],{"id":539},"safe-dependency-installation","Safe Dependency Installation",[14,542,543,544,548],{},"Once you know the runtime's Python version, add third-party libraries without breaking bindings by treating QGIS as a self-contained runtime, following the broader ",[266,545,547],{"href":546},"\u002Fpyqgis-fundamentals-environment-setup\u002F","PyQGIS Fundamentals & Environment Setup"," workflow.",[550,551,552,609,636],"ol",{},[259,553,554,559,560,562,563,566,567,570,571],{},[18,555,556,557,270],{},"Use the bundled ",[23,558,536],{}," QGIS 3.22+ includes ",[23,561,536],{}," in the console. Always target ",[23,564,565],{},"sys.executable"," so the package lands in the QGIS interpreter's ",[23,568,569],{},"site-packages",", not a system one:",[350,572,574],{"className":352,"code":573,"language":354,"meta":355,"style":355},"import subprocess, sys\nsubprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"requests\"])\n",[23,575,576,583],{"__ignoreMap":355},[359,577,578,580],{"class":165,"line":361},[359,579,365],{"class":364},[359,581,582],{"class":368}," subprocess, sys\n",[359,584,585,588,591,593,596,598,601,603,606],{"class":165,"line":372},[359,586,587],{"class":368},"subprocess.check_call([sys.executable, ",[359,589,590],{"class":400},"\"-m\"",[359,592,48],{"class":368},[359,594,595],{"class":400},"\"pip\"",[359,597,48],{"class":368},[359,599,600],{"class":400},"\"install\"",[359,602,48],{"class":368},[359,604,605],{"class":400},"\"requests\"",[359,607,608],{"class":368},"])\n",[259,610,611,614,615,34,618,620,621,624,625,48,628,631,632,635],{},[18,612,613],{},"Windows:"," Run external scripts via ",[23,616,617],{},"python-qgis.bat",[23,619,283],{}," (in the QGIS ",[23,622,623],{},"bin"," folder). This launcher sets ",[23,626,627],{},"PYTHONHOME",[23,629,630],{},"PYTHONPATH",", and ",[23,633,634],{},"QT_PLUGIN_PATH"," automatically, so the correct interpreter and Qt plugins are resolved for you.",[259,637,638,641,642,644],{},[18,639,640],{},"Linux\u002FmacOS:"," Invoke scripts using the QGIS-bundled Python executable directly. Never manually export ",[23,643,630],{}," to system Python directories when targeting the QGIS runtime.",[14,646,647,648,651,652,270],{},"If you need a fully external process — for heavy data crunching that never imports ",[23,649,650],{},"qgis"," — drive it through a separate interpreter as described in ",[266,653,655],{"href":654},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Frunning-python-scripts-outside-qgis-desktop\u002F","Running Python Scripts Outside QGIS Desktop",[248,657,659],{"id":658},"the-release-train-and-what-it-locks","The release train and what it locks",[14,661,662],{},"Each QGIS release ships with one Python version and one Qt version, and those choices propagate to everything you can install alongside them.",[14,664,665],{},[57,666,669,672,675,678,683,689,723,747,766,771],{"viewBox":667,"role":60,"ariaLabel":668,"xmlns":62},"0 0 760 240","A timeline of QGIS long-term releases against the Python and Qt versions each ships with, showing what is locked for the lifetime of that release",[64,670,671],{},"What each QGIS release locks in",[68,673,674],{},"Three long-term releases are shown in sequence. QGIS 3.28 ships Python 3.9 and Qt 5. QGIS 3.34 ships Python 3.12 and Qt 5. QGIS 3.40 ships Python 3.12 with a Qt 6 build appearing alongside. Each release keeps those versions for its whole lifetime, so any package installed beside it must be compatible with them.",[100,676],{"x":102,"y":102,"width":103,"height":677,"fill":105},"240",[107,679,682],{"x":109,"y":680,"style":681,"fill":112,"textAnchor":113},"26","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","You do not choose the Python version — the release does",[165,684],{"x1":685,"y1":686,"x2":687,"y2":686,"stroke":688,"style":195},"60","200","720","#59645f",[690,691,692,699,705,710,714,719],"g",{},[100,693],{"x":694,"y":695,"width":696,"height":697,"rx":698,"fill":194,"stroke":688,"style":195},"80","70","180","110","10",[107,700,704],{"x":701,"y":702,"style":703,"fill":112,"textAnchor":113},"170","96","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","QGIS 3.28 LTR",[107,706,709],{"x":701,"y":707,"style":708,"fill":118,"textAnchor":113},"122","text-anchor:middle;font-size:11px;font-family:sans-serif","Python 3.9",[107,711,713],{"x":701,"y":712,"style":708,"fill":118,"textAnchor":113},"144","Qt 5",[107,715,718],{"x":701,"y":716,"style":717,"fill":688,"textAnchor":113},"168","text-anchor:middle;font-size:10.5px;font-family:sans-serif","older syntax only",[720,721],"circle",{"cx":701,"cy":686,"r":722,"fill":688},"7",[690,724,725,730,734,738,741,745],{},[100,726],{"x":727,"y":685,"width":696,"height":728,"rx":698,"fill":194,"stroke":86,"style":729},"290","120","stroke-width:2.5",[107,731,733],{"x":109,"y":732,"style":703,"fill":86,"textAnchor":113},"86","QGIS 3.34 LTR",[107,735,737],{"x":109,"y":736,"style":708,"fill":118,"textAnchor":113},"112","Python 3.12",[107,739,713],{"x":109,"y":740,"style":708,"fill":118,"textAnchor":113},"134",[107,742,744],{"x":109,"y":186,"style":743,"fill":86,"textAnchor":113},"text-anchor:middle;font-size:10.5px;font-weight:bold;font-family:sans-serif","the baseline here",[720,746],{"cx":109,"cy":686,"r":79,"fill":86},[690,748,749,752,756,758,761,764],{},[100,750],{"x":751,"y":695,"width":696,"height":697,"rx":698,"fill":194,"stroke":92,"style":195},"500",[107,753,755],{"x":754,"y":702,"style":703,"fill":92,"textAnchor":113},"590","QGIS 3.40 LTR",[107,757,737],{"x":754,"y":707,"style":708,"fill":118,"textAnchor":113},[107,759,760],{"x":754,"y":712,"style":708,"fill":118,"textAnchor":113},"Qt 5 · Qt 6 builds",[107,762,763],{"x":754,"y":716,"style":717,"fill":688,"textAnchor":113},"two Qt worlds",[720,765],{"cx":754,"cy":686,"r":722,"fill":92},[107,767,770],{"x":685,"y":768,"style":769,"fill":688},"226","font-size:11px;font-family:sans-serif","older",[107,772,775],{"x":687,"y":768,"style":773,"fill":688,"textAnchor":774},"text-anchor:end;font-size:11px;font-family:sans-serif","end","newer",[248,777,779],{"id":778},"which-python-you-are-actually-running","Which Python you are actually running",[14,781,782],{},"Three interpreters commonly exist on one machine, and a script that works in the Console and fails in a terminal is nearly always running under a different one.",[14,784,785],{},[57,786,789,792,795,797,800,805,810,815,819,823,827,831,834,837,840,843,846,850,854,857,859,862],{"viewBox":787,"role":60,"ariaLabel":788,"xmlns":62},"0 0 760 236","Three interpreters on one machine — the QGIS bundled one, the system one and a virtual environment — with which can import the QGIS bindings",[64,790,791],{},"Three interpreters, one set of bindings",[68,793,794],{},"The QGIS bundled interpreter can import the bindings directly. The system Python cannot unless its version matches and the path is configured. A virtual environment created from the bundled interpreter with system site packages enabled can, while one created from the system Python usually cannot.",[100,796],{"x":102,"y":102,"width":103,"height":223,"fill":105},[107,798,799],{"x":109,"y":680,"style":681,"fill":112,"textAnchor":113},"Check with: python3 -c \"import sys; print(sys.executable)\"",[100,801],{"x":802,"y":803,"width":223,"height":804,"rx":698,"fill":194,"stroke":92,"style":729},"16","52","164",[107,806,809],{"x":740,"y":807,"style":808,"fill":92,"textAnchor":113},"78","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","QGIS bundled",[107,811,814],{"x":740,"y":812,"style":813,"fill":118,"textAnchor":113},"104","text-anchor:middle;font-size:10.5px;font-family:monospace","\u002Fusr\u002Fbin\u002Fpython3",[107,816,818],{"x":740,"y":180,"style":817,"fill":92,"textAnchor":113},"text-anchor:middle;font-size:22px;font-weight:bold;font-family:sans-serif","✓",[107,820,822],{"x":740,"y":821,"style":708,"fill":118,"textAnchor":113},"176","imports the bindings",[107,824,826],{"x":740,"y":825,"style":717,"fill":688,"textAnchor":113},"196","use this for scripts",[100,828],{"x":829,"y":803,"width":223,"height":804,"rx":698,"fill":194,"stroke":830,"style":729},"262","#b45309",[107,832,833],{"x":109,"y":807,"style":808,"fill":830,"textAnchor":113},"system Python",[107,835,836],{"x":109,"y":812,"style":813,"fill":118,"textAnchor":113},"\u002Fusr\u002Flocal\u002Fbin\u002Fpython3",[107,838,839],{"x":109,"y":180,"style":817,"fill":830,"textAnchor":113},"?",[107,841,842],{"x":109,"y":821,"style":708,"fill":118,"textAnchor":113},"only if the version matches",[107,844,845],{"x":109,"y":825,"style":717,"fill":688,"textAnchor":113},"and PYTHONPATH is set",[100,847],{"x":848,"y":803,"width":223,"height":804,"rx":698,"fill":194,"stroke":849,"style":729},"508","#2563eb",[107,851,853],{"x":852,"y":807,"style":808,"fill":849,"textAnchor":113},"626","a virtualenv",[107,855,856],{"x":852,"y":812,"style":813,"fill":118,"textAnchor":113},".venv\u002Fbin\u002Fpython",[107,858,818],{"x":852,"y":180,"style":817,"fill":849,"textAnchor":113},[107,860,861],{"x":852,"y":821,"style":708,"fill":118,"textAnchor":113},"if built from the bundled one",[107,863,864],{"x":852,"y":825,"style":717,"fill":688,"textAnchor":113},"with system site packages",[248,866,868],{"id":867},"qgis-version-compatibility-notes","QGIS-Version Compatibility Notes",[14,870,871],{},"Use this matrix to map any QGIS release to its bundled Python and the ABI tag your wheels must carry. Because the interpreter is shared across a release range, the table groups releases by the Python they ship.",[873,874,875,893],"table",{},[876,877,878],"thead",{},[879,880,881,885,887,890],"tr",{},[882,883,884],"th",{},"QGIS Release Range",[882,886,144],{},[882,888,889],{},"ABI Tag",[882,891,892],{},"Compatibility Notes",[894,895,896,916,930,945,960,974],"tbody",{},[879,897,898,902,905,909],{},[899,900,901],"td",{},"3.16–3.22",[899,903,904],{},"3.9.x",[899,906,907],{},[23,908,47],{},[899,910,911,912,915],{},"Legacy LTR. Requires ",[23,913,914],{},"pip install --ignore-installed"," for reinstalls.",[879,917,918,921,923,927],{},[899,919,920],{},"3.24–3.28",[899,922,904],{},[899,924,925],{},[23,926,47],{},[899,928,929],{},"Stable series. C-extensions must target the 3.9 ABI.",[879,931,932,935,938,942],{},[899,933,934],{},"3.30–3.32",[899,936,937],{},"3.10.x",[899,939,940],{},[23,941,51],{},[899,943,944],{},"Transition builds. macOS Homebrew may ship mismatched wheels.",[879,946,947,950,953,957],{},[899,948,949],{},"3.34–3.36",[899,951,952],{},"3.12.x",[899,954,955],{},[23,956,54],{},[899,958,959],{},"Requires updated C-extensions. PyQt5 remains the default.",[879,961,962,965,967,971],{},[899,963,964],{},"3.38–3.44",[899,966,952],{},[899,968,969],{},[23,970,54],{},[899,972,973],{},"Includes the 3.40 and 3.44 LTRs — the final 3.x (Qt5\u002FPyQt5) series.",[879,975,976,979,982,987],{},[899,977,978],{},"4.0+",[899,980,981],{},"3.12.x+",[899,983,984,986],{},[23,985,54],{},"+",[899,988,989,990,993],{},"Qt6\u002FPyQt6 generation. Confirm with ",[23,991,992],{},"sys.version"," inside the QGIS Console; release notes may revise this.",[14,995,996],{},"Two rules follow directly from the table:",[256,998,999,1008],{},[259,1000,1001,1004,1005,1007],{},[18,1002,1003],{},"Pin your code to an LTR."," The 3.34 and 3.44 LTRs both bundle Python 3.12, so examples pinned to ",[23,1006,54],{}," remain valid across that whole span. Pure-Python packages with no compiled code are generally safe across minor versions; only C-extensions care about the ABI tag.",[259,1009,1010,1013],{},[18,1011,1012],{},"A minor-version bump is a breaking change."," Moving from a 3.9 release to a 3.12 release invalidates every previously installed C-extension. Plan the recompile before you upgrade, not after.",[248,1015,1017],{"id":1016},"troubleshooting-recover-from-abi-mismatches","Troubleshooting: Recover from ABI Mismatches",[14,1019,1020,1021,48,1023,1026],{},"If you encounter ",[23,1022,327],{},[23,1024,1025],{},"DLL load failed",", or silent crashes after installing packages, work through these steps in order:",[550,1028,1029,1052,1095,1101],{},[259,1030,1031,1034,1035],{},[18,1032,1033],{},"Clear corrupted packages."," Delete manually copied folders in the QGIS user profile:",[256,1036,1037,1045],{},[259,1038,1039,1041,1042],{},[18,1040,613],{}," ",[23,1043,1044],{},"%APPDATA%\\QGIS\\QGIS3\\profiles\\default\\python\\",[259,1046,1047,1041,1049],{},[18,1048,640],{},[23,1050,1051],{},"~\u002F.local\u002Fshare\u002FQGIS\u002FQGIS3\u002Fprofiles\u002Fdefault\u002Fpython\u002F",[259,1053,1054,1057,1058],{},[18,1055,1056],{},"Force a reinstall via the bundled pip."," From the Python Console:",[350,1059,1061],{"className":352,"code":1060,"language":354,"meta":355,"style":355},"import subprocess, sys\nsubprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"--force-reinstall\", \"package_name\"])\n",[23,1062,1063,1069],{"__ignoreMap":355},[359,1064,1065,1067],{"class":165,"line":361},[359,1066,365],{"class":364},[359,1068,582],{"class":368},[359,1070,1071,1073,1075,1077,1079,1081,1083,1085,1088,1090,1093],{"class":165,"line":372},[359,1072,587],{"class":368},[359,1074,590],{"class":400},[359,1076,48],{"class":368},[359,1078,595],{"class":400},[359,1080,48],{"class":368},[359,1082,600],{"class":400},[359,1084,48],{"class":368},[359,1086,1087],{"class":400},"\"--force-reinstall\"",[359,1089,48],{"class":368},[359,1091,1092],{"class":400},"\"package_name\"",[359,1094,608],{"class":368},[259,1096,1097,1100],{},[18,1098,1099],{},"Use OSGeo4W for geospatial wheels (Windows)."," For GDAL, Fiona, or Shapely, install via the OSGeo4W installer. It compiles binaries against the exact Python version and MSVC runtime QGIS expects, preventing C-extension segfaults that generic PyPI wheels can cause.",[259,1102,1103,1110,1111,26,1113,1116],{},[18,1104,1105,1106,1109],{},"Avoid ",[23,1107,1108],{},"venv"," for QGIS plugins."," Copying ",[23,1112,650],{},[23,1114,1115],{},"PyQt5"," into a virtual environment only works temporarily and breaks on the next update. Reserve external environments for pure data processing (pandas, rasterio) that runs outside the QGIS process.",[1118,1119,1121],"h3",{"id":1120},"deployment-checklist","Deployment checklist",[256,1123,1124,1134,1145,1148],{},[259,1125,1126,1127,1041,1130,1133],{},"Match your plugin's ",[23,1128,1129],{},"metadata.txt",[23,1131,1132],{},"qgisMinimumVersion"," to the QGIS version you tested against.",[259,1135,1136,1137,1140,1141,1144],{},"Never hardcode ",[23,1138,1139],{},"sys.path",". Use ",[23,1142,1143],{},"os.environ[\"PYTHONPATH\"]"," only when spawning external processes.",[259,1146,1147],{},"Audit C-extensions before upgrading QGIS. A Python minor-version bump (e.g. 3.9 → 3.12) requires recompiling or sourcing new ABI-compatible wheels for every C-extension dependency.",[259,1149,1150,1151,48,1153,631,1155,1158],{},"Keep ",[23,1152,25],{},[23,1154,29],{},[23,1156,1157],{},"qgis.analysis"," imports strictly inside the bundled runtime.",[248,1160,1162],{"id":1161},"conclusion","Conclusion",[14,1164,1165,1166,1169,1170,1172,1173,1172,1175,1177,1178,1180],{},"QGIS compatibility comes down to a single chain: the QGIS release fixes the bundled Python minor version, that minor version fixes the ABI tag, and the ABI tag decides which wheels load. Confirm the interpreter with ",[23,1167,1168],{},"sys.version_info"," in the console, install every C-extension against the matching ",[23,1171,47],{},"\u002F",[23,1174,51],{},[23,1176,54],{}," tag using the bundled ",[23,1179,536],{},", and never try to graft a system or conda Python onto the runtime. When you do upgrade across a minor-version boundary, treat it as a breaking change and recompile your compiled dependencies. Follow those rules and the version-lock stops being a source of cryptic import errors and becomes a predictable constraint you design around.",[248,1182,1184],{"id":1183},"frequently-asked-questions","Frequently Asked Questions",[14,1186,1187,1190,1191,1193,1194,1196],{},[18,1188,1189],{},"Which Python version does QGIS 3.34 LTR ship with?","\nQGIS 3.34 ships with Python 3.12.x, and that interpreter is shared across the whole 3.34–3.36 range. Any C-extension you install for use inside QGIS must therefore target the ",[23,1192,54],{}," ABI. Run ",[23,1195,1168],{}," in the QGIS Python Console to confirm the exact minor version on your install.",[14,1198,1199,1202,1203,26,1205,1208,1209,34,1211,1213],{},[18,1200,1201],{},"Can I upgrade the Python interpreter that QGIS uses?","\nNo. The bundled Python is version-locked because the SIP-generated ",[23,1204,298],{},[23,1206,1207],{},"qgis._gui"," bindings are compiled against one specific Python ABI and Qt build. Swapping in a system Python, conda environment, or newer installer breaks those bindings and produces ",[23,1210,33],{},[23,1212,37],{},". Treat QGIS as a self-contained runtime instead.",[14,1215,1216,1219,1220,1222,1223,1225,1226,1228],{},[18,1217,1218],{},"How do I check whether a wheel is compatible with my QGIS install?","\nMatch the wheel's ABI tag to the QGIS Python minor version: ",[23,1221,47],{}," for QGIS 3.16–3.28, ",[23,1224,51],{}," for 3.30–3.32, and ",[23,1227,54],{}," for 3.34 and later. A wheel built for your operating system's system Python will not necessarily match, so verify the tag before installing any C-extension package. Pure-Python packages with no compiled code are generally safe across minor versions.",[14,1230,1231,1234,1235,1238,1239,1241],{},[18,1232,1233],{},"Why do I get an ABI mismatch after upgrading QGIS?","\nA QGIS upgrade that bumps the Python minor version (for example 3.9 to 3.12) invalidates every previously installed C-extension, because those binaries were compiled against the old ABI. Reinstall the affected packages with ",[23,1236,1237],{},"pip install --force-reinstall"," from the bundled ",[23,1240,536],{},", or source new ABI-compatible wheels. Pure data-processing libraries running outside the QGIS process in their own environment are unaffected.",[14,1243,1244,1247,1248,34,1250,1252,1253,1255],{},[18,1245,1246],{},"Should I use a virtual environment for QGIS plugin dependencies?","\nNot for code that imports ",[23,1249,650],{},[23,1251,1115],{},". Copying those bindings into a ",[23,1254,1108],{}," only works temporarily and breaks on the next QGIS update. Reserve virtual environments for pure data-processing dependencies such as pandas or rasterio that run outside the QGIS process, and let QGIS manage its own bundled packages.",[248,1257,1259],{"id":1258},"related","Related",[256,1261,1262,1271,1275,1279],{},[259,1263,1264,1041,1267,1270],{},[18,1265,1266],{},"Up:",[266,1268,1269],{"href":293},"QGIS API Architecture Explained"," — the parent topic covering how PyQGIS bindings are generated.",[259,1272,1273],{},[266,1274,269],{"href":268},[259,1276,1277],{},[266,1278,655],{"href":654},[259,1280,1281,1283],{},[266,1282,547],{"href":546}," — the top-level overview for the whole setup workflow.",[1285,1286,1287],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#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);}",{"title":355,"searchDepth":372,"depth":372,"links":1289},[1290,1291,1292,1293,1294,1295,1296,1297,1300,1301,1302],{"id":250,"depth":372,"text":251},{"id":308,"depth":372,"text":309},{"id":340,"depth":372,"text":341},{"id":539,"depth":372,"text":540},{"id":658,"depth":372,"text":659},{"id":778,"depth":372,"text":779},{"id":867,"depth":372,"text":868},{"id":1016,"depth":372,"text":1017,"children":1298},[1299],{"id":1120,"depth":380,"text":1121},{"id":1161,"depth":372,"text":1162},{"id":1183,"depth":372,"text":1184},{"id":1258,"depth":372,"text":1259},"Match the right Python version to your QGIS release. See which Python and PyQGIS versions ship together, how ABI tags work, and how to avoid version-lock errors.","md",{"slug":12,"type":1306,"breadcrumb":1307,"datePublished":1308,"dateModified":1309},"article","Python Version Compatibility","2025-03-08","2026-08-27","\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-api-architecture\u002Fqgis-python-version-compatibility-guide",{"title":5,"description":1303},"pyqgis-fundamentals-environment-setup\u002Fqgis-api-architecture\u002Fqgis-python-version-compatibility-guide\u002Findex","KK6sFRbf3tm-Y_6Yy9_kqgQRQjSwftM5HocGV3tXI74",1787823360564]