[{"data":1,"prerenderedAt":1430},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Fuse-pyqgis-with-conda-and-mamba":3},{"id":4,"title":5,"body":6,"description":1420,"extension":1421,"meta":1422,"navigation":354,"path":1426,"seo":1427,"stem":1428,"__hash__":1429},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Fuse-pyqgis-with-conda-and-mamba\u002Findex.md","Use PyQGIS with Conda and Mamba",{"type":7,"value":8,"toc":1406},"minimark",[9,13,17,26,174,179,202,206,209,277,294,314,318,321,498,523,527,530,624,649,656,766,770,773,1106,1111,1115,1118,1136,1146,1160,1164,1172,1175,1179,1186,1260,1264,1318,1322,1328,1332,1341,1347,1358,1369,1373,1402],[10,11,5],"h1",{"id":12},"use-pyqgis-with-conda-and-mamba",[14,15,16],"p",{},"The bundled QGIS Python is convenient and inflexible: one version, one set of packages, shared with the desktop application. A conda environment inverts that — QGIS becomes a package like any other, several versions can coexist, and the whole thing is reproducible from a text file. For anything that has to run on a build server, in a container or alongside a scientific stack, it is the arrangement that causes the least trouble.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002F","Virtual Environments for GIS in PyQGIS",". It covers creating a QGIS environment from conda-forge, pinning versions so the environment is reproducible, the activation details that differ per platform, and where the conda route is and is not appropriate.",[14,27,28],{},[29,30,35,39,43,50,59,69,75,84,90,98,104,107,111,116,120,125,129,134,140,145,149,152,156,159,162,166,170],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 316","img","A system QGIS install sharing one Python with the desktop application, compared with conda environments each holding their own QGIS version and dependencies","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"One shared Python, or one per project",[40,41,42],"desc",{},"A system install ships one Python interpreter with QGIS bound to it, shared by the desktop application and any script. Conda environments each contain their own QGIS, GDAL, PROJ and Python, so two projects can use different versions at once and neither can disturb the other or the desktop install.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","316","#f6f3ea",[51,52,58],"text",{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Installing a package should not be able to break the desktop",[44,60],{"x":61,"y":62,"width":63,"height":64,"rx":65,"fill":66,"stroke":67,"style":68},"24","56","330","224","10","#fdf2e2","#b45309","stroke-width:2.5",[51,70,74],{"x":71,"y":72,"style":73,"fill":67,"textAnchor":57},"189","84","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","system install",[44,76],{"x":77,"y":78,"width":79,"height":80,"rx":81,"fill":82,"stroke":67,"style":83},"52","102","274","46","6","#fffdf7","stroke-width:1.8",[51,85,89],{"x":71,"y":86,"style":87,"fill":88,"textAnchor":57},"130","text-anchor:middle;font-size:10.5px;font-family:sans-serif","#2f3b35","QGIS 3.34 · Python 3.12 · GDAL 3.8",[44,91],{"x":77,"y":92,"width":86,"height":93,"rx":94,"fill":95,"stroke":96,"style":97},"160","42","5","#efeadd","#59645f","stroke-width:1.5",[51,99,103],{"x":100,"y":101,"style":102,"fill":88,"textAnchor":57},"117","186","text-anchor:middle;font-size:10px;font-family:sans-serif","the desktop app",[44,105],{"x":106,"y":92,"width":86,"height":93,"rx":94,"fill":95,"stroke":96,"style":97},"196",[51,108,110],{"x":109,"y":101,"style":102,"fill":88,"textAnchor":57},"261","your scripts",[51,112,115],{"x":71,"y":113,"style":87,"fill":114,"textAnchor":57},"234","#b91c1c","one pip install away from",[51,117,119],{"x":71,"y":118,"style":87,"fill":114,"textAnchor":57},"254","breaking both at once",[44,121],{"x":122,"y":62,"width":63,"height":64,"rx":65,"fill":123,"stroke":124,"style":68},"406","#edf8e9","#15803d",[51,126,128],{"x":127,"y":72,"style":73,"fill":124,"textAnchor":57},"571","conda environments",[44,130],{"x":131,"y":78,"width":86,"height":132,"rx":81,"fill":133,"stroke":124,"style":83},"430","70","#e8efe6",[51,135,139],{"x":136,"y":137,"style":138,"fill":88,"textAnchor":57},"495","126","text-anchor:middle;font-size:10px;font-family:monospace","qgis-334",[51,141,144],{"x":136,"y":142,"style":143,"fill":88,"textAnchor":57},"146","text-anchor:middle;font-size:9.5px;font-family:sans-serif","QGIS 3.34",[51,146,148],{"x":136,"y":147,"style":143,"fill":88,"textAnchor":57},"162","GDAL 3.8",[44,150],{"x":151,"y":78,"width":86,"height":132,"rx":81,"fill":133,"stroke":124,"style":83},"582",[51,153,155],{"x":154,"y":137,"style":138,"fill":88,"textAnchor":57},"647","qgis-340",[51,157,158],{"x":154,"y":142,"style":143,"fill":88,"textAnchor":57},"QGIS 3.40",[51,160,161],{"x":154,"y":147,"style":143,"fill":88,"textAnchor":57},"GDAL 3.9",[44,163],{"x":131,"y":164,"width":165,"height":93,"rx":94,"fill":82,"stroke":124,"style":97},"184","282",[51,167,169],{"x":127,"y":168,"style":102,"fill":88,"textAnchor":57},"210","the desktop install, untouched",[51,171,173],{"x":127,"y":172,"style":87,"fill":124,"textAnchor":57},"252","reproducible from environment.yml",[175,176,178],"h2",{"id":177},"prerequisites","Prerequisites",[180,181,182,196,199],"ul",{},[183,184,185,186,190,191,195],"li",{},"A conda distribution. ",[187,188,189],"strong",{},"Miniforge"," is the one to install: it defaults to conda-forge, which is where the QGIS packages live, and it ships ",[192,193,194],"code",{},"mamba"," as the solver.",[183,197,198],{},"Roughly 3 GB of disk per environment. QGIS pulls in Qt, GDAL, PROJ, GEOS and a large dependency tree.",[183,200,201],{},"On Linux, a working X or Wayland display only if you intend to open the GUI; headless use needs none.",[175,203,205],{"id":204},"create-the-environment","Create the environment",[14,207,208],{},"One command, and the version pin is the important part of it.",[210,211,216],"pre",{"className":212,"code":213,"language":214,"meta":215,"style":215},"language-bash shiki shiki-themes github-dark","mamba create -n qgis-334 -c conda-forge \"qgis=3.34.*\" python=3.12 -y\nmamba activate qgis-334\npython -c \"import qgis.core; print(qgis.core.Qgis.QGIS_VERSION)\"\n","bash","",[192,217,218,255,266],{"__ignoreMap":215},[219,220,223,226,230,234,237,240,243,246,249,252],"span",{"class":221,"line":222},"line",1,[219,224,194],{"class":225},"svObZ",[219,227,229],{"class":228},"sU2Wk"," create",[219,231,233],{"class":232},"sDLfK"," -n",[219,235,236],{"class":228}," qgis-334",[219,238,239],{"class":232}," -c",[219,241,242],{"class":228}," conda-forge",[219,244,245],{"class":228}," \"qgis=3.34.*\"",[219,247,248],{"class":228}," python=",[219,250,251],{"class":232},"3.12",[219,253,254],{"class":232}," -y\n",[219,256,258,260,263],{"class":221,"line":257},2,[219,259,194],{"class":225},[219,261,262],{"class":228}," activate",[219,264,265],{"class":228}," qgis-334\n",[219,267,269,272,274],{"class":221,"line":268},3,[219,270,271],{"class":225},"python",[219,273,239],{"class":232},[219,275,276],{"class":228}," \"import qgis.core; print(qgis.core.Qgis.QGIS_VERSION)\"\n",[14,278,279,282,283,286,287,289,290,293],{},[187,280,281],{},"Breakdown:"," Pinning to ",[192,284,285],{},"3.34.*"," gets the LTR line and its patch releases rather than whatever is newest, which is what makes the environment stable over months. Pinning Python explicitly avoids the solver choosing a different interpreter on a later rebuild and silently changing behaviour. ",[192,288,194],{}," is a drop-in replacement for ",[192,291,292],{},"conda"," with a much faster solver — on a dependency tree this size the difference is minutes. Verifying the import immediately is worth the extra line, because a solve that succeeded does not guarantee the Qt libraries load.",[14,295,296,297,300,301,304,305,308,309,313],{},"Note there is no need to set ",[192,298,299],{},"PYTHONPATH"," or ",[192,302,303],{},"QGIS_PREFIX_PATH"," by hand. The conda package installs the Python bindings into the environment's ",[192,306,307],{},"site-packages"," and sets the prefix through activation scripts, which is the main practical advantage over pointing a virtualenv at a system install as described in ",[21,310,312],{"href":311},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Frunning-python-scripts-outside-qgis-desktop\u002F","running Python scripts outside QGIS Desktop",".",[175,315,317],{"id":316},"initialise-qgis-in-a-script","Initialise QGIS in a script",[14,319,320],{},"Inside the environment the standalone initialisation is the ordinary one.",[210,322,325],{"className":323,"code":324,"language":271,"meta":215,"style":215},"language-python shiki shiki-themes github-dark","import os\nfrom qgis.core import QgsApplication\n\nos.environ.setdefault(\"QT_QPA_PLATFORM\", \"offscreen\")\n\nQgsApplication.setPrefixPath(os.environ[\"CONDA_PREFIX\"], True)\napp = QgsApplication([], False)\napp.initQgis()\n\nfrom qgis.analysis import QgsNativeAlgorithms\nimport processing\nfrom processing.core.Processing import Processing\n\nProcessing.initialize()\nQgsApplication.processingRegistry().addProvider(QgsNativeAlgorithms())\n\n# ... work ...\n\napp.exitQgis()\n",[192,326,327,337,350,356,374,379,396,413,419,424,437,445,458,463,469,475,480,487,492],{"__ignoreMap":215},[219,328,329,333],{"class":221,"line":222},[219,330,332],{"class":331},"snl16","import",[219,334,336],{"class":335},"s95oV"," os\n",[219,338,339,342,345,347],{"class":221,"line":257},[219,340,341],{"class":331},"from",[219,343,344],{"class":335}," qgis.core ",[219,346,332],{"class":331},[219,348,349],{"class":335}," QgsApplication\n",[219,351,352],{"class":221,"line":268},[219,353,355],{"emptyLinePlaceholder":354},true,"\n",[219,357,359,362,365,368,371],{"class":221,"line":358},4,[219,360,361],{"class":335},"os.environ.setdefault(",[219,363,364],{"class":228},"\"QT_QPA_PLATFORM\"",[219,366,367],{"class":335},", ",[219,369,370],{"class":228},"\"offscreen\"",[219,372,373],{"class":335},")\n",[219,375,377],{"class":221,"line":376},5,[219,378,355],{"emptyLinePlaceholder":354},[219,380,382,385,388,391,394],{"class":221,"line":381},6,[219,383,384],{"class":335},"QgsApplication.setPrefixPath(os.environ[",[219,386,387],{"class":228},"\"CONDA_PREFIX\"",[219,389,390],{"class":335},"], ",[219,392,393],{"class":232},"True",[219,395,373],{"class":335},[219,397,399,402,405,408,411],{"class":221,"line":398},7,[219,400,401],{"class":335},"app ",[219,403,404],{"class":331},"=",[219,406,407],{"class":335}," QgsApplication([], ",[219,409,410],{"class":232},"False",[219,412,373],{"class":335},[219,414,416],{"class":221,"line":415},8,[219,417,418],{"class":335},"app.initQgis()\n",[219,420,422],{"class":221,"line":421},9,[219,423,355],{"emptyLinePlaceholder":354},[219,425,427,429,432,434],{"class":221,"line":426},10,[219,428,341],{"class":331},[219,430,431],{"class":335}," qgis.analysis ",[219,433,332],{"class":331},[219,435,436],{"class":335}," QgsNativeAlgorithms\n",[219,438,440,442],{"class":221,"line":439},11,[219,441,332],{"class":331},[219,443,444],{"class":335}," processing\n",[219,446,448,450,453,455],{"class":221,"line":447},12,[219,449,341],{"class":331},[219,451,452],{"class":335}," processing.core.Processing ",[219,454,332],{"class":331},[219,456,457],{"class":335}," Processing\n",[219,459,461],{"class":221,"line":460},13,[219,462,355],{"emptyLinePlaceholder":354},[219,464,466],{"class":221,"line":465},14,[219,467,468],{"class":335},"Processing.initialize()\n",[219,470,472],{"class":221,"line":471},15,[219,473,474],{"class":335},"QgsApplication.processingRegistry().addProvider(QgsNativeAlgorithms())\n",[219,476,478],{"class":221,"line":477},16,[219,479,355],{"emptyLinePlaceholder":354},[219,481,483],{"class":221,"line":482},17,[219,484,486],{"class":485},"sjoCn","# ... work ...\n",[219,488,490],{"class":221,"line":489},18,[219,491,355],{"emptyLinePlaceholder":354},[219,493,495],{"class":221,"line":494},19,[219,496,497],{"class":335},"app.exitQgis()\n",[14,499,500,502,503,506,507,510,511,514,515,518,519,522],{},[187,501,281],{}," ",[192,504,505],{},"CONDA_PREFIX"," is set by activation and points at the environment root, which is exactly where the conda package puts QGIS's resources — so the prefix path needs no hard-coded location. ",[192,508,509],{},"QT_QPA_PLATFORM=offscreen"," is what lets the whole thing run on a machine with no display, which is the usual reason for choosing conda in the first place; without it, Qt aborts on a headless server with a message about not finding a display. Importing ",[192,512,513],{},"processing"," before ",[192,516,517],{},"Processing.initialize()"," is the required order, and the native algorithm provider must be added explicitly or ",[192,520,521],{},"processing.run"," finds nothing.",[175,524,526],{"id":525},"make-the-environment-reproducible","Make the environment reproducible",[14,528,529],{},"An environment created by hand is not reproducible. An environment file is.",[210,531,535],{"className":532,"code":533,"language":534,"meta":215,"style":215},"language-yaml shiki shiki-themes github-dark","name: qgis-334\nchannels:\n  - conda-forge\ndependencies:\n  - python=3.12\n  - qgis=3.34.*\n  - gdal=3.8.*\n  - pytest=8.*\n  - pip\n  - pip:\n      - pytest-qgis\n","yaml",[192,536,537,549,557,565,572,579,586,593,600,607,616],{"__ignoreMap":215},[219,538,539,543,546],{"class":221,"line":222},[219,540,542],{"class":541},"s4JwU","name",[219,544,545],{"class":335},": ",[219,547,548],{"class":228},"qgis-334\n",[219,550,551,554],{"class":221,"line":257},[219,552,553],{"class":541},"channels",[219,555,556],{"class":335},":\n",[219,558,559,562],{"class":221,"line":268},[219,560,561],{"class":335},"  - ",[219,563,564],{"class":228},"conda-forge\n",[219,566,567,570],{"class":221,"line":358},[219,568,569],{"class":541},"dependencies",[219,571,556],{"class":335},[219,573,574,576],{"class":221,"line":376},[219,575,561],{"class":335},[219,577,578],{"class":228},"python=3.12\n",[219,580,581,583],{"class":221,"line":381},[219,582,561],{"class":335},[219,584,585],{"class":228},"qgis=3.34.*\n",[219,587,588,590],{"class":221,"line":398},[219,589,561],{"class":335},[219,591,592],{"class":228},"gdal=3.8.*\n",[219,594,595,597],{"class":221,"line":415},[219,596,561],{"class":335},[219,598,599],{"class":228},"pytest=8.*\n",[219,601,602,604],{"class":221,"line":421},[219,603,561],{"class":335},[219,605,606],{"class":228},"pip\n",[219,608,609,611,614],{"class":221,"line":426},[219,610,561],{"class":335},[219,612,613],{"class":541},"pip",[219,615,556],{"class":335},[219,617,618,621],{"class":221,"line":439},[219,619,620],{"class":335},"      - ",[219,622,623],{"class":228},"pytest-qgis\n",[14,625,626,628,629,632,633,636,637,640,641,644,645,648],{},[187,627,281],{}," Listing ",[192,630,631],{},"gdal"," explicitly alongside ",[192,634,635],{},"qgis"," looks redundant — QGIS depends on it — but it pins the version the solver may otherwise move, and GDAL version changes are the most common cause of a workflow behaving differently between machines. The ",[192,638,639],{},"pip:"," section handles packages not on conda-forge; keeping it short matters, because mixing pip and conda in one environment is where dependency resolution stops being reliable. Recreating is then ",[192,642,643],{},"mamba env create -f environment.yml",", and ",[192,646,647],{},"mamba env export --from-history > environment.yml"," regenerates the file from what was actually requested rather than from the full resolved tree.",[14,650,651,652,655],{},"For a genuinely locked environment, ",[192,653,654],{},"conda-lock"," produces a platform-specific lock file with exact builds — the difference between \"the same requests\" and \"the same bytes\", and worth the extra tool for anything running in CI.",[14,657,658],{},[29,659,662,665,668,671,686,689,694,699,703,707,711,715,719,722,725,728,731,734,737,740,743,746,749,752,755,758,761],{"viewBox":660,"role":32,"ariaLabel":661,"xmlns":34},"0 0 760 288","Three levels of reproducibility: an environment created by hand, one described by an environment file, and one pinned by a lock file with exact builds",[36,663,664],{},"Three levels of reproducible",[40,666,667],{},"An environment created by typing commands cannot be recreated reliably. An environment file records the requested packages and version ranges, so a rebuild gets the same intent but possibly different builds. A lock file records exact package builds per platform, so a rebuild gets identical bytes.",[44,669],{"x":46,"y":46,"width":47,"height":670,"fill":49},"288",[672,673,674],"defs",{},[675,676,682],"marker",{"id":677,"viewBox":678,"refX":679,"refY":94,"markerWidth":680,"markerHeight":680,"orient":681},"repArrow","0 0 10 10","8","7","auto-start-reverse",[683,684],"path",{"d":685,"fill":88},"M0 0 L10 5 L0 10 z",[51,687,688],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"How much of \"it works on my machine\" you want to remove",[44,690],{"x":691,"y":62,"width":692,"height":693,"rx":65,"fill":66,"stroke":114,"style":68},"20","228","180",[51,695,698],{"x":696,"y":72,"style":697,"fill":114,"textAnchor":57},"134","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","typed by hand",[51,700,702],{"x":696,"y":701,"style":87,"fill":88,"textAnchor":57},"112","mamba create …",[51,704,706],{"x":696,"y":705,"style":87,"fill":88,"textAnchor":57},"136","then a few installs",[51,708,710],{"x":696,"y":709,"style":87,"fill":114,"textAnchor":57},"164","nothing records what",[51,712,714],{"x":696,"y":713,"style":87,"fill":114,"textAnchor":57},"182","was actually asked for",[51,716,718],{"x":696,"y":717,"style":102,"fill":88,"textAnchor":57},"212","fine for an experiment",[44,720],{"x":721,"y":62,"width":692,"height":693,"rx":65,"fill":66,"stroke":67,"style":68},"266",[51,723,724],{"x":53,"y":72,"style":697,"fill":67,"textAnchor":57},"environment.yml",[51,726,727],{"x":53,"y":701,"style":87,"fill":88,"textAnchor":57},"requested packages",[51,729,730],{"x":53,"y":705,"style":87,"fill":88,"textAnchor":57},"and version ranges",[51,732,733],{"x":53,"y":709,"style":87,"fill":67,"textAnchor":57},"same intent, possibly",[51,735,736],{"x":53,"y":713,"style":87,"fill":67,"textAnchor":57},"different builds",[51,738,739],{"x":53,"y":717,"style":102,"fill":88,"textAnchor":57},"right for most projects",[44,741],{"x":742,"y":62,"width":692,"height":693,"rx":65,"fill":123,"stroke":124,"style":68},"512",[51,744,654],{"x":745,"y":72,"style":697,"fill":124,"textAnchor":57},"626",[51,747,748],{"x":745,"y":701,"style":87,"fill":88,"textAnchor":57},"exact builds",[51,750,751],{"x":745,"y":705,"style":87,"fill":88,"textAnchor":57},"per platform",[51,753,754],{"x":745,"y":709,"style":87,"fill":124,"textAnchor":57},"identical bytes",[51,756,757],{"x":745,"y":713,"style":87,"fill":124,"textAnchor":57},"on every rebuild",[51,759,760],{"x":745,"y":717,"style":102,"fill":88,"textAnchor":57},"right for CI",[51,762,765],{"x":53,"y":763,"style":764,"fill":96,"textAnchor":57},"270","text-anchor:middle;font-size:11px;font-family:sans-serif","the cost rises left to right; so does the number of arguments avoided",[175,767,769],{"id":768},"a-smoke-test-worth-keeping","A smoke test worth keeping",[14,771,772],{},"Environments break silently. A GDAL upgrade changes a driver, a Qt build lands without the offscreen plugin, a solve quietly moves PROJ and every transform shifts. A short script run at the end of every environment build catches all three in about two seconds.",[210,774,776],{"className":323,"code":775,"language":271,"meta":215,"style":215},"import os\nfrom qgis.core import (\n    QgsApplication, QgsVectorLayer, QgsCoordinateReferenceSystem,\n    QgsCoordinateTransform, QgsCoordinateTransformContext, QgsPointXY, Qgis,\n)\n\nos.environ.setdefault(\"QT_QPA_PLATFORM\", \"offscreen\")\nQgsApplication.setPrefixPath(os.environ[\"CONDA_PREFIX\"], True)\napp = QgsApplication([], False)\napp.initQgis()\n\nprint(\"QGIS   \", Qgis.QGIS_VERSION)\n\nmemory = QgsVectorLayer(\"Point?crs=EPSG:4326\", \"smoke\", \"memory\")\nassert memory.isValid(), \"memory provider unavailable\"\n\nsrc = QgsCoordinateReferenceSystem(\"EPSG:4326\")\ndst = QgsCoordinateReferenceSystem(\"EPSG:3857\")\ntransform = QgsCoordinateTransform(src, dst, QgsCoordinateTransformContext())\npoint = transform.transform(QgsPointXY(-2.0, 52.0))\nassert abs(point.x() + 222638.98) \u003C 1.0, f\"projection maths looks wrong: {point.x()}\"\n\nfrom processing.core.Processing import Processing\nfrom qgis.analysis import QgsNativeAlgorithms\nProcessing.initialize()\nQgsApplication.processingRegistry().addProvider(QgsNativeAlgorithms())\nassert QgsApplication.processingRegistry().algorithmById(\"native:buffer\"), \"no native algorithms\"\n\nprint(\"smoke test passed\")\napp.exitQgis()\n",[192,777,778,784,795,800,805,809,813,825,837,849,853,857,876,880,905,916,920,935,949,959,984,1030,1035,1046,1057,1062,1067,1084,1089,1101],{"__ignoreMap":215},[219,779,780,782],{"class":221,"line":222},[219,781,332],{"class":331},[219,783,336],{"class":335},[219,785,786,788,790,792],{"class":221,"line":257},[219,787,341],{"class":331},[219,789,344],{"class":335},[219,791,332],{"class":331},[219,793,794],{"class":335}," (\n",[219,796,797],{"class":221,"line":268},[219,798,799],{"class":335},"    QgsApplication, QgsVectorLayer, QgsCoordinateReferenceSystem,\n",[219,801,802],{"class":221,"line":358},[219,803,804],{"class":335},"    QgsCoordinateTransform, QgsCoordinateTransformContext, QgsPointXY, Qgis,\n",[219,806,807],{"class":221,"line":376},[219,808,373],{"class":335},[219,810,811],{"class":221,"line":381},[219,812,355],{"emptyLinePlaceholder":354},[219,814,815,817,819,821,823],{"class":221,"line":398},[219,816,361],{"class":335},[219,818,364],{"class":228},[219,820,367],{"class":335},[219,822,370],{"class":228},[219,824,373],{"class":335},[219,826,827,829,831,833,835],{"class":221,"line":415},[219,828,384],{"class":335},[219,830,387],{"class":228},[219,832,390],{"class":335},[219,834,393],{"class":232},[219,836,373],{"class":335},[219,838,839,841,843,845,847],{"class":221,"line":421},[219,840,401],{"class":335},[219,842,404],{"class":331},[219,844,407],{"class":335},[219,846,410],{"class":232},[219,848,373],{"class":335},[219,850,851],{"class":221,"line":426},[219,852,418],{"class":335},[219,854,855],{"class":221,"line":439},[219,856,355],{"emptyLinePlaceholder":354},[219,858,859,862,865,868,871,874],{"class":221,"line":447},[219,860,861],{"class":232},"print",[219,863,864],{"class":335},"(",[219,866,867],{"class":228},"\"QGIS   \"",[219,869,870],{"class":335},", Qgis.",[219,872,873],{"class":232},"QGIS_VERSION",[219,875,373],{"class":335},[219,877,878],{"class":221,"line":460},[219,879,355],{"emptyLinePlaceholder":354},[219,881,882,885,887,890,893,895,898,900,903],{"class":221,"line":465},[219,883,884],{"class":335},"memory ",[219,886,404],{"class":331},[219,888,889],{"class":335}," QgsVectorLayer(",[219,891,892],{"class":228},"\"Point?crs=EPSG:4326\"",[219,894,367],{"class":335},[219,896,897],{"class":228},"\"smoke\"",[219,899,367],{"class":335},[219,901,902],{"class":228},"\"memory\"",[219,904,373],{"class":335},[219,906,907,910,913],{"class":221,"line":471},[219,908,909],{"class":331},"assert",[219,911,912],{"class":335}," memory.isValid(), ",[219,914,915],{"class":228},"\"memory provider unavailable\"\n",[219,917,918],{"class":221,"line":477},[219,919,355],{"emptyLinePlaceholder":354},[219,921,922,925,927,930,933],{"class":221,"line":482},[219,923,924],{"class":335},"src ",[219,926,404],{"class":331},[219,928,929],{"class":335}," QgsCoordinateReferenceSystem(",[219,931,932],{"class":228},"\"EPSG:4326\"",[219,934,373],{"class":335},[219,936,937,940,942,944,947],{"class":221,"line":489},[219,938,939],{"class":335},"dst ",[219,941,404],{"class":331},[219,943,929],{"class":335},[219,945,946],{"class":228},"\"EPSG:3857\"",[219,948,373],{"class":335},[219,950,951,954,956],{"class":221,"line":494},[219,952,953],{"class":335},"transform ",[219,955,404],{"class":331},[219,957,958],{"class":335}," QgsCoordinateTransform(src, dst, QgsCoordinateTransformContext())\n",[219,960,962,965,967,970,973,976,978,981],{"class":221,"line":961},20,[219,963,964],{"class":335},"point ",[219,966,404],{"class":331},[219,968,969],{"class":335}," transform.transform(QgsPointXY(",[219,971,972],{"class":331},"-",[219,974,975],{"class":232},"2.0",[219,977,367],{"class":335},[219,979,980],{"class":232},"52.0",[219,982,983],{"class":335},"))\n",[219,985,987,989,992,995,998,1001,1004,1007,1010,1012,1015,1018,1021,1024,1027],{"class":221,"line":986},21,[219,988,909],{"class":331},[219,990,991],{"class":232}," abs",[219,993,994],{"class":335},"(point.x() ",[219,996,997],{"class":331},"+",[219,999,1000],{"class":232}," 222638.98",[219,1002,1003],{"class":335},") ",[219,1005,1006],{"class":331},"\u003C",[219,1008,1009],{"class":232}," 1.0",[219,1011,367],{"class":335},[219,1013,1014],{"class":331},"f",[219,1016,1017],{"class":228},"\"projection maths looks wrong: ",[219,1019,1020],{"class":232},"{",[219,1022,1023],{"class":335},"point.x()",[219,1025,1026],{"class":232},"}",[219,1028,1029],{"class":228},"\"\n",[219,1031,1033],{"class":221,"line":1032},22,[219,1034,355],{"emptyLinePlaceholder":354},[219,1036,1038,1040,1042,1044],{"class":221,"line":1037},23,[219,1039,341],{"class":331},[219,1041,452],{"class":335},[219,1043,332],{"class":331},[219,1045,457],{"class":335},[219,1047,1049,1051,1053,1055],{"class":221,"line":1048},24,[219,1050,341],{"class":331},[219,1052,431],{"class":335},[219,1054,332],{"class":331},[219,1056,436],{"class":335},[219,1058,1060],{"class":221,"line":1059},25,[219,1061,468],{"class":335},[219,1063,1065],{"class":221,"line":1064},26,[219,1066,474],{"class":335},[219,1068,1070,1072,1075,1078,1081],{"class":221,"line":1069},27,[219,1071,909],{"class":331},[219,1073,1074],{"class":335}," QgsApplication.processingRegistry().algorithmById(",[219,1076,1077],{"class":228},"\"native:buffer\"",[219,1079,1080],{"class":335},"), ",[219,1082,1083],{"class":228},"\"no native algorithms\"\n",[219,1085,1087],{"class":221,"line":1086},28,[219,1088,355],{"emptyLinePlaceholder":354},[219,1090,1092,1094,1096,1099],{"class":221,"line":1091},29,[219,1093,861],{"class":232},[219,1095,864],{"class":335},[219,1097,1098],{"class":228},"\"smoke test passed\"",[219,1100,373],{"class":335},[219,1102,1104],{"class":221,"line":1103},30,[219,1105,497],{"class":335},[14,1107,1108,1110],{},[187,1109,281],{}," Each assertion targets a different failure mode: the memory provider proves the core libraries loaded, the coordinate check proves PROJ found its data files, and the algorithm lookup proves Processing registered. Checking an actual projected coordinate rather than merely that the transform did not raise is what catches a PROJ installation missing its database — a broken transform frequently returns the input unchanged rather than failing. Running this as the last step of an environment build, and again in CI before the real tests, turns a mysterious afternoon into a two-line error message.",[175,1112,1114],{"id":1113},"platform-quirks","Platform quirks",[14,1116,1117],{},"Each platform has one thing that catches people out.",[14,1119,1120,1121,1124,1125,1127,1128,1131,1132,1135],{},"On ",[187,1122,1123],{},"Linux",", a headless machine needs ",[192,1126,509],{}," and, for some Qt builds, the ",[192,1129,1130],{},"xvfb"," fallback. Containers additionally need ",[192,1133,1134],{},"libgl1"," present even for offscreen rendering.",[14,1137,1120,1138,1141,1142,1145],{},[187,1139,1140],{},"macOS",", the conda QGIS is a genuinely separate installation from a ",[192,1143,1144],{},".dmg"," install and does not share its plugins or profile. Apple Silicon builds are available on conda-forge but occasionally lag the Intel ones by a release.",[14,1147,1120,1148,1151,1152,1155,1156,1159],{},[187,1149,1150],{},"Windows",", activation must happen through the conda shell hook — running the environment's ",[192,1153,1154],{},"python.exe"," directly without activating leaves the DLL search path unset, and imports fail with an opaque message about a missing module that is plainly present on disk. In a scheduled task, call ",[192,1157,1158],{},"conda activate"," in the batch file rather than pointing at the interpreter.",[175,1161,1163],{"id":1162},"when-not-to-use-conda","When not to use conda",[14,1165,1166,1167,1171],{},"Conda is the wrong choice for developing a plugin that will run in a user's desktop QGIS. The environment's QGIS is a different installation with a different profile, so a plugin developed against it can pass every test and still fail on the machine it is meant for. Develop plugins against the same install that will run them, and see ",[21,1168,1170],{"href":1169},"\u002Fpyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002F","setting up PyCharm for QGIS"," for that arrangement.",[14,1173,1174],{},"It is also unnecessary overhead when a script only ever runs on one machine that already has QGIS installed and does not need extra packages. The conda route earns its keep when reproducibility, multiple versions or a headless server are involved — which is to say, most automation and almost no interactive work.",[175,1176,1178],{"id":1177},"qgis-version-compatibility","QGIS version compatibility",[14,1180,1181,1182,1185],{},"The examples target ",[187,1183,1184],{},"QGIS 3.34 LTR"," (Python 3.12).",[1187,1188,1189,1205],"table",{},[1190,1191,1192],"thead",{},[1193,1194,1195,1199,1202],"tr",{},[1196,1197,1198],"th",{},"QGIS version",[1196,1200,1201],{},"Python",[1196,1203,1204],{},"Notes",[1206,1207,1208,1220,1230,1240,1250],"tbody",{},[1193,1209,1210,1214,1217],{},[1211,1212,1213],"td",{},"3.22 LTR",[1211,1215,1216],{},"3.9",[1211,1218,1219],{},"Available on conda-forge; older builds may be unavailable for Apple Silicon.",[1193,1221,1222,1225,1227],{},[1211,1223,1224],{},"3.28 LTR",[1211,1226,1216],{},[1211,1228,1229],{},"Widely used conda-forge build; stable dependency tree.",[1193,1231,1232,1235,1237],{},[1211,1233,1234],{},"3.34 LTR",[1211,1236,251],{},[1211,1238,1239],{},"Baseline for this page.",[1193,1241,1242,1245,1247],{},[1211,1243,1244],{},"3.40",[1211,1246,251],{},[1211,1248,1249],{},"Newer GDAL and PROJ; pin GDAL explicitly if a workflow depends on its behaviour.",[1193,1251,1252,1255,1257],{},[1211,1253,1254],{},"3.44+",[1211,1256,251],{},[1211,1258,1259],{},"Qt6 builds appearing; check that any PyQt code targets the right binding.",[175,1261,1263],{"id":1262},"troubleshooting","Troubleshooting",[180,1265,1266,1278,1287,1297,1306,1312],{},[183,1267,1268,1274,1275,1277],{},[187,1269,1270,1273],{},[192,1271,1272],{},"ImportError: DLL load failed"," on Windows."," The environment was not activated. Use the conda shell hook rather than calling ",[192,1276,1154],{}," directly.",[183,1279,1280,1283,1284,1286],{},[187,1281,1282],{},"Qt aborts about a display on a server."," Set ",[192,1285,509],{}," before importing.",[183,1288,1289,502,1294,1296],{},[187,1290,1291,1293],{},[192,1292,521],{}," cannot find any algorithm.",[192,1295,517],{}," was skipped, or the native provider was never added.",[183,1298,1299,1302,1303,1305],{},[187,1300,1301],{},"The solve takes forever or fails."," Use ",[192,1304,194],{},", and make sure conda-forge is the only channel — mixing defaults with conda-forge is the usual cause.",[183,1307,1308,1311],{},[187,1309,1310],{},"GDAL behaves differently from the desktop."," The environment has a different GDAL version. Pin it explicitly.",[183,1313,1314,1317],{},[187,1315,1316],{},"A plugin works in conda and not in the desktop."," They are separate installations with separate profiles. Test against the target install.",[175,1319,1321],{"id":1320},"conclusion","Conclusion",[14,1323,1324,1325,1327],{},"Use Miniforge, create the environment with a pinned QGIS and Python, set ",[192,1326,509],{}," for headless work, and record the environment in a file rather than in your shell history. Reach for conda when reproducibility or multiple versions matter, and stay with the system install when developing something that has to run inside somebody's desktop QGIS.",[175,1329,1331],{"id":1330},"frequently-asked-questions","Frequently Asked Questions",[14,1333,1334,1337,1338,1340],{},[187,1335,1336],{},"Can I run QGIS Desktop from a conda environment?","\nYes — ",[192,1339,635],{}," on the command line inside the environment launches the full application, with its own profile directory. It is a useful way to test a workflow against a different version without a second system install.",[14,1342,1343,1346],{},[187,1344,1345],{},"Does conda give me the same plugins?","\nNo. The environment has its own profile, so plugins must be installed into it separately. That isolation is the point, and it is also why it is unsuitable for plugin development.",[14,1348,1349,1352,1353,1357],{},[187,1350,1351],{},"Can I pip install packages alongside it?","\nYes, but conservatively. Anything with compiled dependencies — especially anything linking GDAL or GEOS — should come from conda-forge, or the two copies of the library will conflict. See ",[21,1354,1356],{"href":1355},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Finstall-python-packages-into-qgis\u002F","installing Python packages into QGIS"," for the equivalent discussion on a system install.",[14,1359,1360,1363,1364,1368],{},[187,1361,1362],{},"Is this how I should set up CI?","\nIt is the most common approach, and pairs well with a lock file. See ",[21,1365,1367],{"href":1366},"\u002Fqgis-plugin-development\u002Ftesting-and-ci-for-plugins\u002Frun-qgis-plugin-tests-in-github-actions\u002F","running QGIS plugin tests in GitHub Actions"," for the container-based alternative.",[175,1370,1372],{"id":1371},"related","Related",[180,1374,1375,1380,1385,1390,1396],{},[183,1376,1377,1379],{},[21,1378,24],{"href":23}," — the guide this recipe belongs to",[183,1381,1382],{},[21,1383,1384],{"href":311},"Running Python Scripts Outside QGIS Desktop",[183,1386,1387],{},[21,1388,1389],{"href":1355},"Install Python Packages into QGIS",[183,1391,1392],{},[21,1393,1395],{"href":1394},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Frun-pyqgis-in-docker-container\u002F","Run PyQGIS in a Docker Container",[183,1397,1398],{},[21,1399,1401],{"href":1400},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Fuse-pyqgis-in-jupyter-notebook\u002F","Use PyQGIS in a Jupyter Notebook",[1403,1404,1405],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}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 .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}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}",{"title":215,"searchDepth":257,"depth":257,"links":1407},[1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419],{"id":177,"depth":257,"text":178},{"id":204,"depth":257,"text":205},{"id":316,"depth":257,"text":317},{"id":525,"depth":257,"text":526},{"id":768,"depth":257,"text":769},{"id":1113,"depth":257,"text":1114},{"id":1162,"depth":257,"text":1163},{"id":1177,"depth":257,"text":1178},{"id":1262,"depth":257,"text":1263},{"id":1320,"depth":257,"text":1321},{"id":1330,"depth":257,"text":1331},{"id":1371,"depth":257,"text":1372},"Install a self-contained PyQGIS environment from conda-forge — creating the environment, pinning the QGIS version, activation quirks on each platform, and when conda beats the system install.","md",{"slug":12,"type":1423,"breadcrumb":1424,"datePublished":1425,"dateModified":1425},"article","Conda & Mamba","2026-08-27","\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Fuse-pyqgis-with-conda-and-mamba",{"title":5,"description":1420},"pyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Fuse-pyqgis-with-conda-and-mamba\u002Findex","yeUYDU83jLTPHM8x68Pyw3YYx8u2wh6XZpre9aONrLg",1787823360567]