[{"data":1,"prerenderedAt":1283},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002Fenable-pyqgis-autocompletion-with-type-stubs":3},{"id":4,"title":5,"body":6,"description":1273,"extension":1274,"meta":1275,"navigation":272,"path":1279,"seo":1280,"stem":1281,"__hash__":1282},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002Fenable-pyqgis-autocompletion-with-type-stubs\u002Findex.md","Enable PyQGIS Autocompletion with Type Stubs",{"type":7,"value":8,"toc":1258},"minimark",[9,13,22,31,205,210,236,240,246,329,339,361,365,368,405,425,433,437,440,522,549,553,556,580,586,693,697,708,753,765,769,772,938,962,976,980,983,1017,1021,1027,1106,1110,1162,1166,1180,1184,1193,1203,1213,1219,1223,1254],[10,11,5],"h1",{"id":12},"enable-pyqgis-autocompletion-with-type-stubs",[14,15,16,17,21],"p",{},"The QGIS Python API is generated from C++ through SIP, and the result is a set of extension modules an editor cannot introspect. Without help, ",[18,19,20],"code",{},"QgsVectorLayer."," offers nothing, every method call is untyped, and a misspelt attribute is discovered at runtime. Stub files fix that: plain-text declarations of every class and signature that the language server reads instead of trying to inspect a binary.",[14,23,24,25,30],{},"This recipe belongs to ",[26,27,29],"a",{"href":28},"\u002Fpyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002F","Setting Up PyCharm for QGIS",". It covers what QGIS already ships, installing stubs for the parts it does not, configuring Pylance and PyCharm to find them, and the limits of what stubs can tell you about a dynamically typed API.",[14,32,33],{},[34,35,40,44,48,55,72,81,91,97,105,111,117,122,127,130,137,142,146,151,155,157,160,163,169,173,179,185,189,193,197,201],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 760 304","img","Why an editor cannot introspect a compiled extension module, and how a stub file supplies the same information as readable declarations","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"What the language server can and cannot read",[45,46,47],"desc",{},"A compiled SIP extension module is a binary the language server cannot inspect, so completion is empty. A stub file declares the same classes and method signatures in plain Python syntax, which the language server reads directly, producing completion, parameter hints and type checking without changing what runs.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","760","304","#f6f3ea",[56,57,58],"defs",{},[59,60,67],"marker",{"id":61,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"stubArrow","0 0 10 10","8","5","7","auto-start-reverse",[68,69],"path",{"d":70,"fill":71},"M0 0 L10 5 L0 10 z","#2f3b35",[73,74,80],"text",{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","The stub is for the editor; the binary is for Python",[49,82],{"x":83,"y":84,"width":85,"height":86,"rx":87,"fill":88,"stroke":89,"style":90},"26","56","330","112","10","#fdf2e2","#b91c1c","stroke-width:2.5",[73,92,96],{"x":93,"y":94,"style":95,"fill":89,"textAnchor":79},"191","84","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","without stubs",[49,98],{"x":99,"y":100,"width":101,"height":99,"rx":102,"fill":103,"stroke":89,"style":104},"50","98","130","6","#fffdf7","stroke-width:1.6",[73,106,110],{"x":107,"y":108,"style":109,"fill":71,"textAnchor":79},"115","120","text-anchor:middle;font-size:9.5px;font-family:monospace","_core.so",[73,112,116],{"x":107,"y":113,"style":114,"fill":115,"textAnchor":79},"138","text-anchor:middle;font-size:9px;font-family:sans-serif","#59645f","compiled SIP module",[49,118],{"x":119,"y":100,"width":101,"height":99,"rx":102,"fill":120,"stroke":115,"style":121},"204","#efeadd","stroke-width:1.4;stroke-dasharray:4 3",[73,123,126],{"x":124,"y":108,"style":125,"fill":115,"textAnchor":79},"269","text-anchor:middle;font-size:9.5px;font-family:sans-serif","completion:",[73,128,129],{"x":124,"y":113,"style":125,"fill":89,"textAnchor":79},"nothing",[131,132],"line",{"x1":133,"y1":134,"x2":135,"y2":134,"stroke":71,"style":136},"184","123","198","stroke-width:1.8;marker-end:url(#stubArrow)",[49,138],{"x":83,"y":133,"width":85,"height":139,"rx":87,"fill":140,"stroke":141,"style":90},"96","#edf8e9","#15803d",[73,143,145],{"x":93,"y":144,"style":95,"fill":141,"textAnchor":79},"212","with stubs",[49,147],{"x":99,"y":148,"width":101,"height":149,"rx":102,"fill":150,"stroke":141,"style":104},"226","40","#e8efe6",[73,152,154],{"x":107,"y":153,"style":109,"fill":71,"textAnchor":79},"251","core.pyi",[49,156],{"x":119,"y":148,"width":101,"height":149,"rx":102,"fill":150,"stroke":141,"style":104},[73,158,159],{"x":124,"y":153,"style":125,"fill":71,"textAnchor":79},"full completion",[131,161],{"x1":133,"y1":162,"x2":135,"y2":162,"stroke":71,"style":136},"246",[49,164],{"x":165,"y":84,"width":85,"height":166,"rx":87,"fill":167,"stroke":168,"style":90},"404","224","#eff3ff","#2563eb",[73,170,172],{"x":171,"y":94,"style":95,"fill":168,"textAnchor":79},"569","what a stub declares",[49,174],{"x":175,"y":176,"width":177,"height":178,"rx":102,"fill":103,"stroke":168,"style":104},"428","100","282","150",[73,180,184],{"x":181,"y":182,"style":183,"fill":71},"444","124","font-size:9.5px;font-family:monospace","class QgsVectorLayer(QgsMapLayer):",[73,186,188],{"x":181,"y":187,"style":183,"fill":71},"146","    def featureCount(self) -> int: ...",[73,190,192],{"x":181,"y":191,"style":183,"fill":71},"168","    def crs(self)",[73,194,196],{"x":181,"y":195,"style":183,"fill":71},"190","        -> QgsCoordinateReferenceSystem: ...",[73,198,200],{"x":181,"y":199,"style":183,"fill":115},"216","    # no bodies — declarations only",[73,202,204],{"x":181,"y":203,"style":183,"fill":115},"238","    # never imported at runtime",[206,207,209],"h2",{"id":208},"prerequisites","Prerequisites",[211,212,213,221,228],"ul",{},[214,215,216,220],"li",{},[217,218,219],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[214,222,223,224,227],{},"An editor with a language server: VS Code with Pylance, or PyCharm. Both read ",[18,225,226],{},".pyi"," files; neither finds them automatically for QGIS.",[214,229,230,231,235],{},"A place to install packages that your editor's interpreter can see — the ",[26,232,234],{"href":233},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Fuse-pyqgis-with-conda-and-mamba\u002F","conda environment"," or virtualenv you point the editor at.",[206,237,239],{"id":238},"what-qgis-already-ships","What QGIS already ships",[14,241,242,243,245],{},"More than most people realise. Recent QGIS builds include generated ",[18,244,226],{}," files alongside the compiled modules.",[247,248,253],"pre",{"className":249,"code":250,"language":251,"meta":252,"style":252},"language-python shiki shiki-themes github-dark","import qgis.core, os, glob\n\ndirectory = os.path.dirname(qgis.core.__file__)\nprint(directory)\nprint([os.path.basename(p) for p in glob.glob(os.path.join(directory, \"*.pyi\"))])\n","python","",[18,254,255,267,274,293,302],{"__ignoreMap":252},[256,257,259,263],"span",{"class":131,"line":258},1,[256,260,262],{"class":261},"snl16","import",[256,264,266],{"class":265},"s95oV"," qgis.core, os, glob\n",[256,268,270],{"class":131,"line":269},2,[256,271,273],{"emptyLinePlaceholder":272},true,"\n",[256,275,277,280,283,286,290],{"class":131,"line":276},3,[256,278,279],{"class":265},"directory ",[256,281,282],{"class":261},"=",[256,284,285],{"class":265}," os.path.dirname(qgis.core.",[256,287,289],{"class":288},"sDLfK","__file__",[256,291,292],{"class":265},")\n",[256,294,296,299],{"class":131,"line":295},4,[256,297,298],{"class":288},"print",[256,300,301],{"class":265},"(directory)\n",[256,303,305,307,310,313,316,319,322,326],{"class":131,"line":304},5,[256,306,298],{"class":288},[256,308,309],{"class":265},"([os.path.basename(p) ",[256,311,312],{"class":261},"for",[256,314,315],{"class":265}," p ",[256,317,318],{"class":261},"in",[256,320,321],{"class":265}," glob.glob(os.path.join(directory, ",[256,323,325],{"class":324},"sU2Wk","\"*.pyi\"",[256,327,328],{"class":265},"))])\n",[14,330,331,334,335,338],{},[217,332,333],{},"Breakdown:"," If this prints ",[18,336,337],{},"_core.pyi"," and friends, the declarations are already on disk and the only remaining problem is that the editor is not looking there — which the analysis-path settings below solve. If it prints an empty list, the build did not ship them and third-party stubs are the route. The check takes ten seconds and determines which half of this page applies.",[14,340,341,342,345,346,345,349,352,353,356,357,360],{},"The shipped stubs cover ",[18,343,344],{},"qgis.core",", ",[18,347,348],{},"qgis.gui",[18,350,351],{},"qgis.analysis"," and ",[18,354,355],{},"qgis.server",". They do not cover ",[18,358,359],{},"processing",", which is ordinary Python and needs no stub, nor PyQt, which needs its own.",[206,362,364],{"id":363},"install-stubs-for-what-is-missing","Install stubs for what is missing",[14,366,367],{},"Two packages cover the gaps.",[247,369,373],{"className":370,"code":371,"language":372,"meta":252,"style":252},"language-bash shiki shiki-themes github-dark","python -m pip install PyQt5-stubs\npython -m pip install qgis-stubs\n","bash",[18,374,375,392],{"__ignoreMap":252},[256,376,377,380,383,386,389],{"class":131,"line":258},[256,378,251],{"class":379},"svObZ",[256,381,382],{"class":288}," -m",[256,384,385],{"class":324}," pip",[256,387,388],{"class":324}," install",[256,390,391],{"class":324}," PyQt5-stubs\n",[256,393,394,396,398,400,402],{"class":131,"line":269},[256,395,251],{"class":379},[256,397,382],{"class":288},[256,399,385],{"class":324},[256,401,388],{"class":324},[256,403,404],{"class":324}," qgis-stubs\n",[14,406,407,409,410,413,414,345,417,420,421,424],{},[217,408,333],{}," ",[18,411,412],{},"PyQt5-stubs"," is the widely used community package for the Qt bindings, and it is what makes ",[18,415,416],{},"QColor",[18,418,419],{},"QWidget"," and the signal machinery complete properly — a large share of plugin code is Qt rather than QGIS. ",[18,422,423],{},"qgis-stubs"," supplies QGIS declarations for builds that ship none; installing it alongside shipped stubs is harmless but redundant, and on a mismatched version it is worse than redundant because the signatures may not match the QGIS actually installed. Check the shipped stubs first.",[14,426,427,428,432],{},"Install into the environment the ",[429,430,431],"em",{},"editor"," uses. Installing into the QGIS system Python from a terminal is a common mis-step that changes nothing about completion, because the editor was pointed at a different interpreter.",[206,434,436],{"id":435},"configure-pylance","Configure Pylance",[14,438,439],{},"Pylance needs to be told where to look and how strictly to check.",[247,441,445],{"className":442,"code":443,"language":444,"meta":252,"style":252},"language-json shiki shiki-themes github-dark","{\n  \"python.analysis.extraPaths\": [\n    \"\u002Fusr\u002Fshare\u002Fqgis\u002Fpython\",\n    \"\u002Fusr\u002Fshare\u002Fqgis\u002Fpython\u002Fplugins\"\n  ],\n  \"python.analysis.stubPath\": \"${workspaceFolder}\u002Ftypings\",\n  \"python.analysis.typeCheckingMode\": \"basic\",\n  \"python.analysis.useLibraryCodeForTypes\": true\n}\n","json",[18,446,447,452,460,468,473,478,492,505,516],{"__ignoreMap":252},[256,448,449],{"class":131,"line":258},[256,450,451],{"class":265},"{\n",[256,453,454,457],{"class":131,"line":269},[256,455,456],{"class":288},"  \"python.analysis.extraPaths\"",[256,458,459],{"class":265},": [\n",[256,461,462,465],{"class":131,"line":276},[256,463,464],{"class":324},"    \"\u002Fusr\u002Fshare\u002Fqgis\u002Fpython\"",[256,466,467],{"class":265},",\n",[256,469,470],{"class":131,"line":295},[256,471,472],{"class":324},"    \"\u002Fusr\u002Fshare\u002Fqgis\u002Fpython\u002Fplugins\"\n",[256,474,475],{"class":131,"line":304},[256,476,477],{"class":265},"  ],\n",[256,479,481,484,487,490],{"class":131,"line":480},6,[256,482,483],{"class":288},"  \"python.analysis.stubPath\"",[256,485,486],{"class":265},": ",[256,488,489],{"class":324},"\"${workspaceFolder}\u002Ftypings\"",[256,491,467],{"class":265},[256,493,495,498,500,503],{"class":131,"line":494},7,[256,496,497],{"class":288},"  \"python.analysis.typeCheckingMode\"",[256,499,486],{"class":265},[256,501,502],{"class":324},"\"basic\"",[256,504,467],{"class":265},[256,506,508,511,513],{"class":131,"line":507},8,[256,509,510],{"class":288},"  \"python.analysis.useLibraryCodeForTypes\"",[256,512,486],{"class":265},[256,514,515],{"class":288},"true\n",[256,517,519],{"class":131,"line":518},9,[256,520,521],{"class":265},"}\n",[14,523,524,409,526,529,530,533,534,536,537,540,541,544,545,548],{},[217,525,333],{},[18,527,528],{},"stubPath"," points at a local directory for stubs you write yourself, which is how a plugin's own generated resources module gets typed. ",[18,531,532],{},"useLibraryCodeForTypes: true"," lets Pylance fall back to inspecting installed packages where no stub exists — worth having on, because it makes ",[18,535,359],{}," complete. ",[18,538,539],{},"typeCheckingMode: \"strict\""," is tempting and usually counterproductive on QGIS code: the API returns bare ",[18,542,543],{},"object"," in enough places that strict mode produces more noise than signal. Start at ",[18,546,547],{},"basic"," and raise it for your own modules with a per-directory override if you want.",[206,550,552],{"id":551},"configure-pycharm","Configure PyCharm",[14,554,555],{},"PyCharm handles this through the interpreter's path configuration rather than through a settings file.",[14,557,558,559,562,563,566,567,570,571,352,573,576,577,579],{},"Open ",[429,560,561],{},"Settings → Project → Python Interpreter",", click the gear, choose ",[429,564,565],{},"Show All",", select the interpreter, then the ",[429,568,569],{},"Show paths"," icon. Add the QGIS ",[18,572,251],{},[18,574,575],{},"python\u002Fplugins"," directories. PyCharm indexes them, finds the ",[18,578,226],{}," files, and completion works from the next reindex.",[14,581,582,583,585],{},"The one non-obvious detail is that PyCharm prefers a stub to the real module when both are present, so a stale ",[18,584,423],{}," install against a newer QGIS produces confidently wrong completion — offering methods that no longer exist and hiding ones that do. When completion disagrees with the running code, uninstalling the third-party stubs is the first thing to try.",[14,587,588],{},[34,589,592,595,598,601,608,611,616,621,626,629,632,636,640,644,649,653,658,663,667,670,675,679,683,687,690],{"viewBox":590,"role":37,"ariaLabel":591,"xmlns":39},"0 0 760 288","A stale stub package taking precedence over the installed QGIS bindings, so the editor confidently suggests methods that no longer exist",[41,593,594],{},"A stale stub is worse than no stub",[45,596,597],{},"When both a stub file and the compiled module are present the language server trusts the stub. If the stub package targets an older QGIS than the one installed, completion offers removed methods and hides new ones, and the disagreement only shows up at runtime.",[49,599],{"x":51,"y":51,"width":52,"height":600,"fill":54},"288",[56,602,603],{},[59,604,606],{"id":605,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"staleArrow",[68,607],{"d":70,"fill":71},[73,609,610],{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"The editor trusts the stub, not the binary",[49,612],{"x":83,"y":84,"width":613,"height":614,"rx":63,"fill":88,"stroke":615,"style":90},"220","80","#b45309",[73,617,620],{"x":618,"y":94,"style":619,"fill":615,"textAnchor":79},"136","text-anchor:middle;font-size:10.5px;font-weight:bold;font-family:sans-serif","qgis-stubs for 3.22",[73,622,625],{"x":618,"y":623,"style":624,"fill":71,"textAnchor":79},"106","text-anchor:middle;font-size:10px;font-family:sans-serif","installed by pip",[73,627,628],{"x":618,"y":182,"style":624,"fill":71,"textAnchor":79},"two years old",[49,630],{"x":83,"y":631,"width":613,"height":614,"rx":63,"fill":140,"stroke":141,"style":90},"164",[73,633,635],{"x":618,"y":634,"style":619,"fill":141,"textAnchor":79},"192","QGIS 3.34 bindings",[73,637,639],{"x":618,"y":638,"style":624,"fill":71,"textAnchor":79},"214","what actually runs",[73,641,643],{"x":618,"y":642,"style":624,"fill":71,"textAnchor":79},"232","newer signatures",[131,645],{"x1":646,"y1":139,"x2":647,"y2":101,"stroke":615,"style":648},"252","300","stroke-width:2.5;marker-end:url(#staleArrow)",[131,650],{"x1":646,"y1":119,"x2":647,"y2":651,"stroke":115,"style":652},"170","stroke-width:2;stroke-dasharray:5 4;marker-end:url(#staleArrow)",[49,654],{"x":655,"y":86,"width":656,"height":657,"rx":63,"fill":167,"stroke":168,"style":90},"308","180","76",[73,659,662],{"x":660,"y":661,"style":619,"fill":168,"textAnchor":79},"398","140","language server",[73,664,666],{"x":660,"y":665,"style":624,"fill":71,"textAnchor":79},"162","prefers the stub",[73,668,669],{"x":660,"y":656,"style":125,"fill":115,"textAnchor":79},"ignores the binary",[131,671],{"x1":672,"y1":178,"x2":673,"y2":178,"stroke":71,"style":674},"494","524","stroke-width:2;marker-end:url(#staleArrow)",[49,676],{"x":677,"y":139,"width":119,"height":678,"rx":87,"fill":103,"stroke":89,"style":90},"532","108",[73,680,682],{"x":681,"y":182,"style":619,"fill":89,"textAnchor":79},"634","what you see",[73,684,686],{"x":681,"y":685,"style":624,"fill":71,"textAnchor":79},"148","removed methods offered",[73,688,689],{"x":681,"y":191,"style":624,"fill":71,"textAnchor":79},"new ones flagged as errors",[73,691,692],{"x":681,"y":195,"style":624,"fill":89,"textAnchor":79},"runtime disagrees",[206,694,696],{"id":695},"write-a-stub-for-your-own-generated-code","Write a stub for your own generated code",[14,698,699,700,703,704,707],{},"A plugin compiled from a ",[18,701,702],{},".qrc"," produces ",[18,705,706],{},"resources.py",", which is machine-generated, enormous and useless to complete against. A three-line stub replaces it.",[247,709,711],{"className":249,"code":710,"language":251,"meta":252,"style":252},"# typings\u002Fresources.pyi\ndef qInitResources() -> None: ...\ndef qCleanupResources() -> None: ...\n",[18,712,713,719,738],{"__ignoreMap":252},[256,714,715],{"class":131,"line":258},[256,716,718],{"class":717},"sjoCn","# typings\u002Fresources.pyi\n",[256,720,721,724,727,730,733,735],{"class":131,"line":269},[256,722,723],{"class":261},"def",[256,725,726],{"class":379}," qInitResources",[256,728,729],{"class":265},"() -> ",[256,731,732],{"class":288},"None",[256,734,486],{"class":265},[256,736,737],{"class":288},"...\n",[256,739,740,742,745,747,749,751],{"class":131,"line":276},[256,741,723],{"class":261},[256,743,744],{"class":379}," qCleanupResources",[256,746,729],{"class":265},[256,748,732],{"class":288},[256,750,486],{"class":265},[256,752,737],{"class":288},[14,754,755,757,758,760,761,764],{},[217,756,333],{}," Putting this in the directory named by ",[18,759,528],{}," makes the editor treat those two functions as the module's entire public surface, which is true. It removes several thousand lines of base64 from the index and makes the import resolve cleanly. The same trick applies to any generated module — a compiled ",[18,762,763],{},".ui"," conversion, a vendored library with no annotations — and costs a minute each.",[206,766,768],{"id":767},"annotating-your-own-code-so-the-stubs-pay-off","Annotating your own code so the stubs pay off",[14,770,771],{},"Stubs give the editor knowledge about the QGIS API. Annotating your own functions is what lets that knowledge propagate through a codebase, and it is where most of the practical benefit appears.",[247,773,775],{"className":249,"code":774,"language":251,"meta":252,"style":252},"from typing import Optional, Iterable\nfrom qgis.core import QgsVectorLayer, QgsProject, QgsFeature\n\n\ndef layer_by_name(name: str) -> Optional[QgsVectorLayer]:\n    matches = QgsProject.instance().mapLayersByName(name)\n    if not matches:\n        return None\n    layer = matches[0]\n    return layer if isinstance(layer, QgsVectorLayer) else None\n\n\ndef selected_or_all(layer: QgsVectorLayer) -> Iterable[QgsFeature]:\n    if layer.selectedFeatureCount():\n        return layer.selectedFeatures()\n    return layer.getFeatures()\n",[18,776,777,790,802,806,810,826,836,847,855,870,893,898,903,914,922,930],{"__ignoreMap":252},[256,778,779,782,785,787],{"class":131,"line":258},[256,780,781],{"class":261},"from",[256,783,784],{"class":265}," typing ",[256,786,262],{"class":261},[256,788,789],{"class":265}," Optional, Iterable\n",[256,791,792,794,797,799],{"class":131,"line":269},[256,793,781],{"class":261},[256,795,796],{"class":265}," qgis.core ",[256,798,262],{"class":261},[256,800,801],{"class":265}," QgsVectorLayer, QgsProject, QgsFeature\n",[256,803,804],{"class":131,"line":276},[256,805,273],{"emptyLinePlaceholder":272},[256,807,808],{"class":131,"line":295},[256,809,273],{"emptyLinePlaceholder":272},[256,811,812,814,817,820,823],{"class":131,"line":304},[256,813,723],{"class":261},[256,815,816],{"class":379}," layer_by_name",[256,818,819],{"class":265},"(name: ",[256,821,822],{"class":288},"str",[256,824,825],{"class":265},") -> Optional[QgsVectorLayer]:\n",[256,827,828,831,833],{"class":131,"line":480},[256,829,830],{"class":265},"    matches ",[256,832,282],{"class":261},[256,834,835],{"class":265}," QgsProject.instance().mapLayersByName(name)\n",[256,837,838,841,844],{"class":131,"line":494},[256,839,840],{"class":261},"    if",[256,842,843],{"class":261}," not",[256,845,846],{"class":265}," matches:\n",[256,848,849,852],{"class":131,"line":507},[256,850,851],{"class":261},"        return",[256,853,854],{"class":288}," None\n",[256,856,857,860,862,865,867],{"class":131,"line":518},[256,858,859],{"class":265},"    layer ",[256,861,282],{"class":261},[256,863,864],{"class":265}," matches[",[256,866,51],{"class":288},[256,868,869],{"class":265},"]\n",[256,871,873,876,879,882,885,888,891],{"class":131,"line":872},10,[256,874,875],{"class":261},"    return",[256,877,878],{"class":265}," layer ",[256,880,881],{"class":261},"if",[256,883,884],{"class":288}," isinstance",[256,886,887],{"class":265},"(layer, QgsVectorLayer) ",[256,889,890],{"class":261},"else",[256,892,854],{"class":288},[256,894,896],{"class":131,"line":895},11,[256,897,273],{"emptyLinePlaceholder":272},[256,899,901],{"class":131,"line":900},12,[256,902,273],{"emptyLinePlaceholder":272},[256,904,906,908,911],{"class":131,"line":905},13,[256,907,723],{"class":261},[256,909,910],{"class":379}," selected_or_all",[256,912,913],{"class":265},"(layer: QgsVectorLayer) -> Iterable[QgsFeature]:\n",[256,915,917,919],{"class":131,"line":916},14,[256,918,840],{"class":261},[256,920,921],{"class":265}," layer.selectedFeatureCount():\n",[256,923,925,927],{"class":131,"line":924},15,[256,926,851],{"class":261},[256,928,929],{"class":265}," layer.selectedFeatures()\n",[256,931,933,935],{"class":131,"line":932},16,[256,934,875],{"class":261},[256,936,937],{"class":265}," layer.getFeatures()\n",[14,939,940,409,942,945,946,949,950,953,954,957,958,961],{},[217,941,333],{},[18,943,944],{},"mapLayersByName()"," is declared as returning a list of ",[18,947,948],{},"QgsMapLayer",", so the ",[18,951,952],{},"isinstance"," narrowing is what tells the editor — and the reader — that a vector layer is what comes back. Returning ",[18,955,956],{},"Optional"," rather than raising forces callers to handle the missing case, and the editor will flag one that does not. ",[18,959,960],{},"Iterable[QgsFeature]"," covers both branches, which return different concrete types; declaring the common interface is more honest than picking one.",[14,963,964,965,967,968,971,972,975],{},"Two annotations do disproportionate work in PyQGIS code. Narrowing a ",[18,966,948],{}," to its real subclass, as above, unlocks completion for everything downstream. And annotating a parameter as ",[18,969,970],{},"QgsVectorLayer"," rather than leaving it bare means every ",[18,973,974],{},"layer."," inside the function completes — which, in a module of twenty helpers, is the difference between the stubs being useful and being theoretical.",[206,977,979],{"id":978},"what-stubs-cannot-do","What stubs cannot do",[14,981,982],{},"Stubs describe signatures, not behaviour, and three QGIS patterns defeat them.",[14,984,985,986,988,989,991,992,995,996,999,1000,1003,1004,1007,1008,1011,1012,1016],{},"Methods returning ",[18,987,948],{}," when the caller knows it is a ",[18,990,970],{}," require a cast or an assertion for the editor to follow — ",[18,993,994],{},"layer = cast(QgsVectorLayer, project.mapLayersByName(\"x\")[0])"," is the idiom. Property dictionaries such as ",[18,997,998],{},"QgsFillSymbol.createSimple({...})"," take arbitrary string keys, so no stub can validate them. And ",[18,1001,1002],{},"processing.run()"," returns a plain ",[18,1005,1006],{},"dict",", so the output key is a string the editor cannot check — which is exactly the string most likely to be wrong, and the reason to compare it against ",[18,1009,1010],{},"outputDefinitions()"," as described in ",[26,1013,1015],{"href":1014},"\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002Frun-graphical-model-from-python-pyqgis\u002F","running a graphical model from Python",".",[206,1018,1020],{"id":1019},"qgis-version-compatibility","QGIS version compatibility",[14,1022,1023,1024,1026],{},"The examples target ",[217,1025,219],{}," (Python 3.12).",[1028,1029,1030,1046],"table",{},[1031,1032,1033],"thead",{},[1034,1035,1036,1040,1043],"tr",{},[1037,1038,1039],"th",{},"QGIS version",[1037,1041,1042],{},"Python",[1037,1044,1045],{},"Notes",[1047,1048,1049,1064,1075,1085,1096],"tbody",{},[1034,1050,1051,1055,1058],{},[1052,1053,1054],"td",{},"3.16 LTR",[1052,1056,1057],{},"3.7",[1052,1059,1060,1061,1063],{},"Few or no shipped ",[18,1062,226],{},"; third-party stubs are the main route.",[1034,1065,1066,1069,1072],{},[1052,1067,1068],{},"3.22 LTR",[1052,1070,1071],{},"3.9",[1052,1073,1074],{},"Generated stubs begin appearing in some distribution builds.",[1034,1076,1077,1080,1082],{},[1052,1078,1079],{},"3.28 LTR",[1052,1081,1071],{},[1052,1083,1084],{},"Shipped stubs more complete across core, gui and analysis.",[1034,1086,1087,1090,1093],{},[1052,1088,1089],{},"3.34 LTR",[1052,1091,1092],{},"3.12",[1052,1094,1095],{},"Baseline for this page; shipped stubs cover most of the API.",[1034,1097,1098,1101,1103],{},[1052,1099,1100],{},"3.40+",[1052,1102,1092],{},[1052,1104,1105],{},"Qt6 builds need PyQt6 stubs rather than PyQt5-stubs.",[206,1107,1109],{"id":1108},"troubleshooting","Troubleshooting",[211,1111,1112,1118,1124,1136,1144,1152],{},[214,1113,1114,1117],{},[217,1115,1116],{},"Completion is empty despite installing stubs."," They went into a different interpreter than the editor uses. Check which interpreter is selected.",[214,1119,1120,1123],{},[217,1121,1122],{},"Completion offers methods that do not exist."," A stale third-party stub is taking precedence. Uninstall it and rely on the shipped one.",[214,1125,1126,1132,1133,1135],{},[217,1127,1128,1131],{},[18,1129,1130],{},"import processing"," is flagged."," The ",[18,1134,575],{}," directory is missing from the analysis paths.",[214,1137,1138,409,1141,1143],{},[217,1139,1140],{},"Every Qt call is untyped.",[18,1142,412],{}," is not installed, or the build is Qt6 and needs the PyQt6 equivalent.",[214,1145,1146,1149,1150,1016],{},[217,1147,1148],{},"Strict mode floods the problems panel."," The API returns loosely typed values in many places. Use ",[18,1151,547],{},[214,1153,1154,1157,1158,1161],{},[217,1155,1156],{},"PyCharm still shows nothing after adding paths."," It needs a reindex — ",[429,1159,1160],{},"File → Invalidate Caches"," and restart.",[206,1163,1165],{"id":1164},"conclusion","Conclusion",[14,1167,1168,1169,352,1171,1173,1174,1176,1177,1179],{},"Check what QGIS already ships before installing anything, add the ",[18,1170,251],{},[18,1172,575],{}," directories to the editor's analysis paths, install ",[18,1175,412],{}," for the Qt half, and keep type checking at ",[18,1178,547],{},". When completion and runtime disagree, suspect a stale stub package rather than the bindings.",[206,1181,1183],{"id":1182},"frequently-asked-questions","Frequently Asked Questions",[14,1185,1186,1189,1190,1192],{},[217,1187,1188],{},"Do stubs change what runs?","\nNo. A ",[18,1191,226],{}," is never imported at runtime; it exists purely for static analysis. Deleting every stub changes nothing about behaviour.",[14,1194,1195,1198,1199,1202],{},[217,1196,1197],{},"Can I generate stubs myself?","\nYes — ",[18,1200,1201],{},"stubgen"," from mypy produces a starting point from the compiled modules, though the result needs manual work for overloads and enums. It is a reasonable option for a build with no shipped stubs and no matching package.",[14,1204,1205,1208,1209,1016],{},[217,1206,1207],{},"Do stubs help the QGIS Python console?","\nThe built-in console does its own introspection and is unaffected. Completion there comes from the live objects — see ",[26,1210,1212],{"href":1211},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Frun-and-save-scripts-in-qgis-python-editor\u002F","running and saving scripts in the QGIS Python editor",[14,1214,1215,1218],{},[217,1216,1217],{},"Should I commit a typings directory?","\nYes, for stubs you wrote about your own generated code. Not for third-party packages, which belong in the dependency file.",[206,1220,1222],{"id":1221},"related","Related",[211,1224,1225,1230,1236,1242,1248],{},[214,1226,1227,1229],{},[26,1228,29],{"href":28}," — the guide this recipe belongs to",[214,1231,1232],{},[26,1233,1235],{"href":1234},"\u002Fpyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002Fconfigure-vs-code-for-pyqgis-development\u002F","Configure VS Code for PyQGIS Development",[214,1237,1238],{},[26,1239,1241],{"href":1240},"\u002Fpyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002Fbest-ide-for-qgis-plugin-development\u002F","Best IDE for QGIS Plugin Development",[214,1243,1244],{},[26,1245,1247],{"href":1246},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-api-architecture\u002Fexplore-pyqgis-api-with-dir-and-help\u002F","Explore the PyQGIS API with dir and help",[214,1249,1250],{},[26,1251,1253],{"href":1252},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-api-architecture\u002Fqgis-python-version-compatibility-guide\u002F","QGIS Python Version Compatibility Guide",[1255,1256,1257],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":252,"searchDepth":269,"depth":269,"links":1259},[1260,1261,1262,1263,1264,1265,1266,1267,1268,1269,1270,1271,1272],{"id":208,"depth":269,"text":209},{"id":238,"depth":269,"text":239},{"id":363,"depth":269,"text":364},{"id":435,"depth":269,"text":436},{"id":551,"depth":269,"text":552},{"id":695,"depth":269,"text":696},{"id":767,"depth":269,"text":768},{"id":978,"depth":269,"text":979},{"id":1019,"depth":269,"text":1020},{"id":1108,"depth":269,"text":1109},{"id":1164,"depth":269,"text":1165},{"id":1182,"depth":269,"text":1183},{"id":1221,"depth":269,"text":1222},"Get real completion and type checking for the QGIS API — where the shipped .pyi files live, installing PyQt5-stubs and qgis-stubs, and configuring Pylance or PyCharm to use them.","md",{"slug":12,"type":1276,"breadcrumb":1277,"datePublished":1278,"dateModified":1278},"article","Type Stubs & Completion","2026-08-27","\u002Fpyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002Fenable-pyqgis-autocompletion-with-type-stubs",{"title":5,"description":1273},"pyqgis-fundamentals-environment-setup\u002Fsetting-up-pycharm-for-qgis\u002Fenable-pyqgis-autocompletion-with-type-stubs\u002Findex","L-a1-E18Saf59aBgpoRznNnGnPScBw2ZkWsoamahBE0",1787823360565]