[{"data":1,"prerenderedAt":2139},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Fbatch-process-all-layers-in-project-pyqgis":3},{"id":4,"title":5,"body":6,"description":2128,"extension":2129,"meta":2130,"navigation":240,"path":2135,"seo":2136,"stem":2137,"__hash__":2138},"docs\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Fbatch-process-all-layers-in-project-pyqgis\u002Findex.md","Batch Process All Layers in a Project with PyQGIS",{"type":7,"value":8,"toc":2113},"minimark",[9,13,17,26,174,179,197,201,208,463,478,482,485,539,826,831,835,838,1165,1182,1186,1189,1362,1376,1380,1383,1539,1549,1553,1556,1607,1840,1849,1853,1856,1965,1974,1978,1994,1998,2029,2033,2039,2043,2053,2063,2069,2075,2079,2109],[10,11,5],"h1",{"id":12},"batch-process-all-layers-in-a-project-with-pyqgis",[14,15,16],"p",{},"Many batch jobs start not from a folder of files but from a project someone has already assembled: forty layers in carefully named groups, each loaded from a different source with its own subset filter. \"Reproject everything in this project\", \"export every layer in the Deliverables group to GeoPackage\", \"fix geometries in all polygon layers\", \"add a checksum field to every table\" — the project is the unit of work, and the script should respect the structure people put into it.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002F","Batch Processing with PyQGIS",". It walks a project's layers by type, group and name, skips layers that do not apply, runs a Processing algorithm per layer, writes outputs with sensible names, optionally swaps results back into the project, and reports per layer.",[14,27,28],{},[29,30,35,39,43,50,67,76,85,91,96,100,104,108,115,123,129,134,138,142,146,150,156,162,166,170],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 280","img","A project layer tree filtered by group, type, name and validity into a list of layers that are processed one by one with a report","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Selecting layers from a project",[40,41,42],"desc",{},"A project's layer tree contains groups such as Base, Analysis and Deliverables, each holding layers of different types. A selection step walks the tree and keeps layers matching rules: inside a given group, of vector polygon type, with names matching a pattern, and valid. The selected layers are processed one by one and each result is recorded in a report.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","280","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"bpTreeArrow","0 0 10 10","8","5","7","auto-start-reverse",[63,64],"path",{"d":65,"fill":66},"M0 0 L10 5 L0 10 z","#2f3b35",[68,69,75],"text",{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","Walk the tree, apply rules, process",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":58,"fill":82,"stroke":83,"style":84},"24","56","220","196","#fffdf7","#59645f","stroke-width:2",[68,86,90],{"x":87,"y":88,"style":89,"fill":73,"textAnchor":74},"134","97.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","layer tree",[68,92,95],{"x":87,"y":93,"style":94,"fill":66,"textAnchor":74},"127.78","text-anchor:middle;font-size:10.5px;font-family:sans-serif","Base\u002F",[68,97,99],{"x":87,"y":98,"style":94,"fill":66,"textAnchor":74},"157.78","Analysis\u002F",[68,101,103],{"x":87,"y":102,"style":94,"fill":66,"textAnchor":74},"187.78","Deliverables\u002F",[68,105,107],{"x":87,"y":106,"style":94,"fill":83,"textAnchor":74},"217.78","groups + layers",[109,110],"line",{"x1":111,"y1":112,"x2":113,"y2":112,"stroke":66,"style":114},"244","154","286","stroke-width:1.8;marker-end:url(#bpTreeArrow)",[44,116],{"x":117,"y":118,"width":119,"height":120,"rx":58,"fill":121,"stroke":122,"style":84},"290","72","210","164","#fdf2e2","#b45309",[68,124,128],{"x":125,"y":126,"style":127,"fill":122,"textAnchor":74},"395","101.6","text-anchor:middle;font-size:11.0px;font-family:sans-serif;font-weight:bold","rules",[68,130,133],{"x":125,"y":131,"style":132,"fill":66,"textAnchor":74},"129.6","text-anchor:middle;font-size:10.0px;font-family:sans-serif","in group?",[68,135,137],{"x":125,"y":136,"style":132,"fill":66,"textAnchor":74},"157.6","type = polygon?",[68,139,141],{"x":125,"y":140,"style":132,"fill":66,"textAnchor":74},"185.6","name pattern?",[68,143,145],{"x":125,"y":144,"style":132,"fill":66,"textAnchor":74},"213.6","valid?",[109,147],{"x1":148,"y1":112,"x2":149,"y2":112,"stroke":66,"style":114},"500","542",[44,151],{"x":152,"y":118,"width":153,"height":120,"rx":58,"fill":154,"stroke":155,"style":84},"546","190","#e8efe6","#15803d",[68,157,161],{"x":158,"y":159,"style":89,"fill":160,"textAnchor":74},"641","115.78","#166534","per layer",[68,163,165],{"x":158,"y":164,"style":94,"fill":66,"textAnchor":74},"143.78","process",[68,167,169],{"x":158,"y":168,"style":94,"fill":66,"textAnchor":74},"171.78","write output",[68,171,173],{"x":158,"y":172,"style":94,"fill":66,"textAnchor":74},"199.78","report line",[175,176,178],"h2",{"id":177},"prerequisites","Prerequisites",[180,181,182,186,194],"ul",{},[183,184,185],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series.",[183,187,188,189,193],{},"A project, either open in QGIS or loaded in a standalone script as in ",[21,190,192],{"href":191},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-projects\u002Fsave-and-load-qgis-project-pyqgis\u002F","saving and loading a QGIS project",".",[183,195,196],{},"A clear rule for which layers to process; \"all of them\" is rarely right.",[175,198,200],{"id":199},"iterate-layers-in-tree-order","Iterate layers in tree order",[14,202,203,207],{},[204,205,206],"code",{},"QgsProject.mapLayers()"," returns layers in no particular order. Walking the layer tree instead follows the order and grouping users see in the Layers panel, which makes reports and outputs easier to follow.",[209,210,215],"pre",{"className":211,"code":212,"language":213,"meta":214,"style":214},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProject, QgsLayerTreeLayer, QgsLayerTreeGroup\n\nproject = QgsProject.instance()\n\ndef layers_in_tree(node=None, path=()):\n    node = node or project.layerTreeRoot()\n    for child in node.children():\n        if isinstance(child, QgsLayerTreeGroup):\n            yield from layers_in_tree(child, path + (child.name(),))\n        elif isinstance(child, QgsLayerTreeLayer) and child.layer() is not None:\n            yield \"\u002F\".join(path), child.layer()\n\nfor group, layer in layers_in_tree():\n    print(f\"{group or '(root)':\u003C20} {layer.name():\u003C28} {type(layer).__name__}\")\n","python","",[204,216,217,235,242,254,259,286,303,318,330,345,374,387,392,406],{"__ignoreMap":214},[218,219,221,225,229,232],"span",{"class":109,"line":220},1,[218,222,224],{"class":223},"snl16","from",[218,226,228],{"class":227},"s95oV"," qgis.core ",[218,230,231],{"class":223},"import",[218,233,234],{"class":227}," QgsProject, QgsLayerTreeLayer, QgsLayerTreeGroup\n",[218,236,238],{"class":109,"line":237},2,[218,239,241],{"emptyLinePlaceholder":240},true,"\n",[218,243,245,248,251],{"class":109,"line":244},3,[218,246,247],{"class":227},"project ",[218,249,250],{"class":223},"=",[218,252,253],{"class":227}," QgsProject.instance()\n",[218,255,257],{"class":109,"line":256},4,[218,258,241],{"emptyLinePlaceholder":240},[218,260,262,265,269,272,274,278,281,283],{"class":109,"line":261},5,[218,263,264],{"class":223},"def",[218,266,268],{"class":267},"svObZ"," layers_in_tree",[218,270,271],{"class":227},"(node",[218,273,250],{"class":223},[218,275,277],{"class":276},"sDLfK","None",[218,279,280],{"class":227},", path",[218,282,250],{"class":223},[218,284,285],{"class":227},"()):\n",[218,287,289,292,294,297,300],{"class":109,"line":288},6,[218,290,291],{"class":227},"    node ",[218,293,250],{"class":223},[218,295,296],{"class":227}," node ",[218,298,299],{"class":223},"or",[218,301,302],{"class":227}," project.layerTreeRoot()\n",[218,304,306,309,312,315],{"class":109,"line":305},7,[218,307,308],{"class":223},"    for",[218,310,311],{"class":227}," child ",[218,313,314],{"class":223},"in",[218,316,317],{"class":227}," node.children():\n",[218,319,321,324,327],{"class":109,"line":320},8,[218,322,323],{"class":223},"        if",[218,325,326],{"class":276}," isinstance",[218,328,329],{"class":227},"(child, QgsLayerTreeGroup):\n",[218,331,333,336,339,342],{"class":109,"line":332},9,[218,334,335],{"class":223},"            yield from",[218,337,338],{"class":227}," layers_in_tree(child, path ",[218,340,341],{"class":223},"+",[218,343,344],{"class":227}," (child.name(),))\n",[218,346,348,351,353,356,359,362,365,368,371],{"class":109,"line":347},10,[218,349,350],{"class":223},"        elif",[218,352,326],{"class":276},[218,354,355],{"class":227},"(child, QgsLayerTreeLayer) ",[218,357,358],{"class":223},"and",[218,360,361],{"class":227}," child.layer() ",[218,363,364],{"class":223},"is",[218,366,367],{"class":223}," not",[218,369,370],{"class":276}," None",[218,372,373],{"class":227},":\n",[218,375,377,380,384],{"class":109,"line":376},11,[218,378,379],{"class":223},"            yield",[218,381,383],{"class":382},"sU2Wk"," \"\u002F\"",[218,385,386],{"class":227},".join(path), child.layer()\n",[218,388,390],{"class":109,"line":389},12,[218,391,241],{"emptyLinePlaceholder":240},[218,393,395,398,401,403],{"class":109,"line":394},13,[218,396,397],{"class":223},"for",[218,399,400],{"class":227}," group, layer ",[218,402,314],{"class":223},[218,404,405],{"class":227}," layers_in_tree():\n",[218,407,409,412,415,418,421,424,427,429,432,435,438,441,444,447,449,452,455,458,460],{"class":109,"line":408},14,[218,410,411],{"class":276},"    print",[218,413,414],{"class":227},"(",[218,416,417],{"class":223},"f",[218,419,420],{"class":382},"\"",[218,422,423],{"class":276},"{",[218,425,426],{"class":227},"group ",[218,428,299],{"class":223},[218,430,431],{"class":382}," '(root)'",[218,433,434],{"class":223},":\u003C20",[218,436,437],{"class":276},"}",[218,439,440],{"class":276}," {",[218,442,443],{"class":227},"layer.name()",[218,445,446],{"class":223},":\u003C28",[218,448,437],{"class":276},[218,450,451],{"class":276}," {type",[218,453,454],{"class":227},"(layer).",[218,456,457],{"class":276},"__name__}",[218,459,420],{"class":382},[218,461,462],{"class":227},")\n",[14,464,465,469,470,473,474,477],{},[466,467,468],"strong",{},"Breakdown:"," Recursing through groups yields each layer with its group path, such as ",[204,471,472],{},"Deliverables\u002FRoads",", which is useful both as a selection criterion and for building output folder names. Tree layers whose ",[204,475,476],{},"layer()"," is None are broken references — layers removed from the project but left in the tree — and are skipped. A layer that appears twice in the tree is yielded twice; collect ids in a set if each should be processed once.",[175,479,481],{"id":480},"select-the-layers-that-apply","Select the layers that apply",[14,483,484],{},"Combining a few rules — group, type, geometry, name pattern, validity — selects exactly the layers a job should touch.",[14,486,487],{},[29,488,491,494,497,500,503,507,511,515,519,523,526,530,533,536],{"viewBox":489,"role":32,"ariaLabel":490,"xmlns":34},"0 0 760 240","Selection rules for layers — valid, polygon vector, in the Deliverables group, not a scratch layer — with skipped layers recorded with reasons",[36,492,493],{},"Selection rules",[40,495,496],{},"A layer is selected when it is valid, is a vector layer with polygon geometry, sits in the Deliverables group or below, and its name does not start with an underscore, a convention for scratch layers. Each skipped layer is recorded with the reason, so the report explains why something was not processed.",[44,498],{"x":46,"y":46,"width":47,"height":499,"fill":49},"240",[68,501,502],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Keep, or skip with a reason",[44,504],{"x":78,"y":79,"width":505,"height":506,"rx":58,"fill":154,"stroke":155,"style":84},"340","160",[68,508,510],{"x":509,"y":88,"style":89,"fill":160,"textAnchor":74},"194","keep if",[68,512,514],{"x":509,"y":513,"style":94,"fill":66,"textAnchor":74},"125.78","valid vector, polygon",[68,516,518],{"x":509,"y":517,"style":94,"fill":66,"textAnchor":74},"153.78","under Deliverables\u002F",[68,520,522],{"x":509,"y":521,"style":94,"fill":66,"textAnchor":74},"181.78","name not _scratch",[44,524],{"x":525,"y":79,"width":505,"height":506,"rx":58,"fill":82,"stroke":83,"style":84},"396",[68,527,529],{"x":528,"y":88,"style":89,"fill":73,"textAnchor":74},"566","else skip with reason",[68,531,532],{"x":528,"y":513,"style":94,"fill":66,"textAnchor":74},"invalid source",[68,534,535],{"x":528,"y":517,"style":94,"fill":66,"textAnchor":74},"wrong type or group",[68,537,538],{"x":528,"y":521,"style":94,"fill":83,"textAnchor":74},"reported, not silent",[209,540,542],{"className":211,"code":541,"language":213,"meta":214,"style":214},"import re\nfrom qgis.core import QgsVectorLayer, QgsWkbTypes\n\ndef select(group, layer, group_prefix=\"Deliverables\", pattern=r\"^[^_]\"):\n    if not layer.isValid():\n        return False, \"invalid source\"\n    if not isinstance(layer, QgsVectorLayer):\n        return False, \"not a vector layer\"\n    if layer.geometryType() != QgsWkbTypes.PolygonGeometry:\n        return False, \"not polygons\"\n    if not group.startswith(group_prefix):\n        return False, f\"outside {group_prefix}\"\n    if not re.match(pattern, layer.name()):\n        return False, \"scratch layer\"\n    return True, \"\"\n\nselected, skipped = [], []\nfor group, layer in layers_in_tree():\n    ok, why = select(group, layer)\n    (selected if ok else skipped).append((group, layer, why))\nprint(len(selected), \"selected;\", len(skipped), \"skipped\")\n",[204,543,544,551,562,566,605,615,629,640,651,664,675,684,707,716,727,741,746,757,768,779,797],{"__ignoreMap":214},[218,545,546,548],{"class":109,"line":220},[218,547,231],{"class":223},[218,549,550],{"class":227}," re\n",[218,552,553,555,557,559],{"class":109,"line":237},[218,554,224],{"class":223},[218,556,228],{"class":227},[218,558,231],{"class":223},[218,560,561],{"class":227}," QgsVectorLayer, QgsWkbTypes\n",[218,563,564],{"class":109,"line":244},[218,565,241],{"emptyLinePlaceholder":240},[218,567,568,570,573,576,578,581,584,586,589,591,594,597,600,602],{"class":109,"line":256},[218,569,264],{"class":223},[218,571,572],{"class":267}," select",[218,574,575],{"class":227},"(group, layer, group_prefix",[218,577,250],{"class":223},[218,579,580],{"class":382},"\"Deliverables\"",[218,582,583],{"class":227},", pattern",[218,585,250],{"class":223},[218,587,588],{"class":223},"r",[218,590,420],{"class":382},[218,592,593],{"class":276},"^[",[218,595,596],{"class":223},"^",[218,598,599],{"class":276},"_]",[218,601,420],{"class":382},[218,603,604],{"class":227},"):\n",[218,606,607,610,612],{"class":109,"line":261},[218,608,609],{"class":223},"    if",[218,611,367],{"class":223},[218,613,614],{"class":227}," layer.isValid():\n",[218,616,617,620,623,626],{"class":109,"line":288},[218,618,619],{"class":223},"        return",[218,621,622],{"class":276}," False",[218,624,625],{"class":227},", ",[218,627,628],{"class":382},"\"invalid source\"\n",[218,630,631,633,635,637],{"class":109,"line":305},[218,632,609],{"class":223},[218,634,367],{"class":223},[218,636,326],{"class":276},[218,638,639],{"class":227},"(layer, QgsVectorLayer):\n",[218,641,642,644,646,648],{"class":109,"line":320},[218,643,619],{"class":223},[218,645,622],{"class":276},[218,647,625],{"class":227},[218,649,650],{"class":382},"\"not a vector layer\"\n",[218,652,653,655,658,661],{"class":109,"line":332},[218,654,609],{"class":223},[218,656,657],{"class":227}," layer.geometryType() ",[218,659,660],{"class":223},"!=",[218,662,663],{"class":227}," QgsWkbTypes.PolygonGeometry:\n",[218,665,666,668,670,672],{"class":109,"line":347},[218,667,619],{"class":223},[218,669,622],{"class":276},[218,671,625],{"class":227},[218,673,674],{"class":382},"\"not polygons\"\n",[218,676,677,679,681],{"class":109,"line":376},[218,678,609],{"class":223},[218,680,367],{"class":223},[218,682,683],{"class":227}," group.startswith(group_prefix):\n",[218,685,686,688,690,692,694,697,699,702,704],{"class":109,"line":389},[218,687,619],{"class":223},[218,689,622],{"class":276},[218,691,625],{"class":227},[218,693,417],{"class":223},[218,695,696],{"class":382},"\"outside ",[218,698,423],{"class":276},[218,700,701],{"class":227},"group_prefix",[218,703,437],{"class":276},[218,705,706],{"class":382},"\"\n",[218,708,709,711,713],{"class":109,"line":394},[218,710,609],{"class":223},[218,712,367],{"class":223},[218,714,715],{"class":227}," re.match(pattern, layer.name()):\n",[218,717,718,720,722,724],{"class":109,"line":408},[218,719,619],{"class":223},[218,721,622],{"class":276},[218,723,625],{"class":227},[218,725,726],{"class":382},"\"scratch layer\"\n",[218,728,730,733,736,738],{"class":109,"line":729},15,[218,731,732],{"class":223},"    return",[218,734,735],{"class":276}," True",[218,737,625],{"class":227},[218,739,740],{"class":382},"\"\"\n",[218,742,744],{"class":109,"line":743},16,[218,745,241],{"emptyLinePlaceholder":240},[218,747,749,752,754],{"class":109,"line":748},17,[218,750,751],{"class":227},"selected, skipped ",[218,753,250],{"class":223},[218,755,756],{"class":227}," [], []\n",[218,758,760,762,764,766],{"class":109,"line":759},18,[218,761,397],{"class":223},[218,763,400],{"class":227},[218,765,314],{"class":223},[218,767,405],{"class":227},[218,769,771,774,776],{"class":109,"line":770},19,[218,772,773],{"class":227},"    ok, why ",[218,775,250],{"class":223},[218,777,778],{"class":227}," select(group, layer)\n",[218,780,782,785,788,791,794],{"class":109,"line":781},20,[218,783,784],{"class":227},"    (selected ",[218,786,787],{"class":223},"if",[218,789,790],{"class":227}," ok ",[218,792,793],{"class":223},"else",[218,795,796],{"class":227}," skipped).append((group, layer, why))\n",[218,798,800,803,805,808,811,814,816,818,821,824],{"class":109,"line":799},21,[218,801,802],{"class":276},"print",[218,804,414],{"class":227},[218,806,807],{"class":276},"len",[218,809,810],{"class":227},"(selected), ",[218,812,813],{"class":382},"\"selected;\"",[218,815,625],{"class":227},[218,817,807],{"class":276},[218,819,820],{"class":227},"(skipped), ",[218,822,823],{"class":382},"\"skipped\"",[218,825,462],{"class":227},[14,827,828,830],{},[466,829,468],{}," Each rule returns a reason when it rejects a layer, so the final report can say why every skipped layer was skipped — far more useful than a silent filter when someone asks why their layer was not exported. A naming convention such as a leading underscore for scratch layers lets users opt layers out without editing the script. Rules are ordinary Python and easy to change per job.",[175,832,834],{"id":833},"run-an-algorithm-per-layer","Run an algorithm per layer",[14,836,837],{},"Each selected layer becomes an input to the same Processing algorithm. The layer object can be passed directly, which preserves subset filters and joined fields — something a file path would lose.",[209,839,841],{"className":211,"code":840,"language":213,"meta":214,"style":214},"import processing\nfrom pathlib import Path\n\nout_dir = Path(\"\u002Fdata\u002Fexports\") \u002F project.baseName()\nout_dir.mkdir(parents=True, exist_ok=True)\nreport = []\n\nfor group, layer, _ in selected:\n    safe = re.sub(r\"[^A-Za-z0-9_-]+\", \"_\", f\"{group}_{layer.name()}\").strip(\"_\")\n    try:\n        fixed = processing.run(\"native:fixgeometries\", {\n            \"INPUT\": layer, \"OUTPUT\": \"memory:\"})[\"OUTPUT\"]\n        out = processing.run(\"native:savefeatures\", {\n            \"INPUT\": fixed, \"OUTPUT\": str(out_dir \u002F \"deliverables.gpkg\"),\n            \"LAYER_NAME\": safe, \"ACTION_ON_EXISTING_FILE\": 1})[\"OUTPUT\"]\n        report.append((layer.name(), \"ok\", fixed.featureCount(), out))\n    except Exception as err:\n        report.append((layer.name(), f\"FAILED: {err}\", 0, \"\"))\n",[204,842,843,850,862,866,888,913,923,927,939,1001,1008,1024,1049,1063,1088,1110,1121,1135],{"__ignoreMap":214},[218,844,845,847],{"class":109,"line":220},[218,846,231],{"class":223},[218,848,849],{"class":227}," processing\n",[218,851,852,854,857,859],{"class":109,"line":237},[218,853,224],{"class":223},[218,855,856],{"class":227}," pathlib ",[218,858,231],{"class":223},[218,860,861],{"class":227}," Path\n",[218,863,864],{"class":109,"line":244},[218,865,241],{"emptyLinePlaceholder":240},[218,867,868,871,873,876,879,882,885],{"class":109,"line":256},[218,869,870],{"class":227},"out_dir ",[218,872,250],{"class":223},[218,874,875],{"class":227}," Path(",[218,877,878],{"class":382},"\"\u002Fdata\u002Fexports\"",[218,880,881],{"class":227},") ",[218,883,884],{"class":223},"\u002F",[218,886,887],{"class":227}," project.baseName()\n",[218,889,890,893,897,899,902,904,907,909,911],{"class":109,"line":261},[218,891,892],{"class":227},"out_dir.mkdir(",[218,894,896],{"class":895},"s9osk","parents",[218,898,250],{"class":223},[218,900,901],{"class":276},"True",[218,903,625],{"class":227},[218,905,906],{"class":895},"exist_ok",[218,908,250],{"class":223},[218,910,901],{"class":276},[218,912,462],{"class":227},[218,914,915,918,920],{"class":109,"line":288},[218,916,917],{"class":227},"report ",[218,919,250],{"class":223},[218,921,922],{"class":227}," []\n",[218,924,925],{"class":109,"line":305},[218,926,241],{"emptyLinePlaceholder":240},[218,928,929,931,934,936],{"class":109,"line":320},[218,930,397],{"class":223},[218,932,933],{"class":227}," group, layer, _ ",[218,935,314],{"class":223},[218,937,938],{"class":227}," selected:\n",[218,940,941,944,946,949,951,953,956,958,961,963,965,967,970,972,974,976,978,981,983,986,988,990,992,994,997,999],{"class":109,"line":332},[218,942,943],{"class":227},"    safe ",[218,945,250],{"class":223},[218,947,948],{"class":227}," re.sub(",[218,950,588],{"class":223},[218,952,420],{"class":382},[218,954,955],{"class":276},"[",[218,957,596],{"class":223},[218,959,960],{"class":276},"A-Za-z0-9_-]",[218,962,341],{"class":223},[218,964,420],{"class":382},[218,966,625],{"class":227},[218,968,969],{"class":382},"\"_\"",[218,971,625],{"class":227},[218,973,417],{"class":223},[218,975,420],{"class":382},[218,977,423],{"class":276},[218,979,980],{"class":227},"group",[218,982,437],{"class":276},[218,984,985],{"class":382},"_",[218,987,423],{"class":276},[218,989,443],{"class":227},[218,991,437],{"class":276},[218,993,420],{"class":382},[218,995,996],{"class":227},").strip(",[218,998,969],{"class":382},[218,1000,462],{"class":227},[218,1002,1003,1006],{"class":109,"line":347},[218,1004,1005],{"class":223},"    try",[218,1007,373],{"class":227},[218,1009,1010,1013,1015,1018,1021],{"class":109,"line":376},[218,1011,1012],{"class":227},"        fixed ",[218,1014,250],{"class":223},[218,1016,1017],{"class":227}," processing.run(",[218,1019,1020],{"class":382},"\"native:fixgeometries\"",[218,1022,1023],{"class":227},", {\n",[218,1025,1026,1029,1032,1035,1038,1041,1044,1046],{"class":109,"line":389},[218,1027,1028],{"class":382},"            \"INPUT\"",[218,1030,1031],{"class":227},": layer, ",[218,1033,1034],{"class":382},"\"OUTPUT\"",[218,1036,1037],{"class":227},": ",[218,1039,1040],{"class":382},"\"memory:\"",[218,1042,1043],{"class":227},"})[",[218,1045,1034],{"class":382},[218,1047,1048],{"class":227},"]\n",[218,1050,1051,1054,1056,1058,1061],{"class":109,"line":394},[218,1052,1053],{"class":227},"        out ",[218,1055,250],{"class":223},[218,1057,1017],{"class":227},[218,1059,1060],{"class":382},"\"native:savefeatures\"",[218,1062,1023],{"class":227},[218,1064,1065,1067,1070,1072,1074,1077,1080,1082,1085],{"class":109,"line":408},[218,1066,1028],{"class":382},[218,1068,1069],{"class":227},": fixed, ",[218,1071,1034],{"class":382},[218,1073,1037],{"class":227},[218,1075,1076],{"class":276},"str",[218,1078,1079],{"class":227},"(out_dir ",[218,1081,884],{"class":223},[218,1083,1084],{"class":382}," \"deliverables.gpkg\"",[218,1086,1087],{"class":227},"),\n",[218,1089,1090,1093,1096,1099,1101,1104,1106,1108],{"class":109,"line":729},[218,1091,1092],{"class":382},"            \"LAYER_NAME\"",[218,1094,1095],{"class":227},": safe, ",[218,1097,1098],{"class":382},"\"ACTION_ON_EXISTING_FILE\"",[218,1100,1037],{"class":227},[218,1102,1103],{"class":276},"1",[218,1105,1043],{"class":227},[218,1107,1034],{"class":382},[218,1109,1048],{"class":227},[218,1111,1112,1115,1118],{"class":109,"line":743},[218,1113,1114],{"class":227},"        report.append((layer.name(), ",[218,1116,1117],{"class":382},"\"ok\"",[218,1119,1120],{"class":227},", fixed.featureCount(), out))\n",[218,1122,1123,1126,1129,1132],{"class":109,"line":748},[218,1124,1125],{"class":223},"    except",[218,1127,1128],{"class":276}," Exception",[218,1130,1131],{"class":223}," as",[218,1133,1134],{"class":227}," err:\n",[218,1136,1137,1139,1141,1144,1146,1149,1151,1153,1155,1157,1159,1162],{"class":109,"line":759},[218,1138,1114],{"class":227},[218,1140,417],{"class":223},[218,1142,1143],{"class":382},"\"FAILED: ",[218,1145,423],{"class":276},[218,1147,1148],{"class":227},"err",[218,1150,437],{"class":276},[218,1152,420],{"class":382},[218,1154,625],{"class":227},[218,1156,46],{"class":276},[218,1158,625],{"class":227},[218,1160,1161],{"class":382},"\"\"",[218,1163,1164],{"class":227},"))\n",[14,1166,1167,1169,1170,1173,1174,1177,1178,1181],{},[466,1168,468],{}," Passing the layer object means Processing reads exactly what the user sees — filtered, with joins — rather than the raw source file. Output table names combine group and layer names, cleaned of characters GeoPackage dislikes, so ",[204,1171,1172],{},"Deliverables\u002FRoads (2026)"," becomes ",[204,1175,1176],{},"Deliverables_Roads_2026",". ",[204,1179,1180],{},"ACTION_ON_EXISTING_FILE: 1"," adds each table to the same GeoPackage; make sure the file starts fresh or uses mode 0 on the first write if reruns should replace it. Catching exceptions per layer keeps one bad layer from stopping the rest.",[175,1183,1185],{"id":1184},"swap-results-back-into-the-project","Swap results back into the project",[14,1187,1188],{},"Some jobs should change the project itself: replace each layer with its repaired or reprojected version while keeping styles, names and positions in the tree.",[209,1190,1192],{"className":211,"code":1191,"language":213,"meta":214,"style":214},"for group, layer, _ in selected:\n    name = layer.name()\n    safe = re.sub(r\"[^A-Za-z0-9_-]+\", \"_\", f\"{group}_{name}\").strip(\"_\")\n    new_source = f\"{out_dir \u002F 'deliverables.gpkg'}|layername={safe}\"\n    layer.setDataSource(new_source, name, \"ogr\")\n    if not layer.isValid():\n        print(\"could not repoint\", name)\nproject.write(str(out_dir \u002F f\"{project.baseName()}_fixed.qgz\"))\n",[204,1193,1194,1204,1214,1269,1304,1314,1322,1335],{"__ignoreMap":214},[218,1195,1196,1198,1200,1202],{"class":109,"line":220},[218,1197,397],{"class":223},[218,1199,933],{"class":227},[218,1201,314],{"class":223},[218,1203,938],{"class":227},[218,1205,1206,1209,1211],{"class":109,"line":237},[218,1207,1208],{"class":227},"    name ",[218,1210,250],{"class":223},[218,1212,1213],{"class":227}," layer.name()\n",[218,1215,1216,1218,1220,1222,1224,1226,1228,1230,1232,1234,1236,1238,1240,1242,1244,1246,1248,1250,1252,1254,1256,1259,1261,1263,1265,1267],{"class":109,"line":244},[218,1217,943],{"class":227},[218,1219,250],{"class":223},[218,1221,948],{"class":227},[218,1223,588],{"class":223},[218,1225,420],{"class":382},[218,1227,955],{"class":276},[218,1229,596],{"class":223},[218,1231,960],{"class":276},[218,1233,341],{"class":223},[218,1235,420],{"class":382},[218,1237,625],{"class":227},[218,1239,969],{"class":382},[218,1241,625],{"class":227},[218,1243,417],{"class":223},[218,1245,420],{"class":382},[218,1247,423],{"class":276},[218,1249,980],{"class":227},[218,1251,437],{"class":276},[218,1253,985],{"class":382},[218,1255,423],{"class":276},[218,1257,1258],{"class":227},"name",[218,1260,437],{"class":276},[218,1262,420],{"class":382},[218,1264,996],{"class":227},[218,1266,969],{"class":382},[218,1268,462],{"class":227},[218,1270,1271,1274,1276,1279,1281,1283,1285,1287,1290,1292,1295,1297,1300,1302],{"class":109,"line":256},[218,1272,1273],{"class":227},"    new_source ",[218,1275,250],{"class":223},[218,1277,1278],{"class":223}," f",[218,1280,420],{"class":382},[218,1282,423],{"class":276},[218,1284,870],{"class":227},[218,1286,884],{"class":223},[218,1288,1289],{"class":382}," 'deliverables.gpkg'",[218,1291,437],{"class":276},[218,1293,1294],{"class":382},"|layername=",[218,1296,423],{"class":276},[218,1298,1299],{"class":227},"safe",[218,1301,437],{"class":276},[218,1303,706],{"class":382},[218,1305,1306,1309,1312],{"class":109,"line":261},[218,1307,1308],{"class":227},"    layer.setDataSource(new_source, name, ",[218,1310,1311],{"class":382},"\"ogr\"",[218,1313,462],{"class":227},[218,1315,1316,1318,1320],{"class":109,"line":288},[218,1317,609],{"class":223},[218,1319,367],{"class":223},[218,1321,614],{"class":227},[218,1323,1324,1327,1329,1332],{"class":109,"line":305},[218,1325,1326],{"class":276},"        print",[218,1328,414],{"class":227},[218,1330,1331],{"class":382},"\"could not repoint\"",[218,1333,1334],{"class":227},", name)\n",[218,1336,1337,1340,1342,1344,1346,1348,1350,1352,1355,1357,1360],{"class":109,"line":320},[218,1338,1339],{"class":227},"project.write(",[218,1341,1076],{"class":276},[218,1343,1079],{"class":227},[218,1345,884],{"class":223},[218,1347,1278],{"class":223},[218,1349,420],{"class":382},[218,1351,423],{"class":276},[218,1353,1354],{"class":227},"project.baseName()",[218,1356,437],{"class":276},[218,1358,1359],{"class":382},"_fixed.qgz\"",[218,1361,1164],{"class":227},[14,1363,1364,1366,1367,1370,1371,1375],{},[466,1365,468],{}," ",[204,1368,1369],{},"setDataSource"," repoints an existing layer to a new source while keeping its id, style, labels, forms and position in the tree — the same technique used in ",[21,1372,1374],{"href":1373},"\u002Fspatial-data-processing-automation\u002Flayer-data-sources-and-formats\u002Ffix-broken-layer-paths-in-project-pyqgis\u002F","fixing broken layer paths",". Writing the result as a new project file leaves the original untouched, which is the right default; overwrite only after checking the copy. Joins and relations referencing the layer by id keep working, because the id does not change.",[175,1377,1379],{"id":1378},"run-safely-on-a-project-people-are-using","Run safely on a project people are using",[14,1381,1382],{},"Running a batch in the Python console against an open project has risks a standalone script does not: layers in edit mode, unsaved changes, a user still working. A few guards make it safe.",[209,1384,1386],{"className":211,"code":1385,"language":213,"meta":214,"style":214},"from qgis.utils import iface\n\nediting = [l.name() for _, l, _ in selected if l.isEditable() or l.isModified()]\nif editing:\n    raise RuntimeError(f\"save or close edits first: {editing}\")\nif project.isDirty():\n    iface.messageBar().pushWarning(\"Batch\", \"Project has unsaved changes — save a copy before swapping sources\")\n\nbackup = out_dir \u002F f\"{project.baseName()}_before_batch.qgz\"\nproject.write(str(backup))\nprint(\"backup written to\", backup)\n",[204,1387,1388,1400,1404,1434,1441,1467,1474,1489,1493,1518,1527],{"__ignoreMap":214},[218,1389,1390,1392,1395,1397],{"class":109,"line":220},[218,1391,224],{"class":223},[218,1393,1394],{"class":227}," qgis.utils ",[218,1396,231],{"class":223},[218,1398,1399],{"class":227}," iface\n",[218,1401,1402],{"class":109,"line":237},[218,1403,241],{"emptyLinePlaceholder":240},[218,1405,1406,1409,1411,1414,1416,1419,1421,1424,1426,1429,1431],{"class":109,"line":244},[218,1407,1408],{"class":227},"editing ",[218,1410,250],{"class":223},[218,1412,1413],{"class":227}," [l.name() ",[218,1415,397],{"class":223},[218,1417,1418],{"class":227}," _, l, _ ",[218,1420,314],{"class":223},[218,1422,1423],{"class":227}," selected ",[218,1425,787],{"class":223},[218,1427,1428],{"class":227}," l.isEditable() ",[218,1430,299],{"class":223},[218,1432,1433],{"class":227}," l.isModified()]\n",[218,1435,1436,1438],{"class":109,"line":256},[218,1437,787],{"class":223},[218,1439,1440],{"class":227}," editing:\n",[218,1442,1443,1446,1449,1451,1453,1456,1458,1461,1463,1465],{"class":109,"line":261},[218,1444,1445],{"class":223},"    raise",[218,1447,1448],{"class":276}," RuntimeError",[218,1450,414],{"class":227},[218,1452,417],{"class":223},[218,1454,1455],{"class":382},"\"save or close edits first: ",[218,1457,423],{"class":276},[218,1459,1460],{"class":227},"editing",[218,1462,437],{"class":276},[218,1464,420],{"class":382},[218,1466,462],{"class":227},[218,1468,1469,1471],{"class":109,"line":288},[218,1470,787],{"class":223},[218,1472,1473],{"class":227}," project.isDirty():\n",[218,1475,1476,1479,1482,1484,1487],{"class":109,"line":305},[218,1477,1478],{"class":227},"    iface.messageBar().pushWarning(",[218,1480,1481],{"class":382},"\"Batch\"",[218,1483,625],{"class":227},[218,1485,1486],{"class":382},"\"Project has unsaved changes — save a copy before swapping sources\"",[218,1488,462],{"class":227},[218,1490,1491],{"class":109,"line":320},[218,1492,241],{"emptyLinePlaceholder":240},[218,1494,1495,1498,1500,1503,1505,1507,1509,1511,1513,1515],{"class":109,"line":332},[218,1496,1497],{"class":227},"backup ",[218,1499,250],{"class":223},[218,1501,1502],{"class":227}," out_dir ",[218,1504,884],{"class":223},[218,1506,1278],{"class":223},[218,1508,420],{"class":382},[218,1510,423],{"class":276},[218,1512,1354],{"class":227},[218,1514,437],{"class":276},[218,1516,1517],{"class":382},"_before_batch.qgz\"\n",[218,1519,1520,1522,1524],{"class":109,"line":347},[218,1521,1339],{"class":227},[218,1523,1076],{"class":276},[218,1525,1526],{"class":227},"(backup))\n",[218,1528,1529,1531,1533,1536],{"class":109,"line":376},[218,1530,802],{"class":276},[218,1532,414],{"class":227},[218,1534,1535],{"class":382},"\"backup written to\"",[218,1537,1538],{"class":227},", backup)\n",[14,1540,1541,1543,1544,1548],{},[466,1542,468],{}," Refusing to run while any selected layer is in edit mode avoids processing a mix of saved and unsaved features — Processing reads the edit buffer for layer objects, which may or may not be what the user intends. Writing a backup copy of the project before swapping data sources makes the change reversible in one step. For long batches inside QGIS, move the work into a ",[21,1545,1547],{"href":1546},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Frun-background-task-with-qgstask-pyqgis\u002F","background task"," so the interface stays responsive, applying source swaps on the main thread when it finishes.",[175,1550,1552],{"id":1551},"report-per-layer","Report per layer",[14,1554,1555],{},"A batch over a project should end with a short report: every layer, what happened, how many features, where the output went.",[14,1557,1558],{},[29,1559,1562,1565,1568,1571,1574,1577,1581,1585,1589,1593,1595,1599,1603],{"viewBox":1560,"role":32,"ariaLabel":1561,"xmlns":34},"0 0 760 230","A per-layer report listing processed layers with status, feature counts and outputs, and skipped layers with reasons",[36,1563,1564],{},"One line per layer",[40,1566,1567],{},"The report lists every processed layer with status ok or failed with the message, the number of features written and the output location, followed by every skipped layer with the reason. Written as CSV next to the outputs, it answers questions about the run without rerunning it.",[44,1569],{"x":46,"y":46,"width":47,"height":1570,"fill":49},"230",[68,1572,1573],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Processed and skipped, both explained",[44,1575],{"x":78,"y":79,"width":505,"height":1576,"rx":58,"fill":154,"stroke":155,"style":84},"150",[68,1578,1580],{"x":509,"y":1579,"style":89,"fill":160,"textAnchor":74},"92.78","processed",[68,1582,1584],{"x":509,"y":1583,"style":94,"fill":66,"textAnchor":74},"120.78","layer · status",[68,1586,1588],{"x":509,"y":1587,"style":94,"fill":66,"textAnchor":74},"148.78","features · output",[68,1590,1592],{"x":509,"y":1591,"style":94,"fill":83,"textAnchor":74},"176.78","ok or error text",[44,1594],{"x":525,"y":79,"width":505,"height":1576,"rx":58,"fill":82,"stroke":83,"style":84},[68,1596,1598],{"x":528,"y":1597,"style":89,"fill":73,"textAnchor":74},"106.78","skipped",[68,1600,1602],{"x":528,"y":1601,"style":94,"fill":66,"textAnchor":74},"134.78","layer · reason",[68,1604,1606],{"x":528,"y":1605,"style":94,"fill":83,"textAnchor":74},"162.78","invalid, type, group",[209,1608,1610],{"className":211,"code":1609,"language":213,"meta":214,"style":214},"import csv\n\nwith open(out_dir \u002F \"batch_report.csv\", \"w\", newline=\"\", encoding=\"utf-8\") as fh:\n    w = csv.writer(fh)\n    w.writerow([\"layer\", \"status\", \"features\", \"output\"])\n    w.writerows(report)\n    for group, layer, why in skipped:\n        w.writerow([layer.name(), f\"skipped: {why}\", \"\", \"\"])\nfailed = [r for r in report if r[1] != \"ok\"]\nprint(f\"{len(report) - len(failed)} ok, {len(failed)} failed, {len(skipped)} skipped\")\n",[204,1611,1612,1619,1623,1670,1680,1706,1711,1723,1752,1789],{"__ignoreMap":214},[218,1613,1614,1616],{"class":109,"line":220},[218,1615,231],{"class":223},[218,1617,1618],{"class":227}," csv\n",[218,1620,1621],{"class":109,"line":237},[218,1622,241],{"emptyLinePlaceholder":240},[218,1624,1625,1628,1631,1633,1635,1638,1640,1643,1645,1648,1650,1652,1654,1657,1659,1662,1664,1667],{"class":109,"line":244},[218,1626,1627],{"class":223},"with",[218,1629,1630],{"class":276}," open",[218,1632,1079],{"class":227},[218,1634,884],{"class":223},[218,1636,1637],{"class":382}," \"batch_report.csv\"",[218,1639,625],{"class":227},[218,1641,1642],{"class":382},"\"w\"",[218,1644,625],{"class":227},[218,1646,1647],{"class":895},"newline",[218,1649,250],{"class":223},[218,1651,1161],{"class":382},[218,1653,625],{"class":227},[218,1655,1656],{"class":895},"encoding",[218,1658,250],{"class":223},[218,1660,1661],{"class":382},"\"utf-8\"",[218,1663,881],{"class":227},[218,1665,1666],{"class":223},"as",[218,1668,1669],{"class":227}," fh:\n",[218,1671,1672,1675,1677],{"class":109,"line":256},[218,1673,1674],{"class":227},"    w ",[218,1676,250],{"class":223},[218,1678,1679],{"class":227}," csv.writer(fh)\n",[218,1681,1682,1685,1688,1690,1693,1695,1698,1700,1703],{"class":109,"line":261},[218,1683,1684],{"class":227},"    w.writerow([",[218,1686,1687],{"class":382},"\"layer\"",[218,1689,625],{"class":227},[218,1691,1692],{"class":382},"\"status\"",[218,1694,625],{"class":227},[218,1696,1697],{"class":382},"\"features\"",[218,1699,625],{"class":227},[218,1701,1702],{"class":382},"\"output\"",[218,1704,1705],{"class":227},"])\n",[218,1707,1708],{"class":109,"line":288},[218,1709,1710],{"class":227},"    w.writerows(report)\n",[218,1712,1713,1715,1718,1720],{"class":109,"line":305},[218,1714,308],{"class":223},[218,1716,1717],{"class":227}," group, layer, why ",[218,1719,314],{"class":223},[218,1721,1722],{"class":227}," skipped:\n",[218,1724,1725,1728,1730,1733,1735,1738,1740,1742,1744,1746,1748,1750],{"class":109,"line":320},[218,1726,1727],{"class":227},"        w.writerow([layer.name(), ",[218,1729,417],{"class":223},[218,1731,1732],{"class":382},"\"skipped: ",[218,1734,423],{"class":276},[218,1736,1737],{"class":227},"why",[218,1739,437],{"class":276},[218,1741,420],{"class":382},[218,1743,625],{"class":227},[218,1745,1161],{"class":382},[218,1747,625],{"class":227},[218,1749,1161],{"class":382},[218,1751,1705],{"class":227},[218,1753,1754,1757,1759,1762,1764,1767,1769,1772,1774,1777,1779,1782,1784,1787],{"class":109,"line":332},[218,1755,1756],{"class":227},"failed ",[218,1758,250],{"class":223},[218,1760,1761],{"class":227}," [r ",[218,1763,397],{"class":223},[218,1765,1766],{"class":227}," r ",[218,1768,314],{"class":223},[218,1770,1771],{"class":227}," report ",[218,1773,787],{"class":223},[218,1775,1776],{"class":227}," r[",[218,1778,1103],{"class":276},[218,1780,1781],{"class":227},"] ",[218,1783,660],{"class":223},[218,1785,1786],{"class":382}," \"ok\"",[218,1788,1048],{"class":227},[218,1790,1791,1793,1795,1797,1799,1802,1805,1808,1811,1814,1816,1819,1821,1823,1825,1828,1830,1833,1835,1838],{"class":109,"line":347},[218,1792,802],{"class":276},[218,1794,414],{"class":227},[218,1796,417],{"class":223},[218,1798,420],{"class":382},[218,1800,1801],{"class":276},"{len",[218,1803,1804],{"class":227},"(report) ",[218,1806,1807],{"class":223},"-",[218,1809,1810],{"class":276}," len",[218,1812,1813],{"class":227},"(failed)",[218,1815,437],{"class":276},[218,1817,1818],{"class":382}," ok, ",[218,1820,1801],{"class":276},[218,1822,1813],{"class":227},[218,1824,437],{"class":276},[218,1826,1827],{"class":382}," failed, ",[218,1829,1801],{"class":276},[218,1831,1832],{"class":227},"(skipped)",[218,1834,437],{"class":276},[218,1836,1837],{"class":382}," skipped\"",[218,1839,462],{"class":227},[14,1841,1842,1844,1845,193],{},[466,1843,468],{}," Including skipped layers with their reasons makes the report a complete account of the project, not just of what succeeded. Failures carry the exception message, which is usually enough to fix the cause and rerun only those layers. For runs that take long enough to be interrupted, combine this with ",[21,1846,1848],{"href":1847},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Fresume-and-checkpoint-a-batch-run-pyqgis\u002F","checkpointing a batch run",[175,1850,1852],{"id":1851},"run-against-many-projects","Run against many projects",[14,1854,1855],{},"The same function applied to a folder of projects processes an organisation's whole project collection — every project's deliverables exported nightly, for example.",[209,1857,1859],{"className":211,"code":1858,"language":213,"meta":214,"style":214},"from qgis.core import QgsProject\n\nfor qgz in sorted(Path(\"\u002Fdata\u002Fprojects\").glob(\"*.qgz\")):\n    p = QgsProject.instance()\n    p.clear()\n    if not p.read(str(qgz)):\n        print(\"cannot read\", qgz.name)\n        continue\n    print(\"processing\", qgz.name)\n    # call the selection and processing steps above here\n",[204,1860,1861,1872,1876,1903,1912,1917,1931,1943,1948,1959],{"__ignoreMap":214},[218,1862,1863,1865,1867,1869],{"class":109,"line":220},[218,1864,224],{"class":223},[218,1866,228],{"class":227},[218,1868,231],{"class":223},[218,1870,1871],{"class":227}," QgsProject\n",[218,1873,1874],{"class":109,"line":237},[218,1875,241],{"emptyLinePlaceholder":240},[218,1877,1878,1880,1883,1885,1888,1891,1894,1897,1900],{"class":109,"line":244},[218,1879,397],{"class":223},[218,1881,1882],{"class":227}," qgz ",[218,1884,314],{"class":223},[218,1886,1887],{"class":276}," sorted",[218,1889,1890],{"class":227},"(Path(",[218,1892,1893],{"class":382},"\"\u002Fdata\u002Fprojects\"",[218,1895,1896],{"class":227},").glob(",[218,1898,1899],{"class":382},"\"*.qgz\"",[218,1901,1902],{"class":227},")):\n",[218,1904,1905,1908,1910],{"class":109,"line":256},[218,1906,1907],{"class":227},"    p ",[218,1909,250],{"class":223},[218,1911,253],{"class":227},[218,1913,1914],{"class":109,"line":261},[218,1915,1916],{"class":227},"    p.clear()\n",[218,1918,1919,1921,1923,1926,1928],{"class":109,"line":288},[218,1920,609],{"class":223},[218,1922,367],{"class":223},[218,1924,1925],{"class":227}," p.read(",[218,1927,1076],{"class":276},[218,1929,1930],{"class":227},"(qgz)):\n",[218,1932,1933,1935,1937,1940],{"class":109,"line":305},[218,1934,1326],{"class":276},[218,1936,414],{"class":227},[218,1938,1939],{"class":382},"\"cannot read\"",[218,1941,1942],{"class":227},", qgz.name)\n",[218,1944,1945],{"class":109,"line":320},[218,1946,1947],{"class":223},"        continue\n",[218,1949,1950,1952,1954,1957],{"class":109,"line":332},[218,1951,411],{"class":276},[218,1953,414],{"class":227},[218,1955,1956],{"class":382},"\"processing\"",[218,1958,1942],{"class":227},[218,1960,1961],{"class":109,"line":347},[218,1962,1964],{"class":1963},"sjoCn","    # call the selection and processing steps above here\n",[14,1966,1967,1969,1970,193],{},[466,1968,468],{}," Clearing and reading each project in turn reuses the same project instance; everything above then runs against the loaded project. Projects with broken layer paths load with invalid layers, which the validity rule skips and reports. In standalone scripts, read projects with flags that skip loading layouts or data you do not need for speed, as in ",[21,1971,1973],{"href":1972},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-projects\u002Fopen-project-with-read-flags-pyqgis\u002F","opening a project with read flags",[175,1975,1977],{"id":1976},"qgis-version-compatibility","QGIS version compatibility",[14,1979,1980,1981,1983,1984,1986,1987,1990,1991,193],{},"Layer tree traversal, ",[204,1982,1369],{}," and Processing calls work on QGIS 3.34 LTR, 3.40 LTR and QGIS 4. ",[204,1985,1369],{}," with provider options has existed since 3.20. On QGIS 4, ",[204,1988,1989],{},"QgsWkbTypes.PolygonGeometry"," is ",[204,1992,1993],{},"Qgis.GeometryType.Polygon",[175,1995,1997],{"id":1996},"troubleshooting","Troubleshooting",[180,1999,2000,2009,2015,2021],{},[183,2001,2002,1366,2005,2008],{},[466,2003,2004],{},"Layers are processed in a random order.",[204,2006,2007],{},"mapLayers()"," was used; walk the layer tree instead.",[183,2010,2011,2014],{},[466,2012,2013],{},"Subset filters were ignored."," File paths were passed instead of layer objects.",[183,2016,2017,2020],{},[466,2018,2019],{},"Output tables overwrite each other."," Names collide after cleaning; include the group path or a counter.",[183,2022,2023,2026,2027,193],{},[466,2024,2025],{},"The project lost styles after swapping."," A new layer was added instead of repointing; use ",[204,2028,1369],{},[175,2030,2032],{"id":2031},"conclusion","Conclusion",[14,2034,2035,2036,2038],{},"Walk the layer tree to follow the project's order and groups, select layers with explicit rules that record reasons for skipping, pass layer objects to Processing so filters and joins are respected, write outputs with names derived from group and layer, repoint layers with ",[204,2037,1369],{}," when the project should change, and finish with a per-layer report.",[175,2040,2042],{"id":2041},"frequently-asked-questions","Frequently Asked Questions",[14,2044,2045,2048,2049,2052],{},[466,2046,2047],{},"Can I process only visible layers?","\nYes: check ",[204,2050,2051],{},"QgsLayerTreeLayer.isVisible()"," in the tree walk.",[14,2054,2055,2058,2059,2062],{},[466,2056,2057],{},"How do I include raster layers?","\nExtend the rules to accept ",[204,2060,2061],{},"QgsRasterLayer"," and use raster algorithms for them.",[14,2064,2065,2068],{},[466,2066,2067],{},"Does this work in the Python console?","\nYes, against the open project; save a copy before swapping sources.",[14,2070,2071,2074],{},[466,2072,2073],{},"Can a model do this?","\nModels iterate features, not project layers; a script is the right tool.",[175,2076,2078],{"id":2077},"related","Related",[180,2080,2081,2086,2092,2098,2104],{},[183,2082,2083,2085],{},[21,2084,24],{"href":23}," — the guide this recipe belongs to",[183,2087,2088],{},[21,2089,2091],{"href":2090},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Frun-algorithm-over-folder-of-files-pyqgis\u002F","Run an Algorithm Over a Folder of Files in PyQGIS",[183,2093,2094],{},[21,2095,2097],{"href":2096},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Frun-batch-jobs-from-csv-parameter-table-pyqgis\u002F","Run Batch Jobs from a CSV Parameter Table in PyQGIS",[183,2099,2100],{},[21,2101,2103],{"href":2102},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-projects\u002Forganise-layer-tree-groups-pyqgis\u002F","Organise Layer Tree Groups in PyQGIS",[183,2105,2106],{},[21,2107,2108],{"href":1373},"Fix Broken Layer Paths in a Project in PyQGIS",[2110,2111,2112],"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 .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}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);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}",{"title":214,"searchDepth":237,"depth":237,"links":2114},[2115,2116,2117,2118,2119,2120,2121,2122,2123,2124,2125,2126,2127],{"id":177,"depth":237,"text":178},{"id":199,"depth":237,"text":200},{"id":480,"depth":237,"text":481},{"id":833,"depth":237,"text":834},{"id":1184,"depth":237,"text":1185},{"id":1378,"depth":237,"text":1379},{"id":1551,"depth":237,"text":1552},{"id":1851,"depth":237,"text":1852},{"id":1976,"depth":237,"text":1977},{"id":1996,"depth":237,"text":1997},{"id":2031,"depth":237,"text":2032},{"id":2041,"depth":237,"text":2042},{"id":2077,"depth":237,"text":2078},"Apply the same operation to every layer in a QGIS project — iterating layers by type, group or name pattern, skipping what does not apply, running Processing per layer, writing results beside or into the project, and reporting what happened to each layer.","md",{"slug":2131,"type":2132,"breadcrumb":2133,"datePublished":2134,"dateModified":2134},"batch-process-all-layers-in-project-pyqgis","article","Batch Process All Layers in a Project","2026-10-02","\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Fbatch-process-all-layers-in-project-pyqgis",{"title":5,"description":2128},"spatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Fbatch-process-all-layers-in-project-pyqgis\u002Findex","4AsVeoa5V0QtBiX9k5uG4iehNiOmzjzqG4kYvRwCUVU",1790966260687]