[{"data":1,"prerenderedAt":1982},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis":3},{"id":4,"title":5,"body":6,"description":1970,"extension":1971,"meta":1972,"navigation":672,"path":1978,"seo":1979,"stem":1980,"__hash__":1981},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Findex.md","Virtual Environments for QGIS and PyQGIS",{"type":7,"value":8,"toc":1943},"minimark",[9,14,18,42,188,193,196,233,237,240,245,248,287,290,311,322,326,341,360,374,378,392,419,426,430,438,455,482,500,510,582,589,606,613,630,636,640,647,1034,1038,1090,1098,1102,1113,1261,1268,1280,1306,1313,1327,1347,1351,1358,1369,1377,1387,1418,1422,1436,1452,1456,1459,1511,1515,1521,1625,1639,1642,1645,1649,1735,1738,1742,1766,1777,1791,1813,1829,1835,1841,1847,1857,1866,1872,1878,1884,1888,1939],[10,11,13],"h1",{"id":12},"virtual-environments-for-gis-a-step-by-step-guide-for-qgis-and-pyqgis","Virtual Environments for GIS: A Step-by-Step Guide for QGIS and PyQGIS",[15,16,17],"p",{},"Geospatial development relies heavily on complex, interdependent libraries. GDAL, PROJ, NumPy, and the QGIS Python bindings must align precisely to avoid silent failures or corrupted spatial operations. Isolating project dependencies with a Python virtual environment has become a standard practice for modern spatial analysts and developers. By separating a project's packages from your system-wide Python installation, you gain reproducible builds, cleaner dependency trees, and the ability to test PyQGIS scripts without risking your core QGIS installation.",[15,19,20,21,26,27,31,32,36,37,41],{},"This guide builds on the ",[22,23,25],"a",{"href":24},"\u002Fpyqgis-fundamentals-environment-setup\u002F","PyQGIS Fundamentals & Environment Setup"," overview and focuses on one job: standing up an isolated Python environment that can ",[28,29,30],"code",{},"import qgis.core"," correctly. It is a companion to configuring the ",[22,33,35],{"href":34},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002F","QGIS Python Console"," for in-app work and to ",[22,38,40],{"href":39},"\u002Fpyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002F","Setting Up PyCharm for QGIS"," for IDE-driven development — a virtual environment is the artifact all of those workflows attach to. Whether you are automating map exports, building custom processing algorithms, or integrating spatial data pipelines, a properly configured environment ensures your code behaves consistently across deployments.",[43,44,49,53,57,64,74,83,89,97,103,108,113,117,121,125,129,133,136,141,145,149,152,156,159,163,167,172],"svg",{"viewBox":45,"role":46,"ariaLabel":47,"xmlns":48},"0 0 760 380","img","How a virtual environment layers on top of QGIS's bundled Python and compiled bindings","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[50,51,52],"title",{},"Virtual environment layering over QGIS bundled Python",[54,55,56],"desc",{},"QGIS ships its own Python interpreter with compiled qgis bindings and GDAL or PROJ. A venv created with the system flag inherits those bindings, then layers pure-Python pip packages on top.",[58,59],"rect",{"x":60,"y":60,"width":61,"height":62,"fill":63},"0","760","380","#f6f3ea",[58,65],{"x":66,"y":67,"width":68,"height":69,"rx":70,"fill":71,"stroke":72,"style":73},"30","40","320","300","10","#fffdf7","#0f766e","stroke-width:2.5",[75,76,82],"text",{"x":77,"y":78,"style":79,"fill":80,"textAnchor":81},"190","68","text-anchor:middle;font-size:16px;font-weight:bold;font-family:sans-serif","#17211d","middle","QGIS-Bundled Python",[75,84,88],{"x":77,"y":85,"style":86,"fill":87,"textAnchor":81},"88","text-anchor:middle;font-size:12px;font-family:sans-serif","#2f3b35","(the interpreter QGIS installs)",[58,90],{"x":91,"y":92,"width":93,"height":94,"rx":95,"fill":96},"55","108","270","46","6","#26322d",[75,98,102],{"x":77,"y":99,"style":100,"fill":101,"textAnchor":81},"136","text-anchor:middle;font-size:13px;font-family:monospace","#d9f99d","python3.12  (matches QGIS 3.34 LTR)",[58,104],{"x":91,"y":105,"width":93,"height":94,"rx":95,"fill":71,"stroke":106,"style":107},"168","#2563eb","stroke-width:2",[75,109,112],{"x":77,"y":110,"style":111,"fill":87,"textAnchor":81},"196","text-anchor:middle;font-size:13px;font-family:sans-serif","qgis.core \u002F qgis.gui bindings",[58,114],{"x":91,"y":115,"width":93,"height":94,"rx":95,"fill":71,"stroke":116,"style":107},"228","#b45309",[75,118,120],{"x":77,"y":119,"style":111,"fill":87,"textAnchor":81},"256","compiled GDAL \u002F PROJ \u002F Qt",[75,122,124],{"x":77,"y":123,"style":86,"fill":87,"textAnchor":81},"306","version-locked C extensions",[58,126],{"x":127,"y":67,"width":68,"height":69,"rx":70,"fill":71,"stroke":128,"style":73},"410","#15803d",[75,130,132],{"x":131,"y":78,"style":79,"fill":80,"textAnchor":81},"570","Your venv",[75,134,135],{"x":131,"y":85,"style":86,"fill":87,"textAnchor":81},"--system-site-packages",[58,137],{"x":138,"y":92,"width":93,"height":139,"rx":95,"fill":63,"stroke":72,"style":140},"435","66","stroke-width:2;stroke-dasharray:5 4",[75,142,144],{"x":131,"y":143,"style":86,"fill":87,"textAnchor":81},"134","inherits qgis bindings + GDAL",[75,146,148],{"x":131,"y":147,"style":86,"fill":87,"textAnchor":81},"154","from the bundled Python (no copy)",[58,150],{"x":138,"y":77,"width":93,"height":94,"rx":95,"fill":71,"stroke":151,"style":107},"#22c55e",[75,153,155],{"x":131,"y":154,"style":111,"fill":87,"textAnchor":81},"218","pip install requests, pandas, ...",[58,157],{"x":138,"y":158,"width":93,"height":94,"rx":95,"fill":71,"stroke":151,"style":107},"250",[75,160,162],{"x":131,"y":161,"style":111,"fill":87,"textAnchor":81},"278","pure-Python deps layered on top",[75,164,166],{"x":131,"y":165,"style":86,"fill":87,"textAnchor":81},"322","never pip install gdal here",[168,169],"line",{"x1":170,"y1":77,"x2":127,"y2":77,"stroke":72,"style":171},"350","stroke-width:3;marker-end:url(#vearrow)",[173,174,175],"defs",{},[176,177,184],"marker",{"id":178,"viewBox":179,"refX":180,"refY":181,"markerWidth":182,"markerHeight":182,"orient":183},"vearrow","0 0 10 10","8","5","7","auto",[185,186],"path",{"d":187,"fill":72},"M0 0 L10 5 L0 10 z",[189,190,192],"h2",{"id":191},"prerequisites","Prerequisites",[15,194,195],{},"Before configuring an isolated workspace, ensure your system meets the following baseline requirements:",[197,198,199,207,213,227],"ol",{},[200,201,202,206],"li",{},[203,204,205],"strong",{},"QGIS Installed",": A stable Long-Term Release (LTR) or current stable version must be installed on your machine. The virtual environment will reference QGIS's bundled Python interpreter and compiled C-extensions.",[200,208,209,212],{},[203,210,211],{},"Terminal\u002FCommand Line Access",": You will need to execute shell commands. Windows users should use Command Prompt or PowerShell; macOS\u002FLinux users should use Terminal.",[200,214,215,218,219,222,223,226],{},[203,216,217],{},"Basic Python Familiarity",": Understanding how Python resolves modules, how ",[28,220,221],{},"sys.path"," works, and how to activate\u002Fdeactivate environments will streamline the process. If you are new to executing code within QGIS itself, reviewing ",[22,224,225],{"href":34},"QGIS Python Console Basics"," will provide essential context before moving to external environments.",[200,228,229,232],{},[203,230,231],{},"Write Permissions",": Ensure you have write access to the directory where you plan to create the virtual environment.",[189,234,236],{"id":235},"step-by-step-workflow","Step-by-Step Workflow",[15,238,239],{},"The following workflow creates a self-contained Python environment that mirrors your QGIS installation's Python version and library paths.",[241,242,244],"h3",{"id":243},"step-1-locate-the-qgis-python-executable","Step 1: Locate the QGIS Python Executable",[15,246,247],{},"QGIS ships with its own Python distribution to guarantee compatibility with compiled spatial libraries. You must identify the exact path to this interpreter.",[249,250,251,265,274],"ul",{},[200,252,253,256,257,260,261,264],{},[203,254,255],{},"Windows",": Typically ",[28,258,259],{},"C:\\OSGeo4W\\apps\\Python312\\python.exe"," (QGIS 3.34+, including 3.44 LTR) or ",[28,262,263],{},"C:\\OSGeo4W\\apps\\Python39\\python.exe"," (older QGIS 3.28)",[200,266,267,270,271],{},[203,268,269],{},"macOS",": Usually ",[28,272,273],{},"\u002FApplications\u002FQGIS.app\u002FContents\u002FMacOS\u002Fbin\u002Fpython3",[200,275,276,279,280,283,284],{},[203,277,278],{},"Linux",": Often ",[28,281,282],{},"\u002Fusr\u002Fbin\u002Fpython3"," (package manager) or ",[28,285,286],{},"\u002Fopt\u002Fqgis\u002Fbin\u002Fpython3",[15,288,289],{},"Open your terminal and verify the version:",[291,292,297],"pre",{"className":293,"code":294,"language":295,"meta":296,"style":296},"language-bash shiki shiki-themes github-dark","\"\u003Cpath-to-qgis-python>\" --version\n","bash","",[28,298,299],{"__ignoreMap":296},[300,301,303,307],"span",{"class":168,"line":302},1,[300,304,306],{"class":305},"svObZ","\"\u003Cpath-to-qgis-python>\"",[300,308,310],{"class":309},"sDLfK"," --version\n",[15,312,313,314,317,318,321],{},"The output should match your QGIS version's Python release (e.g., ",[28,315,316],{},"Python 3.9.x"," or ",[28,319,320],{},"3.12.x",").",[241,323,325],{"id":324},"step-2-create-the-virtual-environment","Step 2: Create the Virtual Environment",[15,327,328,329,332,333,336,337,340],{},"Use Python's built-in ",[28,330,331],{},"venv"," module to generate the isolated directory. Replace ",[28,334,335],{},"\u003Cpath-to-qgis-python>"," with the executable located in Step 1, and ",[28,338,339],{},"gis_env"," with your preferred environment name.",[291,342,344],{"className":293,"code":343,"language":295,"meta":296,"style":296},"\"\u003Cpath-to-qgis-python>\" -m venv gis_env\n",[28,345,346],{"__ignoreMap":296},[300,347,348,350,353,357],{"class":168,"line":302},[300,349,306],{"class":305},[300,351,352],{"class":309}," -m",[300,354,356],{"class":355},"sU2Wk"," venv",[300,358,359],{"class":355}," gis_env\n",[15,361,362,363,365,366,369,370,373],{},"This command creates a ",[28,364,339],{}," folder containing a private Python binary, ",[28,367,368],{},"pip",", and an empty ",[28,371,372],{},"site-packages"," directory.",[241,375,377],{"id":376},"step-3-activate-the-environment","Step 3: Activate the Environment",[15,379,380,381,384,385,388,389,391],{},"Activation modifies your shell's ",[28,382,383],{},"PATH"," variable so that ",[28,386,387],{},"python"," and ",[28,390,368],{}," commands point to the virtual environment.",[249,393,394,403,411],{},[200,395,396,399,400],{},[203,397,398],{},"Windows (CMD)",": ",[28,401,402],{},"gis_env\\Scripts\\activate.bat",[200,404,405,399,408],{},[203,406,407],{},"Windows (PowerShell)",[28,409,410],{},"gis_env\\Scripts\\Activate.ps1",[200,412,413,399,416],{},[203,414,415],{},"macOS\u002FLinux",[28,417,418],{},"source gis_env\u002Fbin\u002Factivate",[15,420,421,422,425],{},"Your terminal prompt should now display ",[28,423,424],{},"(gis_env)",".",[241,427,429],{"id":428},"step-4-map-qgis-paths-environment-variables","Step 4: Map QGIS Paths & Environment Variables",[15,431,432,433,388,435,437],{},"The virtual environment does not automatically know where QGIS's compiled modules reside. You must explicitly point it to the QGIS ",[28,434,387],{},[28,436,372],{}," directories, then set the required environment variables.",[15,439,440,447,448,450,451,454],{},[203,441,442,443,446],{},"1. Create a ",[28,444,445],{},".pth"," file","\nNavigate to your environment's ",[28,449,372],{}," folder and create a file named ",[28,452,453],{},"qgis.pth",". Add the absolute paths to QGIS's Python libraries (one per line). Do not use relative paths.",[249,456,457,470],{},[200,458,459,462,463],{},[203,460,461],{},"Windows example paths"," (QGIS 3.44 LTR via OSGeo4W):\n",[291,464,468],{"className":465,"code":467,"language":75},[466],"language-text","C:\\OSGeo4W\\apps\\qgis-ltr\\python\nC:\\OSGeo4W\\apps\\Python312\\Lib\\site-packages\n",[28,469,467],{"__ignoreMap":296},[200,471,472,475,476],{},[203,473,474],{},"macOS example paths",":\n",[291,477,480],{"className":478,"code":479,"language":75},[466],"\u002FApplications\u002FQGIS.app\u002FContents\u002FResources\u002Fpython\n\u002FApplications\u002FQGIS.app\u002FContents\u002FResources\u002Fpython\u002Fplugins\n",[28,481,479],{"__ignoreMap":296},[15,483,484,487,488,491,492,495,496,499],{},[203,485,486],{},"2. Update the activation script","\nOpen your environment's activation script (",[28,489,490],{},"Scripts\u002Factivate.bat"," for Windows CMD, ",[28,493,494],{},"Scripts\u002FActivate.ps1"," for PowerShell, or ",[28,497,498],{},"bin\u002Factivate"," for macOS\u002FLinux) and append the following variables. This ensures they load automatically every time you activate the environment.",[15,501,502,509],{},[503,504,505,506,508],"em",{},"macOS\u002FLinux (",[28,507,498],{},")",":",[291,511,513],{"className":293,"code":512,"language":295,"meta":296,"style":296},"export QGIS_PREFIX_PATH=\"\u002FApplications\u002FQGIS.app\u002FContents\u002FMacOS\"\nexport PYTHONPATH=\"${QGIS_PREFIX_PATH}\u002F..\u002FResources\u002Fpython:${QGIS_PREFIX_PATH}\u002F..\u002FResources\u002Fpython\u002Fplugins:${PYTHONPATH}\"\nexport PATH=\"${QGIS_PREFIX_PATH}\u002Fbin:${PATH}\"\n",[28,514,515,531,561],{"__ignoreMap":296},[300,516,517,521,525,528],{"class":168,"line":302},[300,518,520],{"class":519},"snl16","export",[300,522,524],{"class":523},"s95oV"," QGIS_PREFIX_PATH",[300,526,527],{"class":519},"=",[300,529,530],{"class":355},"\"\u002FApplications\u002FQGIS.app\u002FContents\u002FMacOS\"\n",[300,532,534,536,539,541,544,547,550,552,555,558],{"class":168,"line":533},2,[300,535,520],{"class":519},[300,537,538],{"class":523}," PYTHONPATH",[300,540,527],{"class":519},[300,542,543],{"class":355},"\"${",[300,545,546],{"class":523},"QGIS_PREFIX_PATH",[300,548,549],{"class":355},"}\u002F..\u002FResources\u002Fpython:${",[300,551,546],{"class":523},[300,553,554],{"class":355},"}\u002F..\u002FResources\u002Fpython\u002Fplugins:${",[300,556,557],{"class":523},"PYTHONPATH",[300,559,560],{"class":355},"}\"\n",[300,562,564,566,569,571,573,575,578,580],{"class":168,"line":563},3,[300,565,520],{"class":519},[300,567,568],{"class":523}," PATH",[300,570,527],{"class":519},[300,572,543],{"class":355},[300,574,546],{"class":523},[300,576,577],{"class":355},"}\u002Fbin:${",[300,579,383],{"class":523},[300,581,560],{"class":355},[15,583,584,509],{},[503,585,586,587,508],{},"Windows CMD (",[28,588,490],{},[291,590,594],{"className":591,"code":592,"language":593,"meta":296,"style":296},"language-bat shiki shiki-themes github-dark","set \"QGIS_PREFIX_PATH=C:\\OSGeo4W\\apps\\qgis-ltr\"\nset \"PATH=%QGIS_PREFIX_PATH%\\bin;C:\\OSGeo4W\\bin;%PATH%\"\n","bat",[28,595,596,601],{"__ignoreMap":296},[300,597,598],{"class":168,"line":302},[300,599,600],{},"set \"QGIS_PREFIX_PATH=C:\\OSGeo4W\\apps\\qgis-ltr\"\n",[300,602,603],{"class":168,"line":533},[300,604,605],{},"set \"PATH=%QGIS_PREFIX_PATH%\\bin;C:\\OSGeo4W\\bin;%PATH%\"\n",[15,607,608,509],{},[503,609,610,611,508],{},"Windows PowerShell (",[28,612,494],{},[291,614,618],{"className":615,"code":616,"language":617,"meta":296,"style":296},"language-powershell shiki shiki-themes github-dark","$env:QGIS_PREFIX_PATH = \"C:\\OSGeo4W\\apps\\qgis-ltr\"\n$env:PATH = \"$env:QGIS_PREFIX_PATH\\bin;C:\\OSGeo4W\\bin;$env:PATH\"\n","powershell",[28,619,620,625],{"__ignoreMap":296},[300,621,622],{"class":168,"line":302},[300,623,624],{},"$env:QGIS_PREFIX_PATH = \"C:\\OSGeo4W\\apps\\qgis-ltr\"\n",[300,626,627],{"class":168,"line":533},[300,628,629],{},"$env:PATH = \"$env:QGIS_PREFIX_PATH\\bin;C:\\OSGeo4W\\bin;$env:PATH\"\n",[15,631,632,633,635],{},"Once configured, your environment is ready. Developers who prefer integrated development environments should review ",[22,634,40],{"href":39}," to attach this exact environment to a professional IDE for debugging, linting, and autocomplete.",[189,637,639],{"id":638},"code-breakdown-execution","Code Breakdown & Execution",[15,641,642,643,646],{},"After activation, verify that PyQGIS imports correctly. Create a file named ",[28,644,645],{},"verify_qgis.py"," with the following content:",[291,648,651],{"className":649,"code":650,"language":387,"meta":296,"style":296},"language-python shiki shiki-themes github-dark","import sys\nimport os\n\ndef check_pyqgis():\n    try:\n        from qgis.core import QgsApplication\n        from osgeo import gdal, ogr\n\n        print(f\"Python Executable: {sys.executable}\")\n        print(f\"Python Version: {sys.version}\")\n        print(f\"GDAL Version: {gdal.__version__}\")\n\n        # Initialize QGIS application (required for headless operations)\n        prefix = os.environ.get(\"QGIS_PREFIX_PATH\", \"\")\n        qgs = QgsApplication([], False)\n        qgs.setPrefixPath(prefix, True)\n        qgs.initQgis()\n\n        print(\"QGIS Core Import: SUCCESS\")\n        print(\"QGIS Application Initialized: SUCCESS\")\n\n        qgs.exitQgis()\n        return True\n    except ImportError as e:\n        print(f\"Import Error: {e}\")\n        return False\n    except Exception as e:\n        print(f\"Initialization Error: {e}\")\n        return False\n\nif __name__ == \"__main__\":\n    check_pyqgis()\n",[28,652,653,661,668,674,686,694,708,721,726,756,779,803,808,815,837,853,864,870,875,887,899,904,910,919,934,957,965,977,999,1006,1011,1028],{"__ignoreMap":296},[300,654,655,658],{"class":168,"line":302},[300,656,657],{"class":519},"import",[300,659,660],{"class":523}," sys\n",[300,662,663,665],{"class":168,"line":533},[300,664,657],{"class":519},[300,666,667],{"class":523}," os\n",[300,669,670],{"class":168,"line":563},[300,671,673],{"emptyLinePlaceholder":672},true,"\n",[300,675,677,680,683],{"class":168,"line":676},4,[300,678,679],{"class":519},"def",[300,681,682],{"class":305}," check_pyqgis",[300,684,685],{"class":523},"():\n",[300,687,689,692],{"class":168,"line":688},5,[300,690,691],{"class":519},"    try",[300,693,475],{"class":523},[300,695,697,700,703,705],{"class":168,"line":696},6,[300,698,699],{"class":519},"        from",[300,701,702],{"class":523}," qgis.core ",[300,704,657],{"class":519},[300,706,707],{"class":523}," QgsApplication\n",[300,709,711,713,716,718],{"class":168,"line":710},7,[300,712,699],{"class":519},[300,714,715],{"class":523}," osgeo ",[300,717,657],{"class":519},[300,719,720],{"class":523}," gdal, ogr\n",[300,722,724],{"class":168,"line":723},8,[300,725,673],{"emptyLinePlaceholder":672},[300,727,729,732,735,738,741,744,747,750,753],{"class":168,"line":728},9,[300,730,731],{"class":309},"        print",[300,733,734],{"class":523},"(",[300,736,737],{"class":519},"f",[300,739,740],{"class":355},"\"Python Executable: ",[300,742,743],{"class":309},"{",[300,745,746],{"class":523},"sys.executable",[300,748,749],{"class":309},"}",[300,751,752],{"class":355},"\"",[300,754,755],{"class":523},")\n",[300,757,759,761,763,765,768,770,773,775,777],{"class":168,"line":758},10,[300,760,731],{"class":309},[300,762,734],{"class":523},[300,764,737],{"class":519},[300,766,767],{"class":355},"\"Python Version: ",[300,769,743],{"class":309},[300,771,772],{"class":523},"sys.version",[300,774,749],{"class":309},[300,776,752],{"class":355},[300,778,755],{"class":523},[300,780,782,784,786,788,791,793,796,799,801],{"class":168,"line":781},11,[300,783,731],{"class":309},[300,785,734],{"class":523},[300,787,737],{"class":519},[300,789,790],{"class":355},"\"GDAL Version: ",[300,792,743],{"class":309},[300,794,795],{"class":523},"gdal.",[300,797,798],{"class":309},"__version__}",[300,800,752],{"class":355},[300,802,755],{"class":523},[300,804,806],{"class":168,"line":805},12,[300,807,673],{"emptyLinePlaceholder":672},[300,809,811],{"class":168,"line":810},13,[300,812,814],{"class":813},"sjoCn","        # Initialize QGIS application (required for headless operations)\n",[300,816,818,821,823,826,829,832,835],{"class":168,"line":817},14,[300,819,820],{"class":523},"        prefix ",[300,822,527],{"class":519},[300,824,825],{"class":523}," os.environ.get(",[300,827,828],{"class":355},"\"QGIS_PREFIX_PATH\"",[300,830,831],{"class":523},", ",[300,833,834],{"class":355},"\"\"",[300,836,755],{"class":523},[300,838,840,843,845,848,851],{"class":168,"line":839},15,[300,841,842],{"class":523},"        qgs ",[300,844,527],{"class":519},[300,846,847],{"class":523}," QgsApplication([], ",[300,849,850],{"class":309},"False",[300,852,755],{"class":523},[300,854,856,859,862],{"class":168,"line":855},16,[300,857,858],{"class":523},"        qgs.setPrefixPath(prefix, ",[300,860,861],{"class":309},"True",[300,863,755],{"class":523},[300,865,867],{"class":168,"line":866},17,[300,868,869],{"class":523},"        qgs.initQgis()\n",[300,871,873],{"class":168,"line":872},18,[300,874,673],{"emptyLinePlaceholder":672},[300,876,878,880,882,885],{"class":168,"line":877},19,[300,879,731],{"class":309},[300,881,734],{"class":523},[300,883,884],{"class":355},"\"QGIS Core Import: SUCCESS\"",[300,886,755],{"class":523},[300,888,890,892,894,897],{"class":168,"line":889},20,[300,891,731],{"class":309},[300,893,734],{"class":523},[300,895,896],{"class":355},"\"QGIS Application Initialized: SUCCESS\"",[300,898,755],{"class":523},[300,900,902],{"class":168,"line":901},21,[300,903,673],{"emptyLinePlaceholder":672},[300,905,907],{"class":168,"line":906},22,[300,908,909],{"class":523},"        qgs.exitQgis()\n",[300,911,913,916],{"class":168,"line":912},23,[300,914,915],{"class":519},"        return",[300,917,918],{"class":309}," True\n",[300,920,922,925,928,931],{"class":168,"line":921},24,[300,923,924],{"class":519},"    except",[300,926,927],{"class":309}," ImportError",[300,929,930],{"class":519}," as",[300,932,933],{"class":523}," e:\n",[300,935,937,939,941,943,946,948,951,953,955],{"class":168,"line":936},25,[300,938,731],{"class":309},[300,940,734],{"class":523},[300,942,737],{"class":519},[300,944,945],{"class":355},"\"Import Error: ",[300,947,743],{"class":309},[300,949,950],{"class":523},"e",[300,952,749],{"class":309},[300,954,752],{"class":355},[300,956,755],{"class":523},[300,958,960,962],{"class":168,"line":959},26,[300,961,915],{"class":519},[300,963,964],{"class":309}," False\n",[300,966,968,970,973,975],{"class":168,"line":967},27,[300,969,924],{"class":519},[300,971,972],{"class":309}," Exception",[300,974,930],{"class":519},[300,976,933],{"class":523},[300,978,980,982,984,986,989,991,993,995,997],{"class":168,"line":979},28,[300,981,731],{"class":309},[300,983,734],{"class":523},[300,985,737],{"class":519},[300,987,988],{"class":355},"\"Initialization Error: ",[300,990,743],{"class":309},[300,992,950],{"class":523},[300,994,749],{"class":309},[300,996,752],{"class":355},[300,998,755],{"class":523},[300,1000,1002,1004],{"class":168,"line":1001},29,[300,1003,915],{"class":519},[300,1005,964],{"class":309},[300,1007,1009],{"class":168,"line":1008},30,[300,1010,673],{"emptyLinePlaceholder":672},[300,1012,1014,1017,1020,1023,1026],{"class":168,"line":1013},31,[300,1015,1016],{"class":519},"if",[300,1018,1019],{"class":309}," __name__",[300,1021,1022],{"class":519}," ==",[300,1024,1025],{"class":355}," \"__main__\"",[300,1027,475],{"class":523},[300,1029,1031],{"class":168,"line":1030},32,[300,1032,1033],{"class":523},"    check_pyqgis()\n",[241,1035,1037],{"id":1036},"how-it-works","How It Works",[197,1039,1040,1059,1068,1081],{},[200,1041,1042,1045,1046,388,1049,1052,1053,1055,1056,425],{},[203,1043,1044],{},"Module Resolution",": The script imports ",[28,1047,1048],{},"qgis.core",[28,1050,1051],{},"osgeo",". If the ",[28,1054,445],{}," paths are correct, Python resolves these without raising ",[28,1057,1058],{},"ModuleNotFoundError",[200,1060,1061,399,1064,1067],{},[203,1062,1063],{},"Prefix Path Configuration",[28,1065,1066],{},"QgsApplication.setPrefixPath()"," tells the QGIS API where to find plugins, SVG resources, and CRS databases. This is mandatory when running PyQGIS outside the desktop GUI.",[200,1069,1070,399,1073,1076,1077,1080],{},[203,1071,1072],{},"Headless Initialization",[28,1074,1075],{},"qgs.initQgis()"," bootstraps the C++ backend. Without this, spatial operations like ",[28,1078,1079],{},"QgsVectorLayer"," will fail silently or crash.",[200,1082,1083,399,1086,1089],{},[203,1084,1085],{},"Graceful Exit",[28,1087,1088],{},"qgs.exitQgis()"," releases memory and cleans up GDAL drivers, preventing file locks on Windows.",[15,1091,1092,1093,1097],{},"Executing this script demonstrates how to reliably run ",[22,1094,1096],{"href":1095},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Frunning-python-scripts-outside-qgis-desktop\u002F","Running Python scripts outside QGIS desktop",", which is essential for automated ETL pipelines, scheduled geoprocessing tasks, and CI\u002FCD workflows.",[189,1099,1101],{"id":1100},"common-errors-resolutions","Common Errors & Resolutions",[15,1103,1104,1105,1109,1110,1112],{},"Even with careful setup, environment mismatches occur. Below are the most frequent issues and their tested resolutions. If you would rather step through failures interactively, pair this section with ",[22,1106,1108],{"href":1107},"\u002Fpyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002F","Debugging PyQGIS Scripts",", which covers reading tracebacks and inspecting ",[28,1111,221],{}," at runtime.",[43,1114,1117,1120,1123,1125,1132,1137,1142,1147,1151,1155,1158,1161,1163,1167,1171,1176,1179,1182,1186,1189,1192,1195,1198,1201,1204,1207,1209,1211,1213,1217,1221,1225,1229,1231,1234,1237,1240,1242,1245,1248,1251,1253,1256,1258],{"viewBox":1115,"role":46,"ariaLabel":1116,"xmlns":48},"0 0 760 320","Decision tree routing a failed import qgis.core to its fix by symptom: ModuleNotFoundError, WinError 126, Apple Silicon segfault, or CRS database error",[50,1118,1119],{},"Routing a failed import qgis.core to the right fix",[54,1121,1122],{},"Starting from a failing import qgis.core, four symptoms branch to their fixes: ModuleNotFoundError means fix the qgis.pth paths; WinError 126 means add the QGIS bin folder to PATH; a segfault on Apple Silicon means use the bundled Python with arch -arm64 pip; and a CRS or srs.db error means set QGIS_PREFIX_PATH before setPrefixPath.",[58,1124],{"x":60,"y":60,"width":61,"height":68,"fill":63},[173,1126,1127],{},[176,1128,1130],{"id":1129,"viewBox":179,"refX":180,"refY":181,"markerWidth":180,"markerHeight":180,"orient":183},"vefixarrow",[185,1131],{"d":187,"fill":72},[58,1133],{"x":93,"y":1134,"width":1135,"height":1136,"rx":180,"fill":96},"20","220","44",[75,1138,1141],{"x":62,"y":1139,"style":1140,"fill":101,"textAnchor":81},"47","text-anchor:middle;font-size:14px;font-weight:bold;font-family:monospace","import qgis.core fails",[168,1143],{"x1":62,"y1":1144,"x2":62,"y2":1145,"stroke":72,"style":1146},"64","84","stroke-width:3",[168,1148],{"x1":1149,"y1":1145,"x2":1150,"y2":1145,"stroke":72,"style":1146},"102","658",[168,1152],{"x1":1149,"y1":1145,"x2":1149,"y2":1153,"stroke":72,"style":1154},"104","stroke-width:3;marker-end:url(#vefixarrow)",[168,1156],{"x1":1157,"y1":1145,"x2":1157,"y2":1153,"stroke":72,"style":1154},"287",[168,1159],{"x1":1160,"y1":1145,"x2":1160,"y2":1153,"stroke":72,"style":1154},"473",[168,1162],{"x1":1150,"y1":1145,"x2":1150,"y2":1153,"stroke":72,"style":1154},[58,1164],{"x":1165,"y":1153,"width":105,"height":1166,"rx":180,"fill":71,"stroke":72,"style":73},"18","56",[75,1168,1058],{"x":1149,"y":1169,"style":1170,"fill":80,"textAnchor":81},"128","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif",[75,1172,1175],{"x":1149,"y":1173,"style":1174,"fill":87,"textAnchor":81},"147","text-anchor:middle;font-size:11px;font-family:monospace","'qgis.core' missing",[58,1177],{"x":1178,"y":1153,"width":105,"height":1166,"rx":180,"fill":71,"stroke":106,"style":73},"203",[75,1180,1181],{"x":1157,"y":1169,"style":1170,"fill":80,"textAnchor":81},"WinError 126",[75,1183,1185],{"x":1157,"y":1173,"style":1184,"fill":87,"textAnchor":81},"text-anchor:middle;font-size:11px;font-family:sans-serif","DLL not found",[58,1187],{"x":1188,"y":1153,"width":105,"height":1166,"rx":180,"fill":71,"stroke":116,"style":73},"389",[75,1190,1191],{"x":1160,"y":1169,"style":1170,"fill":80,"textAnchor":81},"Segfault on import",[75,1193,1194],{"x":1160,"y":1173,"style":1184,"fill":87,"textAnchor":81},"Apple Silicon ARM64",[58,1196],{"x":1197,"y":1153,"width":105,"height":1166,"rx":180,"fill":71,"stroke":128,"style":73},"574",[75,1199,1200],{"x":1150,"y":1169,"style":1170,"fill":80,"textAnchor":81},"CRS \u002F srs.db error",[75,1202,1203],{"x":1150,"y":1173,"style":1184,"fill":87,"textAnchor":81},"app init fails",[168,1205],{"x1":1149,"y1":1206,"x2":1149,"y2":115,"stroke":72,"style":1154},"160",[168,1208],{"x1":1157,"y1":1206,"x2":1157,"y2":115,"stroke":72,"style":1154},[168,1210],{"x1":1160,"y1":1206,"x2":1160,"y2":115,"stroke":72,"style":1154},[168,1212],{"x1":1150,"y1":1206,"x2":1150,"y2":115,"stroke":72,"style":1154},[58,1214],{"x":1165,"y":115,"width":105,"height":1215,"rx":180,"fill":1216,"stroke":72,"style":107},"76","#eef7f4",[75,1218,1220],{"x":1149,"y":1219,"style":1170,"fill":80,"textAnchor":81},"252","Fix qgis.pth",[75,1222,1224],{"x":1149,"y":1223,"style":1184,"fill":87,"textAnchor":81},"272","absolute paths to",[75,1226,1228],{"x":1149,"y":1227,"style":1184,"fill":87,"textAnchor":81},"289","QGIS python folder",[58,1230],{"x":1178,"y":115,"width":105,"height":1215,"rx":180,"fill":1216,"stroke":106,"style":107},[75,1232,1233],{"x":1157,"y":1219,"style":1170,"fill":80,"textAnchor":81},"Add QGIS bin",[75,1235,1236],{"x":1157,"y":1223,"style":1184,"fill":87,"textAnchor":81},"to PATH, prepend",[75,1238,1239],{"x":1157,"y":1227,"style":1184,"fill":87,"textAnchor":81},"before import qgis",[58,1241],{"x":1188,"y":115,"width":105,"height":1215,"rx":180,"fill":1216,"stroke":116,"style":107},[75,1243,1244],{"x":1160,"y":1219,"style":1170,"fill":80,"textAnchor":81},"Bundled Python +",[75,1246,1247],{"x":1160,"y":1223,"style":1174,"fill":87,"textAnchor":81},"arch -arm64 pip",[75,1249,1250],{"x":1160,"y":1227,"style":1184,"fill":87,"textAnchor":81},"install packages",[58,1252],{"x":1197,"y":115,"width":105,"height":1215,"rx":180,"fill":1216,"stroke":128,"style":107},[75,1254,1255],{"x":1150,"y":1219,"style":1170,"fill":80,"textAnchor":81},"Set prefix path",[75,1257,546],{"x":1150,"y":1223,"style":1174,"fill":87,"textAnchor":81},[75,1259,1260],{"x":1150,"y":1227,"style":1184,"fill":87,"textAnchor":81},"before setPrefixPath",[241,1262,1264,1265],{"id":1263},"_1-modulenotfounderror-no-module-named-qgiscore","1. ",[28,1266,1267],{},"ModuleNotFoundError: No module named 'qgis.core'",[15,1269,1270,1273,1274,1276,1277,509],{},[203,1271,1272],{},"Cause",": The ",[28,1275,445],{}," file is missing, points to the wrong directory, or contains syntax errors.\n",[203,1278,1279],{},"Fix",[249,1281,1282,1293,1299],{},[200,1283,1284,1285,1288,1289,1292],{},"Verify the exact path to QGIS's Python libraries using ",[28,1286,1287],{},"find \u002Fusr -name \"qgis\" -type d 2>\u002Fdev\u002Fnull"," (Unix) or ",[28,1290,1291],{},"dir \u002Fs \u002Fb qgis 2>nul"," (Windows) in the QGIS install tree.",[200,1294,1295,1296,1298],{},"Ensure the ",[28,1297,445],{}," file contains absolute paths, not relative ones.",[200,1300,1301,1302,1305],{},"Re-run ",[28,1303,1304],{},"python -c \"import sys; print(sys.path)\""," to confirm the QGIS paths appear in the list.",[241,1307,1309,1310],{"id":1308},"_2-oserror-winerror-126-the-specified-module-could-not-be-found","2. ",[28,1311,1312],{},"OSError: [WinError 126] The specified module could not be found",[15,1314,1315,1317,1318,1320,1321,1324,1325,509],{},[203,1316,1272],{},": Windows cannot locate GDAL or Qt DLLs because ",[28,1319,383],{}," does not include the QGIS ",[28,1322,1323],{},"bin"," directory.\n",[203,1326,1279],{},[249,1328,1329,1341,1344],{},[200,1330,1331,1332,1334,1335,1337,1338,425],{},"Add the QGIS ",[28,1333,1323],{}," folder to your system ",[28,1336,383],{}," before importing ",[28,1339,1340],{},"qgis",[200,1342,1343],{},"In your activation script, prepend the path exactly as shown in Step 4.",[200,1345,1346],{},"Restart the terminal after modifying environment variables.",[241,1348,1350],{"id":1349},"_3-architecture-mismatch-on-apple-silicon","3. Architecture Mismatch on Apple Silicon",[15,1352,1353,1355,1356,509],{},[203,1354,1272],{},": QGIS on macOS Silicon runs natively as ARM64, but some third-party Python wheels are compiled for x86_64. Mixing architectures causes segmentation faults during import.\n",[203,1357,1279],{},[249,1359,1360,1363],{},[200,1361,1362],{},"Always use the QGIS-bundled Python interpreter to create the venv. Do not use Homebrew or system Python.",[200,1364,1365,1366],{},"If installing additional packages, force architecture compatibility: ",[28,1367,1368],{},"arch -arm64 pip install \u003Cpackage>",[241,1370,1372,1373,1376],{"id":1371},"_4-qgsapplication-fails-to-find-crs-database","4. ",[28,1374,1375],{},"QgsApplication"," fails to find CRS database",[15,1378,1379,1381,1382,1384,1385,509],{},[203,1380,1272],{},": Missing ",[28,1383,546],{}," or incorrect value pointing to the wrong directory.\n",[203,1386,1279],{},[249,1388,1389,1402,1411],{},[200,1390,1391,1392,1395,1396,317,1399,321],{},"Print ",[28,1393,1394],{},"os.environ.get(\"QGIS_PREFIX_PATH\")"," to verify it points to the QGIS prefix (e.g., ",[28,1397,1398],{},"\u002FApplications\u002FQGIS.app\u002FContents\u002FMacOS",[28,1400,1401],{},"C:\\OSGeo4W\\apps\\qgis-ltr",[200,1403,1404,1405,317,1408,425],{},"On Linux, ensure the user has read access to ",[28,1406,1407],{},"\u002Fusr\u002Fshare\u002Fqgis",[28,1409,1410],{},"\u002Fopt\u002Fqgis\u002Fshare\u002Fqgis",[200,1412,1413,1414,1417],{},"Check that the ",[28,1415,1416],{},"resources\u002Fsrs.db"," file exists under the QGIS share directory.",[241,1419,1421],{"id":1420},"_5-gdalproj-version-conflicts","5. GDAL\u002FPROJ Version Conflicts",[15,1423,1424,1426,1427,1430,1431,1433,1434,509],{},[203,1425,1272],{},": Installing ",[28,1428,1429],{},"gdal"," via ",[28,1432,368],{}," in the virtual environment overwrites the QGIS-bundled version, breaking spatial transformations.\n",[203,1435,1279],{},[249,1437,1438,1445],{},[200,1439,1440,1441,1444],{},"Never run ",[28,1442,1443],{},"pip install gdal"," inside a PyQGIS virtual environment. QGIS already provides a compiled, version-locked GDAL.",[200,1446,1447,1448,1451],{},"If you need additional geospatial packages, use ",[28,1449,1450],{},"pip install --no-deps \u003Cpackage>"," to prevent dependency resolution from pulling incompatible C-extensions.",[189,1453,1455],{"id":1454},"best-practices-for-production-deployment","Best Practices for Production Deployment",[15,1457,1458],{},"Once your environment is stable, adopt these practices to maintain reliability across projects:",[249,1460,1461,1471,1487,1493],{},[200,1462,1463,1466,1467,1470],{},[203,1464,1465],{},"Freeze Dependencies",": Run ",[28,1468,1469],{},"pip freeze > requirements.txt"," only for packages you explicitly installed. Do not include QGIS core modules in your requirements file.",[200,1472,1473,1483,1484,1486],{},[203,1474,1475,1476,1478,1479,1482],{},"Use ",[28,1477,331],{}," Over ",[28,1480,1481],{},"conda"," for PyQGIS",": While Conda is excellent for data science, QGIS's Python distribution is tightly coupled with its C++ binaries. ",[28,1485,331],{}," preserves this linkage without introducing Conda's environment resolution overhead.",[200,1488,1489,1492],{},[203,1490,1491],{},"Isolate Per Project",": Never share a single virtual environment across multiple GIS projects. Different projects often require different plugin versions or CRS databases.",[200,1494,1495,1498,1499,317,1502,1505,1506,388,1508,1510],{},[203,1496,1497],{},"Document Path Overrides",": Keep a ",[28,1500,1501],{},"setup.sh",[28,1503,1504],{},"setup.bat"," in your repository that exports the correct ",[28,1507,546],{},[28,1509,557],{},". This ensures new developers or CI runners can bootstrap the environment with a single command.",[189,1512,1514],{"id":1513},"why-a-plain-virtualenv-is-not-enough","Why a plain virtualenv is not enough",[15,1516,1517,1518,1520],{},"The usual Python answer to dependency isolation — create a virtual environment, install what you need — breaks down for PyQGIS because the QGIS bindings are compiled C++ extensions that cannot be installed with ",[28,1519,368],{},". They exist only inside the QGIS installation, built against one specific Python version.",[15,1522,1523],{},[43,1524,1527,1530,1533,1536,1541,1547,1552,1557,1562,1580,1583,1586,1589,1592,1602,1605,1609,1612,1614],{"viewBox":1525,"role":46,"ariaLabel":1526,"xmlns":48},"0 0 760 254","Three environment strategies: a plain virtualenv that cannot see the bindings, one created with system site packages that can, and a container that bundles QGIS itself",[50,1528,1529],{},"Three ways to isolate dependencies around QGIS",[54,1531,1532],{},"A plain virtual environment built from a downloaded Python cannot import the QGIS bindings at all. A virtual environment created from the QGIS-bundled interpreter with system site packages enabled can import them while still isolating your own dependencies. A container image bundles a specific QGIS version with the environment, which is the only fully reproducible option.",[58,1534],{"x":60,"y":60,"width":61,"height":1535,"fill":63},"254",[75,1537,1540],{"x":62,"y":1538,"style":1539,"fill":80,"textAnchor":81},"26","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","pip cannot install the QGIS bindings — plan around that",[58,1542],{"x":1543,"y":94,"width":1544,"height":1545,"rx":70,"fill":71,"stroke":1546,"style":73},"16","236","192","#b91c1c",[75,1548,1551],{"x":143,"y":1549,"style":1550,"fill":1546,"textAnchor":81},"70","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","a plain virtualenv",[75,1553,1556],{"x":143,"y":1554,"style":1555,"fill":87,"textAnchor":81},"96","text-anchor:middle;font-size:10.5px;font-family:monospace","python -m venv .venv",[75,1558,1561],{"x":143,"y":1559,"style":1560,"fill":1546,"textAnchor":81},"130","text-anchor:middle;font-size:22px;font-weight:bold;font-family:sans-serif","✗",[1563,1564,1566,1571,1575],"g",{"style":1565},"font-size:10.5px;font-family:sans-serif",[75,1567,1570],{"x":143,"y":1568,"style":1569,"fill":87,"textAnchor":81},"164","text-anchor:middle","isolates your packages",[75,1572,1574],{"x":143,"y":1573,"style":1569,"fill":1546,"textAnchor":81},"186","cannot import qgis at all",[75,1576,1579],{"x":143,"y":1577,"style":1569,"fill":1578,"textAnchor":81},"212","#59645f","the default answer, and wrong",[58,1581],{"x":1582,"y":94,"width":1544,"height":1545,"rx":70,"fill":71,"stroke":128,"style":73},"262",[75,1584,1585],{"x":62,"y":1549,"style":1550,"fill":128,"textAnchor":81},"venv + system packages",[75,1587,135],{"x":62,"y":1554,"style":1588,"fill":87,"textAnchor":81},"text-anchor:middle;font-size:10px;font-family:monospace",[75,1590,1591],{"x":62,"y":1559,"style":1560,"fill":128,"textAnchor":81},"✓",[1563,1593,1594,1596,1599],{"style":1565},[75,1595,1570],{"x":62,"y":1568,"style":1569,"fill":87,"textAnchor":81},[75,1597,1598],{"x":62,"y":1573,"style":1569,"fill":128,"textAnchor":81},"sees the QGIS bindings",[75,1600,1601],{"x":62,"y":1577,"style":1569,"fill":1578,"textAnchor":81},"built from the bundled python",[58,1603],{"x":1604,"y":94,"width":1544,"height":1545,"rx":70,"fill":71,"stroke":106,"style":73},"508",[75,1606,1608],{"x":1607,"y":1549,"style":1550,"fill":106,"textAnchor":81},"626","a container image",[75,1610,1611],{"x":1607,"y":1554,"style":1555,"fill":87,"textAnchor":81},"FROM qgis\u002Fqgis:3.34",[75,1613,1591],{"x":1607,"y":1559,"style":1560,"fill":106,"textAnchor":81},[1563,1615,1616,1619,1622],{"style":1565},[75,1617,1618],{"x":1607,"y":1568,"style":1569,"fill":87,"textAnchor":81},"pins QGIS itself",[75,1620,1621],{"x":1607,"y":1573,"style":1569,"fill":106,"textAnchor":81},"fully reproducible",[75,1623,1624],{"x":1607,"y":1577,"style":1569,"fill":1578,"textAnchor":81},"the answer for CI and servers",[15,1626,1627,1628,1631,1632,1634,1635,1638],{},"The middle option is the pragmatic one for local work. Creating the environment ",[503,1629,1630],{},"from the QGIS-bundled interpreter"," keeps the ABI matched, and ",[28,1633,135],{}," lets it see the compiled bindings while your own ",[28,1636,1637],{},"pip install","s still land inside the environment rather than polluting the QGIS installation.",[15,1640,1641],{},"That last point matters more than it sounds. Installing packages directly into the QGIS Python is tempting and creates a machine nobody else can reproduce — and on a system-package install of QGIS it will be silently reverted by the next update. Keeping your dependencies in an environment you own means the QGIS installation stays exactly as the packager built it.",[15,1643,1644],{},"For anything that runs unattended, the container is the honest answer. It pins not just your Python dependencies but the QGIS version, the GDAL version and the PROJ transformation grids, all of which affect results. A pipeline that produces slightly different coordinates after a server update is a genuinely difficult problem to diagnose, and pinning the whole stack is the only thing that prevents it.",[189,1646,1648],{"id":1647},"key-takeaways","Key Takeaways",[249,1650,1651,1666,1684,1699,1723],{},[200,1652,1653,1656,1657,1659,1660,388,1662,1665],{},[203,1654,1655],{},"Build the environment on the QGIS-bundled interpreter",", or create it with ",[28,1658,135],{},", so it can see the compiled ",[28,1661,1048],{},[28,1663,1664],{},"qgis.gui"," bindings. A venv from a different Python minor version will fail to import them.",[200,1667,1668,1671,1672,1674,1675,388,1677,1679,1680,1683],{},[203,1669,1670],{},"Map QGIS paths explicitly"," with a ",[28,1673,453],{}," file of absolute paths plus ",[28,1676,546],{},[28,1678,383],{}," exports in your activation script. Without them, imports fail or the CRS database (",[28,1681,1682],{},"srs.db",") cannot be found.",[200,1685,1686,1694,1695,1698],{},[203,1687,1688,1689,317,1691],{},"Never ",[28,1690,1443],{},[28,1692,1693],{},"pip install pyproj"," into a PyQGIS environment — QGIS ships version-locked, compiled builds, and shadowing them breaks spatial transformations. Reserve pip for pure-Python packages, using ",[28,1696,1697],{},"--no-deps"," when a package might pull conflicting C extensions.",[200,1700,1701,1711,1712,1714,1715,1718,1719,1722],{},[203,1702,1703,1704,1707,1708],{},"Always call ",[28,1705,1706],{},"setPrefixPath()"," before ",[28,1709,1710],{},"initQgis()"," and pair ",[28,1713,1710],{}," with ",[28,1716,1717],{},"exitQgis()"," for clean headless runs — this is what lets you ",[22,1720,1721],{"href":1095},"run Python scripts outside QGIS Desktop"," in ETL and CI pipelines.",[200,1724,1725,1728,1729,1731,1732,1734],{},[203,1726,1727],{},"Isolate one environment per project"," and commit a ",[28,1730,1501],{},"\u002F",[28,1733,1504],{}," that exports the paths, so teammates and CI runners reproduce it in one command.",[15,1736,1737],{},"Together these habits turn PyQGIS development from a fragile, system-dependent process into a reproducible workflow that scales from a single script to enterprise geoprocessing pipelines, and stays robust across operating systems, QGIS updates, and team collaborations.",[189,1739,1741],{"id":1740},"frequently-asked-questions","Frequently Asked Questions",[15,1743,1744,1751,1752,1754,1755,388,1757,1759,1760,1762,1763,1765],{},[203,1745,1746,1747,1750],{},"Why can't I just ",[28,1748,1749],{},"pip install qgis"," into a regular virtual environment?","\nThe ",[28,1753,1340],{}," package on PyPI is a documentation stub, not the actual bindings. The real ",[28,1756,1048],{},[28,1758,1664],{}," modules are compiled C++ extensions shipped only with the QGIS installer. You must reference QGIS's bundled Python paths via a ",[28,1761,445],{}," file or ",[28,1764,135],{},", rather than installing them from PyPI.",[15,1767,1768,1771,1772,1774,1775,425],{},[203,1769,1770],{},"Should I create the venv with the QGIS Python or with my system Python?","\nAlways base it on the QGIS-bundled interpreter (or use ",[28,1773,135],{}," so the venv can see QGIS's compiled bindings). The PyQGIS bindings are compiled against a specific Python ABI, so a venv built from a different minor version (for example system Python 3.11 against QGIS 3.34's Python 3.12) will fail to import ",[28,1776,1048],{},[15,1778,1779,1787,1788,1790],{},[203,1780,1781,1782,317,1784,1786],{},"Is it safe to ",[28,1783,1443],{},[28,1785,1693],{}," inside the environment?","\nNo. QGIS already ships compiled, version-locked GDAL and PROJ. Installing them from pip overwrites or shadows those binaries and breaks spatial transformations. Limit pip installs to pure-Python packages, and use ",[28,1789,1450],{}," when a dependency might pull in conflicting C extensions.",[15,1792,1793,1796,1797,1799,1800,1802,1803,317,1805,1807,1808,1810,1811,425],{},[203,1794,1795],{},"Why does my script raise a CRS database error even though imports succeed?","\nThis almost always means ",[28,1798,546],{}," is unset or points to the wrong directory, so QGIS cannot find ",[28,1801,1682],{},". Verify it points to the QGIS prefix (for example ",[28,1804,1401],{},[28,1806,1398],{},") and that ",[28,1809,1706],{}," is called before ",[28,1812,1710],{},[15,1814,1815,1821,1822,1824,1825,1828],{},[203,1816,1817,1818,1820],{},"Should I use ",[28,1819,331],{}," or Conda for PyQGIS work?","\nFor replicating an existing desktop QGIS install, ",[28,1823,331],{}," is preferred because it preserves the tight coupling between QGIS's Python and its C++ binaries. Conda is a reasonable fallback when you need a fully self-contained install (for example on a headless CI runner) via ",[28,1826,1827],{},"conda install -c conda-forge qgis",", but mixing a Conda QGIS with a desktop QGIS on the same machine invites path conflicts.",[15,1830,1831,1834],{},[203,1832,1833],{},"Can I pip install PyQGIS into a virtual environment?","\nNo. The bindings are compiled against a specific Python and ship only inside QGIS, so there is no installable package. Create the environment from the QGIS-bundled interpreter with system site packages enabled, which lets it see the bindings while keeping your own dependencies separate.",[15,1836,1837,1840],{},[203,1838,1839],{},"Is it safe to install packages into the QGIS Python directly?","\nIt works and it is a poor idea. On a system-package installation the changes are reverted by the next QGIS update, and on any installation it produces a machine nobody else can reproduce. Keep your dependencies in an environment you control.",[15,1842,1843,1846],{},[203,1844,1845],{},"How do I make results reproducible across machines?","\nPin the whole stack, not just the Python packages. QGIS, GDAL and the PROJ transformation grids all affect numeric output, which is why a container image built from a tagged QGIS release is the only fully reproducible option for scheduled work.",[15,1848,1849,1852,1853,1856],{},[203,1850,1851],{},"Which Python should the environment be created from?","\nThe one QGIS itself uses. Confirm it with ",[28,1854,1855],{},"python3 -c \"import sys; print(sys.executable)\""," inside the QGIS Python Console, and build the environment from exactly that path.",[15,1858,1859,1862,1863,1865],{},[203,1860,1861],{},"How do I check which interpreter a script is actually using?","\nPrint ",[28,1864,746],{}," at the top of the script. It is the single most useful diagnostic line in PyQGIS environment work, because almost every import problem reduces to the answer being different from what you assumed.",[15,1867,1868,1871],{},[203,1869,1870],{},"Do plugins run in my virtual environment?","\nNo. Plugins run inside the QGIS process using the QGIS interpreter, so a dependency installed only in your environment will not be importable from a plugin. Vendor small pure-Python dependencies inside the plugin package, or document the installation step.",[15,1873,1874,1877],{},[203,1875,1876],{},"Is conda a reasonable way to manage this?","\nYes — the conda-forge QGIS package installs QGIS and its Python together in one environment, which sidesteps the bindings problem entirely. The trade-off is that the QGIS build is conda's rather than your distribution's, which occasionally matters for plugin compatibility.",[15,1879,1880,1883],{},[203,1881,1882],{},"Should the environment live inside the project directory?","\nIt is convenient and easy to recreate, provided the directory is excluded from version control and from any plugin package you build. A stray environment inside a plugin ZIP is a large upload and an immediate review rejection.",[189,1885,1887],{"id":1886},"related","Related",[249,1889,1890,1898,1906,1911,1915,1919,1925,1932],{},[200,1891,1892,1895,1896],{},[203,1893,1894],{},"Start here:"," ",[22,1897,25],{"href":24},[200,1899,1900,1895,1903],{},[203,1901,1902],{},"Goes deeper:",[22,1904,1905],{"href":1095},"Running Python Scripts Outside QGIS Desktop",[200,1907,1908],{},[22,1909,1910],{"href":39},"Setting Up PyCharm for QGIS Development",[200,1912,1913],{},[22,1914,225],{"href":34},[200,1916,1917],{},[22,1918,1108],{"href":1107},[200,1920,1921],{},[22,1922,1924],{"href":1923},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-api-architecture\u002F","QGIS API Architecture",[200,1926,1927,1931],{},[22,1928,1930],{"href":1929},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Finstall-python-packages-into-qgis\u002F","Install Python Packages into the QGIS Environment"," — add a library to the Python QGIS actually uses, without breaking numpy or GDAL.",[200,1933,1934,1938],{},[22,1935,1937],{"href":1936},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Fuse-pyqgis-in-jupyter-notebook\u002F","Use PyQGIS in a Jupyter Notebook"," — run QGIS from a notebook and render maps inline beside your data frames.",[1940,1941,1942],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}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 pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}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 .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}",{"title":296,"searchDepth":533,"depth":533,"links":1944},[1945,1946,1952,1955,1965,1966,1967,1968,1969],{"id":191,"depth":533,"text":192},{"id":235,"depth":533,"text":236,"children":1947},[1948,1949,1950,1951],{"id":243,"depth":563,"text":244},{"id":324,"depth":563,"text":325},{"id":376,"depth":563,"text":377},{"id":428,"depth":563,"text":429},{"id":638,"depth":533,"text":639,"children":1953},[1954],{"id":1036,"depth":563,"text":1037},{"id":1100,"depth":533,"text":1101,"children":1956},[1957,1959,1961,1962,1964],{"id":1263,"depth":563,"text":1958},"1. ModuleNotFoundError: No module named 'qgis.core'",{"id":1308,"depth":563,"text":1960},"2. OSError: [WinError 126] The specified module could not be found",{"id":1349,"depth":563,"text":1350},{"id":1371,"depth":563,"text":1963},"4. QgsApplication fails to find CRS database",{"id":1420,"depth":563,"text":1421},{"id":1454,"depth":533,"text":1455},{"id":1513,"depth":533,"text":1514},{"id":1647,"depth":533,"text":1648},{"id":1740,"depth":533,"text":1741},{"id":1886,"depth":533,"text":1887},"Set up isolated Python virtual environments for QGIS and PyQGIS. Manage dependencies, avoid conflicts, and run standalone scripts outside QGIS desktop.","md",{"slug":1973,"type":1974,"breadcrumb":1975,"datePublished":1976,"dateModified":1977},"virtual-environments-for-gis","guide","Virtual Environments","2025-02-14","2026-07-18","\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis",{"title":5,"description":1970},"pyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Findex","xosI5IICS3fOz1rjqPwWK7qAjaRjgWXKz1FoO3oMYOM",1787823360565]