[{"data":1,"prerenderedAt":1484},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002Fcatch-exceptions-and-tracebacks-pyqgis":3},{"id":4,"title":5,"body":6,"description":1473,"extension":1474,"meta":1475,"navigation":288,"path":1480,"seo":1481,"stem":1482,"__hash__":1483},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002Fcatch-exceptions-and-tracebacks-pyqgis\u002Findex.md","Catch Exceptions and Read Tracebacks in PyQGIS",{"type":7,"value":8,"toc":1460},"minimark",[9,13,26,35,152,157,174,178,181,184,234,252,256,478,496,503,558,567,571,574,723,739,822,826,832,945,954,1050,1054,1057,1217,1230,1234,1299,1305,1309,1361,1365,1368,1372,1378,1387,1401,1410,1416,1425,1429,1456],[10,11,5],"h1",{"id":12},"catch-exceptions-and-read-tracebacks-in-pyqgis",[14,15,16,17,21,22,25],"p",{},"PyQGIS has two failure modes and only one of them raises. Python errors — a typo, a ",[18,19,20],"code",{},"None",", a bad index — produce a traceback you can read. The C++ API underneath mostly does not raise at all: it returns ",[18,23,24],{},"False",", or an invalid layer, or an error code you did not check, and the script carries on until something unrelated breaks twenty lines later. Handling both is what separates a script that tells you what went wrong from one that produces an empty output and a shrug.",[14,27,28,29,34],{},"This recipe belongs to ",[30,31,33],"a",{"href":32},"\u002Fpyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002F","Debugging PyQGIS Scripts",". It covers reading a traceback quickly, catching exceptions where you can act on them, checking the return values that never raise, and logging failures in a form that is useful when nobody is watching.",[14,36,37],{},[38,39,44,48,52,59,76,85,95,101,104,109,116,121,126,130,137,143,147],"svg",{"viewBox":40,"role":41,"ariaLabel":42,"xmlns":43},"0 0 760 268","img","Diagram of a traceback read from the bottom upwards, showing the exception type and message at the bottom, the failing line above it, and the chain of calls that led there further up","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[45,46,47],"title",{},"Read a traceback from the bottom up",[49,50,51],"desc",{},"The last line of a traceback names the exception type and message and is where to start. The frame directly above it is the line that actually failed, usually inside your own code. Further up are the calls that led there, and at the very top is the entry point, which is the least informative part despite being read first by most people.",[53,54],"rect",{"x":55,"y":55,"width":56,"height":57,"fill":58},"0","760","268","#f6f3ea",[60,61,62],"defs",{},[63,64,71],"marker",{"id":65,"viewBox":66,"refX":67,"refY":68,"markerWidth":69,"markerHeight":69,"orient":70},"tbArrow","0 0 10 10","8","5","7","auto-start-reverse",[72,73],"path",{"d":74,"fill":75},"M0 0 L10 5 L0 10 z","#b91c1c",[77,78,84],"text",{"x":79,"y":80,"style":81,"fill":82,"textAnchor":83},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","The useful part is at the end, not the start",[53,86],{"x":87,"y":88,"width":89,"height":90,"rx":91,"fill":92,"stroke":93,"style":94},"60","48","560","38","6","#fffdf7","#59645f","stroke-width:1.5",[77,96,100],{"x":97,"y":98,"style":99,"fill":93},"76","72","font-size:11px;font-family:sans-serif","File console.py, line 1, in module — the entry point, least useful",[53,102],{"x":87,"y":103,"width":89,"height":90,"rx":91,"fill":92,"stroke":93,"style":94},"92",[77,105,108],{"x":97,"y":106,"style":99,"fill":107},"116","#2f3b35","File tools.py, line 44, in run — how you got here",[53,110],{"x":87,"y":111,"width":89,"height":112,"rx":91,"fill":113,"stroke":114,"style":115},"136","42","#eef7f4","#0f766e","stroke-width:2.5",[77,117,120],{"x":97,"y":118,"style":119,"fill":114},"162","font-size:11px;font-weight:bold;font-family:sans-serif","File tools.py, line 91, in summarise — the line that failed",[53,122],{"x":87,"y":123,"width":89,"height":124,"rx":91,"fill":125,"stroke":75,"style":115},"184","44","#fdf2e2",[77,127,129],{"x":97,"y":128,"style":119,"fill":75},"212","AttributeError: NoneType object has no attribute getFeatures",[131,132],"line",{"x1":133,"y1":134,"x2":133,"y2":135,"stroke":75,"style":136},"640","206","160","stroke-width:2;marker-end:url(#tbArrow)",[77,138,142],{"x":139,"y":140,"style":141,"fill":75,"textAnchor":83},"700","200","text-anchor:middle;font-size:10px;font-family:sans-serif","read",[77,144,146],{"x":139,"y":145,"style":141,"fill":75,"textAnchor":83},"182","upward",[77,148,151],{"x":79,"y":149,"style":150,"fill":93,"textAnchor":83},"254","text-anchor:middle;font-size:11px;font-family:sans-serif","Type and message first, then the deepest frame in your own code",[153,154,156],"h2",{"id":155},"prerequisites","Prerequisites",[158,159,160,168,171],"ul",{},[161,162,163,167],"li",{},[164,165,166],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[161,169,170],{},"The Python console open, and a script that fails — the easiest kind to obtain.",[161,172,173],{},"The Log Messages panel visible, from the icon at the bottom right of the QGIS window.",[153,175,177],{"id":176},"read-the-traceback-in-the-right-order","Read the traceback in the right order",[14,179,180],{},"Start at the bottom. The final line gives the exception type and message, and those two together usually identify the problem. The frame immediately above it is the line that failed, which in a PyQGIS script is nearly always in your own code rather than in QGIS. Work upward only if the failing line does not make sense on its own.",[14,182,183],{},"Three exception types cover most PyQGIS failures, and each has a characteristic cause:",[158,185,186,209,226],{},[161,187,188,193,194,196,197,200,201,204,205,208],{},[164,189,190],{},[18,191,192],{},"AttributeError: 'NoneType' object has no attribute ..."," — something returned ",[18,195,20],{}," and you used it anyway. ",[18,198,199],{},"iface.activeLayer()"," with no layer selected, ",[18,202,203],{},"mapLayersByName()"," indexed with ",[18,206,207],{},"[0]"," on an empty list, a field lookup that missed.",[161,210,211,221,222,225],{},[164,212,213,216,217,220],{},[18,214,215],{},"KeyError"," or ",[18,218,219],{},"IndexError"," on a feature"," — a field name that does not exist, or an attribute index that is off by one. Print ",[18,223,224],{},"layer.fields().names()"," and the answer is immediate.",[161,227,228,233],{},[164,229,230],{},[18,231,232],{},"TypeError: arguments did not match any overloaded call"," — the distinctive PyQt message meaning you passed the wrong type to a C++ method. The message lists the signatures it tried, which tells you exactly what was expected.",[14,235,236,237,240,241,244,245,240,248,251],{},"That last one deserves attention because it looks alarming and is usually trivial: a Python ",[18,238,239],{},"float"," where a ",[18,242,243],{},"QVariant"," was wanted, a ",[18,246,247],{},"str",[18,249,250],{},"QgsField"," was wanted, a list where a single value was wanted.",[153,253,255],{"id":254},"catch-what-you-can-actually-handle","Catch what you can actually handle",[257,258,263],"pre",{"className":259,"code":260,"language":261,"meta":262,"style":262},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProject, QgsVectorLayer\n\n\ndef load_parcels(path):\n    layer = QgsVectorLayer(path, \"Parcels\", \"ogr\")\n    if not layer.isValid():\n        raise RuntimeError(f\"cannot open {path}: {layer.error().summary()}\")\n    return layer\n\n\ntry:\n    layer = load_parcels(\"\u002Fdata\u002Fcity.gpkg|layername=parcels\")\nexcept RuntimeError as error:\n    print(f\"Skipping this dataset — {error}\")\n    layer = None\n","python","",[18,264,265,283,290,295,308,333,345,387,396,401,406,415,430,444,468],{"__ignoreMap":262},[266,267,269,273,277,280],"span",{"class":131,"line":268},1,[266,270,272],{"class":271},"snl16","from",[266,274,276],{"class":275},"s95oV"," qgis.core ",[266,278,279],{"class":271},"import",[266,281,282],{"class":275}," QgsProject, QgsVectorLayer\n",[266,284,286],{"class":131,"line":285},2,[266,287,289],{"emptyLinePlaceholder":288},true,"\n",[266,291,293],{"class":131,"line":292},3,[266,294,289],{"emptyLinePlaceholder":288},[266,296,298,301,305],{"class":131,"line":297},4,[266,299,300],{"class":271},"def",[266,302,304],{"class":303},"svObZ"," load_parcels",[266,306,307],{"class":275},"(path):\n",[266,309,311,314,317,320,324,327,330],{"class":131,"line":310},5,[266,312,313],{"class":275},"    layer ",[266,315,316],{"class":271},"=",[266,318,319],{"class":275}," QgsVectorLayer(path, ",[266,321,323],{"class":322},"sU2Wk","\"Parcels\"",[266,325,326],{"class":275},", ",[266,328,329],{"class":322},"\"ogr\"",[266,331,332],{"class":275},")\n",[266,334,336,339,342],{"class":131,"line":335},6,[266,337,338],{"class":271},"    if",[266,340,341],{"class":271}," not",[266,343,344],{"class":275}," layer.isValid():\n",[266,346,348,351,355,358,361,364,367,369,372,375,377,380,382,385],{"class":131,"line":347},7,[266,349,350],{"class":271},"        raise",[266,352,354],{"class":353},"sDLfK"," RuntimeError",[266,356,357],{"class":275},"(",[266,359,360],{"class":271},"f",[266,362,363],{"class":322},"\"cannot open ",[266,365,366],{"class":353},"{",[266,368,72],{"class":275},[266,370,371],{"class":353},"}",[266,373,374],{"class":322},": ",[266,376,366],{"class":353},[266,378,379],{"class":275},"layer.error().summary()",[266,381,371],{"class":353},[266,383,384],{"class":322},"\"",[266,386,332],{"class":275},[266,388,390,393],{"class":131,"line":389},8,[266,391,392],{"class":271},"    return",[266,394,395],{"class":275}," layer\n",[266,397,399],{"class":131,"line":398},9,[266,400,289],{"emptyLinePlaceholder":288},[266,402,404],{"class":131,"line":403},10,[266,405,289],{"emptyLinePlaceholder":288},[266,407,409,412],{"class":131,"line":408},11,[266,410,411],{"class":271},"try",[266,413,414],{"class":275},":\n",[266,416,418,420,422,425,428],{"class":131,"line":417},12,[266,419,313],{"class":275},[266,421,316],{"class":271},[266,423,424],{"class":275}," load_parcels(",[266,426,427],{"class":322},"\"\u002Fdata\u002Fcity.gpkg|layername=parcels\"",[266,429,332],{"class":275},[266,431,433,436,438,441],{"class":131,"line":432},13,[266,434,435],{"class":271},"except",[266,437,354],{"class":353},[266,439,440],{"class":271}," as",[266,442,443],{"class":275}," error:\n",[266,445,447,450,452,454,457,459,462,464,466],{"class":131,"line":446},14,[266,448,449],{"class":353},"    print",[266,451,357],{"class":275},[266,453,360],{"class":271},[266,455,456],{"class":322},"\"Skipping this dataset — ",[266,458,366],{"class":353},[266,460,461],{"class":275},"error",[266,463,371],{"class":353},[266,465,384],{"class":322},[266,467,332],{"class":275},[266,469,471,473,475],{"class":131,"line":470},15,[266,472,313],{"class":275},[266,474,316],{"class":271},[266,476,477],{"class":353}," None\n",[14,479,480,483,484,486,487,489,490,492,493,495],{},[164,481,482],{},"Breakdown:"," The rule for ",[18,485,411],{},"\u002F",[18,488,435],{}," is to catch only where you can do something specific: skip this file, fall back to a default, retry once. Wrapping a whole script in a bare ",[18,491,435],{}," and printing \"an error occurred\" destroys the traceback and with it any chance of a quick fix. Note that the layer failure is converted into an exception first — the API returned an invalid object rather than raising, and turning that into a raise at the boundary is what lets the rest of the code use ordinary exception handling. ",[18,494,379],{}," gives the provider's own message, which distinguishes a missing file from a missing driver from a layer name that is not in the container.",[14,497,498,499,502],{},"Never catch ",[18,500,501],{},"Exception"," without re-raising unless the handler genuinely ends the operation:",[257,504,506],{"className":259,"code":505,"language":261,"meta":262,"style":262},"import traceback\n\ntry:\n    run_analysis()\nexcept Exception:\n    print(traceback.format_exc())     # keep the full detail\n    raise                             # and let it propagate\n",[18,507,508,515,519,525,530,539,550],{"__ignoreMap":262},[266,509,510,512],{"class":131,"line":268},[266,511,279],{"class":271},[266,513,514],{"class":275}," traceback\n",[266,516,517],{"class":131,"line":285},[266,518,289],{"emptyLinePlaceholder":288},[266,520,521,523],{"class":131,"line":292},[266,522,411],{"class":271},[266,524,414],{"class":275},[266,526,527],{"class":131,"line":297},[266,528,529],{"class":275},"    run_analysis()\n",[266,531,532,534,537],{"class":131,"line":310},[266,533,435],{"class":271},[266,535,536],{"class":353}," Exception",[266,538,414],{"class":275},[266,540,541,543,546],{"class":131,"line":335},[266,542,449],{"class":353},[266,544,545],{"class":275},"(traceback.format_exc())     ",[266,547,549],{"class":548},"sjoCn","# keep the full detail\n",[266,551,552,555],{"class":131,"line":347},[266,553,554],{"class":271},"    raise",[266,556,557],{"class":548},"                             # and let it propagate\n",[14,559,560,562,563,566],{},[164,561,482],{}," ",[18,564,565],{},"traceback.format_exc()"," returns the whole traceback as a string, which can be logged, written to a file or shown in a dialog. Re-raising afterwards means the failure still reaches whatever is above — a scheduler, a test runner, the console — instead of being silently absorbed. A handler that logs and swallows is the single most effective way to make a problem un-debuggable.",[153,568,570],{"id":569},"check-the-returns-that-never-raise","Check the returns that never raise",[14,572,573],{},"Much of the QGIS API predates exceptions and reports failure by return value. These are the ones worth checking every time:",[257,575,577],{"className":259,"code":576,"language":261,"meta":262,"style":262},"layer = QgsVectorLayer(uri, \"Parcels\", \"ogr\")\nassert layer.isValid()                                  # invalid, not raised\n\nok = layer.startEditing()                               # False if not editable\nok = layer.addFeatures([feature])                       # False on rejection\nok = layer.commitChanges()                              # False, errors in commitErrors()\nif not ok:\n    print(layer.commitErrors())\n\nerror, message, _, _ = QgsVectorFileWriter.writeAsVectorFormatV3(...)\nif error != QgsVectorFileWriter.NoError:\n    raise RuntimeError(message)\n\nok = QgsProject.instance().write(path)                  # False if the write failed\n",[18,578,579,597,608,612,625,637,649,659,666,670,685,698,707,711],{"__ignoreMap":262},[266,580,581,584,586,589,591,593,595],{"class":131,"line":268},[266,582,583],{"class":275},"layer ",[266,585,316],{"class":271},[266,587,588],{"class":275}," QgsVectorLayer(uri, ",[266,590,323],{"class":322},[266,592,326],{"class":275},[266,594,329],{"class":322},[266,596,332],{"class":275},[266,598,599,602,605],{"class":131,"line":285},[266,600,601],{"class":271},"assert",[266,603,604],{"class":275}," layer.isValid()                                  ",[266,606,607],{"class":548},"# invalid, not raised\n",[266,609,610],{"class":131,"line":292},[266,611,289],{"emptyLinePlaceholder":288},[266,613,614,617,619,622],{"class":131,"line":297},[266,615,616],{"class":275},"ok ",[266,618,316],{"class":271},[266,620,621],{"class":275}," layer.startEditing()                               ",[266,623,624],{"class":548},"# False if not editable\n",[266,626,627,629,631,634],{"class":131,"line":310},[266,628,616],{"class":275},[266,630,316],{"class":271},[266,632,633],{"class":275}," layer.addFeatures([feature])                       ",[266,635,636],{"class":548},"# False on rejection\n",[266,638,639,641,643,646],{"class":131,"line":335},[266,640,616],{"class":275},[266,642,316],{"class":271},[266,644,645],{"class":275}," layer.commitChanges()                              ",[266,647,648],{"class":548},"# False, errors in commitErrors()\n",[266,650,651,654,656],{"class":131,"line":347},[266,652,653],{"class":271},"if",[266,655,341],{"class":271},[266,657,658],{"class":275}," ok:\n",[266,660,661,663],{"class":131,"line":389},[266,662,449],{"class":353},[266,664,665],{"class":275},"(layer.commitErrors())\n",[266,667,668],{"class":131,"line":398},[266,669,289],{"emptyLinePlaceholder":288},[266,671,672,675,677,680,683],{"class":131,"line":403},[266,673,674],{"class":275},"error, message, _, _ ",[266,676,316],{"class":271},[266,678,679],{"class":275}," QgsVectorFileWriter.writeAsVectorFormatV3(",[266,681,682],{"class":353},"...",[266,684,332],{"class":275},[266,686,687,689,692,695],{"class":131,"line":408},[266,688,653],{"class":271},[266,690,691],{"class":275}," error ",[266,693,694],{"class":271},"!=",[266,696,697],{"class":275}," QgsVectorFileWriter.NoError:\n",[266,699,700,702,704],{"class":131,"line":417},[266,701,554],{"class":271},[266,703,354],{"class":353},[266,705,706],{"class":275},"(message)\n",[266,708,709],{"class":131,"line":432},[266,710,289],{"emptyLinePlaceholder":288},[266,712,713,715,717,720],{"class":131,"line":446},[266,714,616],{"class":275},[266,716,316],{"class":271},[266,718,719],{"class":275}," QgsProject.instance().write(path)                  ",[266,721,722],{"class":548},"# False if the write failed\n",[14,724,725,727,728,731,732,734,735,738],{},[164,726,482],{}," Every one of these returns a boolean or an error code that a script can ignore without any warning. ",[18,729,730],{},"commitChanges()"," is the most consequential: it returns ",[18,733,24],{}," when a constraint, a provider rejection or an invalid geometry blocked the commit, and ",[18,736,737],{},"commitErrors()"," holds the reason. A script that ignores it reports success while having written nothing — the exact failure mode that makes people distrust automation. The habit worth building is to treat every one of these calls as a potential raise site and convert it, as the loader above does.",[14,740,741],{},[38,742,745,748,751,753,760,763,769,775,779,783,788,791,795,798,801,804,811,814,818],{"viewBox":743,"role":41,"ariaLabel":744,"xmlns":43},"0 0 760 254","Comparison of two failure styles, Python exceptions that stop the script with a traceback and API return values that continue silently, with the recommended conversion between them",[45,746,747],{},"Two failure styles, one of them silent",[49,749,750],{},"Python errors raise an exception, which stops execution and produces a traceback naming the line. QGIS API calls typically return false or an invalid object, so execution continues and the failure surfaces later somewhere unrelated. Converting the second kind into the first at the point it happens gives every failure the same shape.",[53,752],{"x":55,"y":55,"width":56,"height":149,"fill":58},[60,754,755],{},[63,756,758],{"id":757,"viewBox":66,"refX":67,"refY":68,"markerWidth":69,"markerHeight":69,"orient":70},"failArrow",[72,759],{"d":74,"fill":114},[77,761,762],{"x":79,"y":80,"style":81,"fill":82,"textAnchor":83},"Make the quiet failures loud at the point they happen",[53,764],{"x":765,"y":88,"width":766,"height":767,"rx":768,"fill":113,"stroke":114,"style":115},"20","330","112","10",[77,770,774],{"x":771,"y":772,"style":773,"fill":114,"textAnchor":83},"185","74","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","Python errors",[77,776,778],{"x":771,"y":777,"style":150,"fill":107,"textAnchor":83},"100","raise and stop immediately",[77,780,782],{"x":771,"y":781,"style":150,"fill":107,"textAnchor":83},"122","traceback names the line",[77,784,787],{"x":771,"y":785,"style":150,"fill":786,"textAnchor":83},"144","#15803d","easy to diagnose",[53,789],{"x":790,"y":88,"width":766,"height":767,"rx":768,"fill":125,"stroke":75,"style":115},"410",[77,792,794],{"x":793,"y":772,"style":773,"fill":75,"textAnchor":83},"575","API return values",[77,796,797],{"x":793,"y":777,"style":150,"fill":107,"textAnchor":83},"return False or an invalid object",[77,799,800],{"x":793,"y":781,"style":150,"fill":107,"textAnchor":83},"execution continues",[77,802,803],{"x":793,"y":785,"style":150,"fill":75,"textAnchor":83},"surfaces later, somewhere else",[53,805],{"x":806,"y":807,"width":808,"height":809,"rx":67,"fill":810,"stroke":786,"style":115},"180","188","400","50","#edf8e9",[77,812,813],{"x":79,"y":128,"style":773,"fill":786,"textAnchor":83},"check the return, raise your own error",[77,815,817],{"x":79,"y":816,"style":150,"fill":107,"textAnchor":83},"230","now both kinds fail the same way, at the right line",[131,819],{"x1":793,"y1":135,"x2":820,"y2":123,"stroke":114,"style":821},"470","stroke-width:2;marker-end:url(#failArrow)",[153,823,825],{"id":824},"log-failures-where-they-can-be-found-later","Log failures where they can be found later",[14,827,828,831],{},[18,829,830],{},"print()"," is fine in the console and useless in a plugin or a scheduled job. QGIS's message log is the right destination for anything a user might need to see.",[257,833,835],{"className":259,"code":834,"language":261,"meta":262,"style":262},"from qgis.core import QgsMessageLog, Qgis\nimport traceback\n\ntry:\n    run_analysis(layer)\nexcept Exception as error:\n    QgsMessageLog.logMessage(\n        f\"Analysis failed on {layer.name()}: {error}\\n{traceback.format_exc()}\",\n        \"Parcel Tools\",\n        level=Qgis.Critical,\n    )\n    raise\n",[18,836,837,848,854,858,864,869,879,884,917,924,935,940],{"__ignoreMap":262},[266,838,839,841,843,845],{"class":131,"line":268},[266,840,272],{"class":271},[266,842,276],{"class":275},[266,844,279],{"class":271},[266,846,847],{"class":275}," QgsMessageLog, Qgis\n",[266,849,850,852],{"class":131,"line":285},[266,851,279],{"class":271},[266,853,514],{"class":275},[266,855,856],{"class":131,"line":292},[266,857,289],{"emptyLinePlaceholder":288},[266,859,860,862],{"class":131,"line":297},[266,861,411],{"class":271},[266,863,414],{"class":275},[266,865,866],{"class":131,"line":310},[266,867,868],{"class":275},"    run_analysis(layer)\n",[266,870,871,873,875,877],{"class":131,"line":335},[266,872,435],{"class":271},[266,874,536],{"class":353},[266,876,440],{"class":271},[266,878,443],{"class":275},[266,880,881],{"class":131,"line":347},[266,882,883],{"class":275},"    QgsMessageLog.logMessage(\n",[266,885,886,889,892,894,897,899,901,903,905,908,910,912,914],{"class":131,"line":389},[266,887,888],{"class":271},"        f",[266,890,891],{"class":322},"\"Analysis failed on ",[266,893,366],{"class":353},[266,895,896],{"class":275},"layer.name()",[266,898,371],{"class":353},[266,900,374],{"class":322},[266,902,366],{"class":353},[266,904,461],{"class":275},[266,906,907],{"class":353},"}\\n{",[266,909,565],{"class":275},[266,911,371],{"class":353},[266,913,384],{"class":322},[266,915,916],{"class":275},",\n",[266,918,919,922],{"class":131,"line":398},[266,920,921],{"class":322},"        \"Parcel Tools\"",[266,923,916],{"class":275},[266,925,926,930,932],{"class":131,"line":403},[266,927,929],{"class":928},"s9osk","        level",[266,931,316],{"class":271},[266,933,934],{"class":275},"Qgis.Critical,\n",[266,936,937],{"class":131,"line":408},[266,938,939],{"class":275},"    )\n",[266,941,942],{"class":131,"line":417},[266,943,944],{"class":271},"    raise\n",[14,946,947,949,950,953],{},[164,948,482],{}," The second argument is the tag, which becomes a tab in the Log Messages panel — using your plugin's name keeps your output separate from QGIS's own. Including both the exception and the formatted traceback means a user can copy one block that contains everything you need. The ",[18,951,952],{},"Critical"," level makes the panel's indicator turn red, which is the only reliable way a user notices something happened. Adding the layer name to the message is the difference between a report you can act on and one that says only that something failed.",[14,955,956,1039,1040,1044,1045,1049],{},[38,957,960,963,966,969,972,980,985,989,993,998,1001,1004,1007,1010,1013,1016,1019,1023,1027,1030,1033,1036],{"viewBox":958,"role":41,"ariaLabel":959,"xmlns":43},"0 0 760 248","Diagram matching each destination for an error message to the context it suits, from the console during development to the message log in a plugin and a file in a scheduled job",[45,961,962],{},"Where an error message should go",[49,964,965],{},"During development the console is the right destination because you are watching it. In a plugin the QGIS message log is right, because the user can find it and copy it. In a scheduled job a log file plus a non-zero exit status is right, because nobody is watching and a monitoring system needs a signal.",[53,967],{"x":55,"y":55,"width":56,"height":968,"fill":58},"248",[77,970,971],{"x":79,"y":80,"style":81,"fill":82,"textAnchor":83},"Nobody is watching standard output at three in the morning",[53,973],{"x":974,"y":975,"width":976,"height":977,"rx":768,"fill":978,"stroke":979,"style":115},"16","52","232","172","#eff3ff","#2563eb",[77,981,984],{"x":982,"y":983,"style":773,"fill":979,"textAnchor":83},"132","78","in the console",[77,986,988],{"x":982,"y":987,"style":150,"fill":107,"textAnchor":83},"106","print and the traceback",[77,990,992],{"x":982,"y":991,"style":150,"fill":107,"textAnchor":83},"130","you are looking at it",[53,994],{"x":995,"y":996,"width":123,"height":997,"rx":91,"fill":92,"stroke":979,"style":94},"40","152","46",[77,999,1000],{"x":982,"y":806,"style":150,"fill":107,"textAnchor":83},"development only",[53,1002],{"x":1003,"y":975,"width":976,"height":977,"rx":768,"fill":113,"stroke":114,"style":115},"264",[77,1005,1006],{"x":79,"y":983,"style":773,"fill":114,"textAnchor":83},"in a plugin",[77,1008,1009],{"x":79,"y":987,"style":150,"fill":107,"textAnchor":83},"QgsMessageLog with a tag",[77,1011,1012],{"x":79,"y":991,"style":150,"fill":107,"textAnchor":83},"Critical turns the light red",[53,1014],{"x":1015,"y":996,"width":123,"height":997,"rx":91,"fill":92,"stroke":114,"style":94},"288",[77,1017,1018],{"x":79,"y":806,"style":150,"fill":107,"textAnchor":83},"the user can copy it",[53,1020],{"x":1021,"y":975,"width":976,"height":977,"rx":768,"fill":125,"stroke":1022,"style":115},"512","#b45309",[77,1024,1026],{"x":1025,"y":983,"style":773,"fill":1022,"textAnchor":83},"628","in a scheduled job",[77,1028,1029],{"x":1025,"y":987,"style":150,"fill":107,"textAnchor":83},"a log file with timestamps",[77,1031,1032],{"x":1025,"y":991,"style":150,"fill":107,"textAnchor":83},"and a non-zero exit",[53,1034],{"x":1035,"y":996,"width":123,"height":997,"rx":91,"fill":92,"stroke":1022,"style":94},"536",[77,1037,1038],{"x":1025,"y":806,"style":150,"fill":107,"textAnchor":83},"monitoring can see it"," The wider logging patterns, including writing to files for unattended runs, are in ",[30,1041,1043],{"href":1042},"\u002Fpyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002Flog-messages-to-qgis-message-log-pyqgis\u002F","Log Messages to the QGIS Message Log in PyQGIS"," and ",[30,1046,1048],{"href":1047},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fhandle-errors-and-logging-in-unattended-scripts\u002F","Handle Errors and Logging in Unattended Scripts",".",[153,1051,1053],{"id":1052},"fail-early-with-cheap-checks","Fail early with cheap checks",[14,1055,1056],{},"Most PyQGIS failures are avoidable by validating inputs at the top of a function rather than discovering them in the middle of a loop.",[257,1058,1060],{"className":259,"code":1059,"language":261,"meta":262,"style":262},"def summarise(layer, field):\n    if layer is None or not layer.isValid():\n        raise ValueError(\"a valid vector layer is required\")\n    if field not in layer.fields().names():\n        raise ValueError(f\"{field} is not in {layer.name()}: \"\n                         f\"{', '.join(layer.fields().names())}\")\n    if layer.featureCount() == 0:\n        raise ValueError(f\"{layer.name()} has no features\")\n    ...\n",[18,1061,1062,1072,1092,1106,1122,1153,1174,1189,1212],{"__ignoreMap":262},[266,1063,1064,1066,1069],{"class":131,"line":268},[266,1065,300],{"class":271},[266,1067,1068],{"class":303}," summarise",[266,1070,1071],{"class":275},"(layer, field):\n",[266,1073,1074,1076,1079,1082,1085,1088,1090],{"class":131,"line":285},[266,1075,338],{"class":271},[266,1077,1078],{"class":275}," layer ",[266,1080,1081],{"class":271},"is",[266,1083,1084],{"class":353}," None",[266,1086,1087],{"class":271}," or",[266,1089,341],{"class":271},[266,1091,344],{"class":275},[266,1093,1094,1096,1099,1101,1104],{"class":131,"line":292},[266,1095,350],{"class":271},[266,1097,1098],{"class":353}," ValueError",[266,1100,357],{"class":275},[266,1102,1103],{"class":322},"\"a valid vector layer is required\"",[266,1105,332],{"class":275},[266,1107,1108,1110,1113,1116,1119],{"class":131,"line":297},[266,1109,338],{"class":271},[266,1111,1112],{"class":275}," field ",[266,1114,1115],{"class":271},"not",[266,1117,1118],{"class":271}," in",[266,1120,1121],{"class":275}," layer.fields().names():\n",[266,1123,1124,1126,1128,1130,1132,1134,1136,1139,1141,1144,1146,1148,1150],{"class":131,"line":310},[266,1125,350],{"class":271},[266,1127,1098],{"class":353},[266,1129,357],{"class":275},[266,1131,360],{"class":271},[266,1133,384],{"class":322},[266,1135,366],{"class":353},[266,1137,1138],{"class":275},"field",[266,1140,371],{"class":353},[266,1142,1143],{"class":322}," is not in ",[266,1145,366],{"class":353},[266,1147,896],{"class":275},[266,1149,371],{"class":353},[266,1151,1152],{"class":322},": \"\n",[266,1154,1155,1158,1160,1162,1165,1168,1170,1172],{"class":131,"line":335},[266,1156,1157],{"class":271},"                         f",[266,1159,384],{"class":322},[266,1161,366],{"class":353},[266,1163,1164],{"class":322},"', '",[266,1166,1167],{"class":275},".join(layer.fields().names())",[266,1169,371],{"class":353},[266,1171,384],{"class":322},[266,1173,332],{"class":275},[266,1175,1176,1178,1181,1184,1187],{"class":131,"line":347},[266,1177,338],{"class":271},[266,1179,1180],{"class":275}," layer.featureCount() ",[266,1182,1183],{"class":271},"==",[266,1185,1186],{"class":353}," 0",[266,1188,414],{"class":275},[266,1190,1191,1193,1195,1197,1199,1201,1203,1205,1207,1210],{"class":131,"line":389},[266,1192,350],{"class":271},[266,1194,1098],{"class":353},[266,1196,357],{"class":275},[266,1198,360],{"class":271},[266,1200,384],{"class":322},[266,1202,366],{"class":353},[266,1204,896],{"class":275},[266,1206,371],{"class":353},[266,1208,1209],{"class":322}," has no features\"",[266,1211,332],{"class":275},[266,1213,1214],{"class":131,"line":398},[266,1215,1216],{"class":353},"    ...\n",[14,1218,1219,1221,1222,1225,1226,1229],{},[164,1220,482],{}," Three checks costing microseconds prevent three errors that would otherwise appear thousands of iterations later, with no indication of which input was wrong. Listing the available field names in the message turns \"field not found\" into a message that contains the answer. Raising ",[18,1223,1224],{},"ValueError"," rather than a bare ",[18,1227,1228],{},"RuntimeError"," also lets a caller distinguish \"you gave me bad input\" from \"something went wrong while working\", which matters as soon as the function is called from more than one place.",[153,1231,1233],{"id":1232},"qgis-version-compatibility","QGIS version compatibility",[1235,1236,1237,1253],"table",{},[1238,1239,1240],"thead",{},[1241,1242,1243,1247,1250],"tr",{},[1244,1245,1246],"th",{},"QGIS version",[1244,1248,1249],{},"Python",[1244,1251,1252],{},"Notes",[1254,1255,1256,1268,1278,1289],"tbody",{},[1241,1257,1258,1262,1265],{},[1259,1260,1261],"td",{},"3.22 LTR",[1259,1263,1264],{},"3.9",[1259,1266,1267],{},"Same behaviour; some newer API calls raise where older ones returned codes.",[1241,1269,1270,1273,1275],{},[1259,1271,1272],{},"3.28 LTR",[1259,1274,1264],{},[1259,1276,1277],{},"Identical.",[1241,1279,1280,1283,1286],{},[1259,1281,1282],{},"3.34 LTR",[1259,1284,1285],{},"3.12",[1259,1287,1288],{},"Baseline for this page. Python 3.11 and later add finer-grained error locations in tracebacks, which point at the exact expression.",[1241,1290,1291,1294,1296],{},[1259,1292,1293],{},"3.40 \u002F 3.44",[1259,1295,1285],{},[1259,1297,1298],{},"Identical; more of the API is gradually moving to exceptions, particularly around provider connections.",[14,1300,1301,1302,1304],{},"Where an API call raises on one release and returns a code on another, checking both — a ",[18,1303,411],{}," around a call whose return you also check — is ugly but portable.",[153,1306,1308],{"id":1307},"troubleshooting","Troubleshooting",[158,1310,1311,1317,1323,1330,1336,1352],{},[161,1312,1313,1316],{},[164,1314,1315],{},"The traceback stops at a QGIS module."," The error came from inside C++. The message and the arguments you passed are the evidence; check types first.",[161,1318,1319,1322],{},[164,1320,1321],{},"No traceback at all, and no result."," An API call returned a failure code that was not checked. Add checks to the writer, commit and project calls.",[161,1324,1325,1329],{},[164,1326,1327,1049],{},[18,1328,232],{}," A wrong argument type. The listed signatures show what was expected.",[161,1331,1332,1335],{},[164,1333,1334],{},"The error only happens in a scheduled run."," Something in the environment differs — a path, a provider, an authentication entry. Log the environment at startup.",[161,1337,1338,1341,1342,1344,1345,1044,1348,1351],{},[164,1339,1340],{},"The exception is swallowed somewhere."," A bare ",[18,1343,435],{}," in your code or a plugin's. Search for ",[18,1346,1347],{},"except:",[18,1349,1350],{},"except Exception"," without a re-raise.",[161,1353,1354,1357,1358,1360],{},[164,1355,1356],{},"The Log Messages panel shows nothing."," The wrong tab is selected, or the messages went to ",[18,1359,830],{}," and into the console instead.",[153,1362,1364],{"id":1363},"conclusion","Conclusion",[14,1366,1367],{},"Read tracebacks from the bottom up: type and message first, then the deepest frame in your own code. Catch exceptions only where the handler can do something specific, and always re-raise after logging. Most importantly, check the return values of the API calls that never raise — validity, commit, write, project save — and convert them into exceptions at the point of failure, so every problem fails at the line that caused it.",[153,1369,1371],{"id":1370},"frequently-asked-questions","Frequently Asked Questions",[14,1373,1374,1377],{},[164,1375,1376],{},"Why does QGIS crash instead of raising an exception?","\nA hard crash usually means a C++ object was used after being deleted — most often a layer that was removed while a Python variable still referenced it. That is not catchable; avoid it by clearing references after removal.",[14,1379,1380,1383,1384,1386],{},[164,1381,1382],{},"Should I use assertions for validation?","\nNot in shipped code: assertions can be disabled and their messages are for developers. Raise ",[18,1385,1224],{}," with a message the user can act on.",[14,1388,1389,1392,1393,1396,1397,1049],{},[164,1390,1391],{},"How do I see the traceback from a background task?","\nCatch it inside the task's ",[18,1394,1395],{},"run()"," and log it there — an exception on a worker thread does not reach the console. See ",[30,1398,1400],{"href":1399},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Frun-background-task-with-qgstask-pyqgis\u002F","Run a Background Task with QgsTask in PyQGIS",[14,1402,1403,1409],{},[164,1404,1405,1406,1408],{},"What does ",[18,1407,737],{}," return when a commit fails?","\nA list of strings from the provider, naming constraint violations, invalid geometries or permission problems. Print it — it is far more specific than the boolean.",[14,1411,1412,1415],{},[164,1413,1414],{},"Can I make QGIS show a dialog on every exception?","\nIt already does for uncaught exceptions in plugins. For your own code, decide deliberately: a dialog for something the user must act on, the message log for everything else.",[14,1417,1418,1424],{},[164,1419,1420,1421,1423],{},"Is ",[18,1422,830],{}," ever the right choice?","\nIn the console, yes. Anywhere that runs unattended, no — use the message log or a file, because nobody is watching standard output.",[153,1426,1428],{"id":1427},"related","Related",[158,1430,1431,1436,1440,1446,1450],{},[161,1432,1433,1435],{},[30,1434,33],{"href":32}," — the guide this recipe belongs to",[161,1437,1438],{},[30,1439,1043],{"href":1042},[161,1441,1442],{},[30,1443,1445],{"href":1444},"\u002Fpyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002Ffixing-pyqgis-module-import-errors\u002F","Fixing PyQGIS Module Import Errors",[161,1447,1448],{},[30,1449,1048],{"href":1047},[161,1451,1452],{},[30,1453,1455],{"href":1454},"\u002Fpyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002Fdebug-qgis-plugin-with-debugpy\u002F","Debug a QGIS Plugin with debugpy",[1457,1458,1459],"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 .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 .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":262,"searchDepth":285,"depth":285,"links":1461},[1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472],{"id":155,"depth":285,"text":156},{"id":176,"depth":285,"text":177},{"id":254,"depth":285,"text":255},{"id":569,"depth":285,"text":570},{"id":824,"depth":285,"text":825},{"id":1052,"depth":285,"text":1053},{"id":1232,"depth":285,"text":1233},{"id":1307,"depth":285,"text":1308},{"id":1363,"depth":285,"text":1364},{"id":1370,"depth":285,"text":1371},{"id":1427,"depth":285,"text":1428},"Turn a wall of red text into a fix — read a PyQGIS traceback from the bottom up, catch what you can handle, log the rest with context, and stop the silent failures the API returns instead of raising.","md",{"slug":1476,"type":1477,"breadcrumb":1478,"datePublished":1479,"dateModified":1479},"catch-exceptions-and-tracebacks-pyqgis","article","Exceptions & Tracebacks","2026-08-15","\u002Fpyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002Fcatch-exceptions-and-tracebacks-pyqgis",{"title":5,"description":1473},"pyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002Fcatch-exceptions-and-tracebacks-pyqgis\u002Findex","VRCwv_XYkaAdNBZbo6UPm1YnFPvZYj_0vud8EYCfHJ0",1786789584622]