[{"data":1,"prerenderedAt":2801},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fextending-qgis-with-custom-classes\u002Fwrite-python-vector-data-provider-pyqgis":3},{"id":4,"title":5,"body":6,"description":2790,"extension":2791,"meta":2792,"navigation":465,"path":2797,"seo":2798,"stem":2799,"__hash__":2800},"docs\u002Fqgis-plugin-development\u002Fextending-qgis-with-custom-classes\u002Fwrite-python-vector-data-provider-pyqgis\u002Findex.md","Write a Python Vector Data Provider in PyQGIS",{"type":7,"value":8,"toc":2776},"minimark",[9,13,30,39,188,193,211,215,218,632,729,744,748,755,1442,1466,1470,1473,1542,2270,2290,2294,2297,2385,2394,2398,2401,2604,2614,2618,2626,2630,2654,2658,2692,2696,2699,2703,2716,2722,2732,2738,2742,2772],[10,11,5],"h1",{"id":12},"write-a-python-vector-data-provider-in-pyqgis",[14,15,16,17,21,22,25,26,29],"p",{},"Every QGIS vector layer gets its features from a data provider: ",[18,19,20],"code",{},"ogr"," for files, ",[18,23,24],{},"postgres"," for PostGIS, ",[18,27,28],{},"WFS"," for web services. When data lives somewhere none of them can reach — an in-house REST API, a proprietary sensor log, a Python object model in another application — the usual answer is to copy it into a memory layer or GeoPackage. Sometimes copying is the wrong answer: the data is large and only small parts are viewed at a time, or it changes constantly, or the layer must behave like a live source in projects. A provider written in Python makes the source appear as a normal layer, with QGIS asking for features as it needs them.",[14,31,32,33,38],{},"This recipe belongs to ",[34,35,37],"a",{"href":36},"\u002Fqgis-plugin-development\u002Fextending-qgis-with-custom-classes\u002F","Extending QGIS with Custom Classes",". It builds a read-only provider over a simple Python data source, implements the three cooperating classes a provider needs, honours feature requests so QGIS fetches only what it needs, registers the provider key, and compares the approach with a memory layer.",[14,40,41],{},[42,43,48,52,56,63,80,89,98,104,109,116,124,129,133,137,141,145,149,156,161,165,169,173,178,182,185],"svg",{"viewBox":44,"role":45,"ariaLabel":46,"xmlns":47},"0 0 760 258","img","A layer using a custom provider, which describes the source and creates a feature source and per-request iterators that fetch features one at a time","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[49,50,51],"title",{},"Three classes behind a provider",[53,54,55],"desc",{},"A QgsVectorLayer created with a custom provider key asks the provider registry for the provider. The provider describes the source: fields, geometry type, CRS, extent and feature count. For reading, the provider creates a feature source, a thread-safe snapshot, and the source creates a feature iterator for each request. The iterator's fetchFeature fills one QgsFeature at a time, honouring the request's filters.",[57,58],"rect",{"x":59,"y":59,"width":60,"height":61,"fill":62},"0","760","258","#f6f3ea",[64,65,66],"defs",{},[67,68,75],"marker",{"id":69,"viewBox":70,"refX":71,"refY":72,"markerWidth":73,"markerHeight":73,"orient":74},"dpClassesArrow","0 0 10 10","8","5","7","auto-start-reverse",[76,77],"path",{"d":78,"fill":79},"M0 0 L10 5 L0 10 z","#2f3b35",[81,82,88],"text",{"x":83,"y":84,"style":85,"fill":86,"textAnchor":87},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","Provider, source, iterator",[57,90],{"x":91,"y":92,"width":93,"height":94,"rx":71,"fill":95,"stroke":96,"style":97},"24","110","150","70","#fffdf7","#59645f","stroke-width:2",[81,99,103],{"x":100,"y":101,"style":102,"fill":86,"textAnchor":87},"99","137.6","text-anchor:middle;font-size:11.0px;font-family:sans-serif;font-weight:bold","QgsVectorLayer",[81,105,108],{"x":100,"y":106,"style":107,"fill":96,"textAnchor":87},"159.6","text-anchor:middle;font-size:10.0px;font-family:sans-serif","key \"pysensors\"",[110,111],"line",{"x1":112,"y1":113,"x2":114,"y2":113,"stroke":79,"style":115},"174","145","214","stroke-width:1.8;marker-end:url(#dpClassesArrow)",[57,117],{"x":118,"y":119,"width":120,"height":121,"rx":71,"fill":122,"stroke":123,"style":97},"218","52","200","190","#eef7f4","#0f766e",[81,125,128],{"x":126,"y":127,"style":102,"fill":123,"textAnchor":87},"318","94.6","provider",[81,130,132],{"x":126,"y":131,"style":107,"fill":79,"textAnchor":87},"122.6","fields()",[81,134,136],{"x":126,"y":135,"style":107,"fill":79,"textAnchor":87},"150.6","wkbType() · crs()",[81,138,140],{"x":126,"y":139,"style":107,"fill":79,"textAnchor":87},"178.6","extent()",[81,142,144],{"x":126,"y":143,"style":107,"fill":79,"textAnchor":87},"206.6","featureCount()",[110,146],{"x1":147,"y1":113,"x2":148,"y2":113,"stroke":79,"style":115},"418","458",[57,150],{"x":151,"y":152,"width":153,"height":153,"rx":71,"fill":154,"stroke":155,"style":97},"462","80","130","#eff3ff","#2563eb",[81,157,160],{"x":158,"y":159,"style":102,"fill":155,"textAnchor":87},"527","124.6","feature source",[81,162,164],{"x":158,"y":163,"style":107,"fill":96,"textAnchor":87},"148.6","snapshot for",[81,166,168],{"x":158,"y":167,"style":107,"fill":96,"textAnchor":87},"172.6","threads",[110,170],{"x1":171,"y1":113,"x2":172,"y2":113,"stroke":79,"style":115},"592","622",[57,174],{"x":175,"y":152,"width":92,"height":153,"rx":71,"fill":176,"stroke":177,"style":97},"626","#fdf2e2","#b45309",[81,179,181],{"x":180,"y":159,"style":102,"fill":177,"textAnchor":87},"681","iterator",[81,183,184],{"x":180,"y":163,"style":107,"fill":96,"textAnchor":87},"fetchFeature()",[81,186,187],{"x":180,"y":167,"style":107,"fill":96,"textAnchor":87},"per request",[189,190,192],"h2",{"id":191},"prerequisites","Prerequisites",[194,195,196,200,203],"ul",{},[197,198,199],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series. Python providers have been possible since 3.12; the API shown follows QGIS's own Python provider tests.",[197,201,202],{},"A data source accessible from Python. The example wraps a list of sensor records; in practice it might be a REST client or a database driver.",[197,204,205,206,210],{},"Comfort with Python classes and with how layers read features, as in ",[34,207,209],{"href":208},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fread-feature-attributes-and-geometry-pyqgis\u002F","reading feature attributes and geometry",".",[189,212,214],{"id":213},"the-data-source","The data source",[14,216,217],{},"The provider wraps something that can list records. Keeping that access in its own small class separates \"how to talk to the source\" from \"how to be a QGIS provider\".",[219,220,225],"pre",{"className":221,"code":222,"language":223,"meta":224,"style":224},"language-python shiki shiki-themes github-dark","class SensorSource:\n    \"\"\"Stand-in for a REST client or driver: records with id, x, y, attributes.\"\"\"\n    def __init__(self, uri):\n        self.uri = uri                     # e.g. \"https:\u002F\u002Fsensors.example.org\u002Fapi?network=air\"\n        self._records = [\n            {\"id\": 1, \"x\": 13.40, \"y\": 52.52, \"name\": \"Mitte\", \"pm25\": 11.2},\n            {\"id\": 2, \"x\": 13.45, \"y\": 52.50, \"name\": \"Friedrichshain\", \"pm25\": 14.8},\n            {\"id\": 3, \"x\": 13.31, \"y\": 52.51, \"name\": \"Charlottenburg\", \"pm25\": 9.6},\n        ]\n\n    def records(self, bbox=None, ids=None):\n        for r in self._records:\n            if ids is not None and r[\"id\"] not in ids:\n                continue\n            if bbox and not (bbox[0] \u003C= r[\"x\"] \u003C= bbox[2] and bbox[1] \u003C= r[\"y\"] \u003C= bbox[3]):\n                continue\n            yield r\n","python","",[18,226,227,243,250,263,282,295,354,404,454,460,467,493,511,549,555,618,623],{"__ignoreMap":224},[228,229,231,235,239],"span",{"class":110,"line":230},1,[228,232,234],{"class":233},"snl16","class",[228,236,238],{"class":237},"svObZ"," SensorSource",[228,240,242],{"class":241},"s95oV",":\n",[228,244,246],{"class":110,"line":245},2,[228,247,249],{"class":248},"sU2Wk","    \"\"\"Stand-in for a REST client or driver: records with id, x, y, attributes.\"\"\"\n",[228,251,253,256,260],{"class":110,"line":252},3,[228,254,255],{"class":233},"    def",[228,257,259],{"class":258},"sDLfK"," __init__",[228,261,262],{"class":241},"(self, uri):\n",[228,264,266,269,272,275,278],{"class":110,"line":265},4,[228,267,268],{"class":258},"        self",[228,270,271],{"class":241},".uri ",[228,273,274],{"class":233},"=",[228,276,277],{"class":241}," uri                     ",[228,279,281],{"class":280},"sjoCn","# e.g. \"https:\u002F\u002Fsensors.example.org\u002Fapi?network=air\"\n",[228,283,285,287,290,292],{"class":110,"line":284},5,[228,286,268],{"class":258},[228,288,289],{"class":241},"._records ",[228,291,274],{"class":233},[228,293,294],{"class":241}," [\n",[228,296,298,301,304,307,310,313,316,318,321,323,326,328,331,333,336,338,341,343,346,348,351],{"class":110,"line":297},6,[228,299,300],{"class":241},"            {",[228,302,303],{"class":248},"\"id\"",[228,305,306],{"class":241},": ",[228,308,309],{"class":258},"1",[228,311,312],{"class":241},", ",[228,314,315],{"class":248},"\"x\"",[228,317,306],{"class":241},[228,319,320],{"class":258},"13.40",[228,322,312],{"class":241},[228,324,325],{"class":248},"\"y\"",[228,327,306],{"class":241},[228,329,330],{"class":258},"52.52",[228,332,312],{"class":241},[228,334,335],{"class":248},"\"name\"",[228,337,306],{"class":241},[228,339,340],{"class":248},"\"Mitte\"",[228,342,312],{"class":241},[228,344,345],{"class":248},"\"pm25\"",[228,347,306],{"class":241},[228,349,350],{"class":258},"11.2",[228,352,353],{"class":241},"},\n",[228,355,357,359,361,363,366,368,370,372,375,377,379,381,384,386,388,390,393,395,397,399,402],{"class":110,"line":356},7,[228,358,300],{"class":241},[228,360,303],{"class":248},[228,362,306],{"class":241},[228,364,365],{"class":258},"2",[228,367,312],{"class":241},[228,369,315],{"class":248},[228,371,306],{"class":241},[228,373,374],{"class":258},"13.45",[228,376,312],{"class":241},[228,378,325],{"class":248},[228,380,306],{"class":241},[228,382,383],{"class":258},"52.50",[228,385,312],{"class":241},[228,387,335],{"class":248},[228,389,306],{"class":241},[228,391,392],{"class":248},"\"Friedrichshain\"",[228,394,312],{"class":241},[228,396,345],{"class":248},[228,398,306],{"class":241},[228,400,401],{"class":258},"14.8",[228,403,353],{"class":241},[228,405,407,409,411,413,416,418,420,422,425,427,429,431,434,436,438,440,443,445,447,449,452],{"class":110,"line":406},8,[228,408,300],{"class":241},[228,410,303],{"class":248},[228,412,306],{"class":241},[228,414,415],{"class":258},"3",[228,417,312],{"class":241},[228,419,315],{"class":248},[228,421,306],{"class":241},[228,423,424],{"class":258},"13.31",[228,426,312],{"class":241},[228,428,325],{"class":248},[228,430,306],{"class":241},[228,432,433],{"class":258},"52.51",[228,435,312],{"class":241},[228,437,335],{"class":248},[228,439,306],{"class":241},[228,441,442],{"class":248},"\"Charlottenburg\"",[228,444,312],{"class":241},[228,446,345],{"class":248},[228,448,306],{"class":241},[228,450,451],{"class":258},"9.6",[228,453,353],{"class":241},[228,455,457],{"class":110,"line":456},9,[228,458,459],{"class":241},"        ]\n",[228,461,463],{"class":110,"line":462},10,[228,464,466],{"emptyLinePlaceholder":465},true,"\n",[228,468,470,472,475,478,480,483,486,488,490],{"class":110,"line":469},11,[228,471,255],{"class":233},[228,473,474],{"class":237}," records",[228,476,477],{"class":241},"(self, bbox",[228,479,274],{"class":233},[228,481,482],{"class":258},"None",[228,484,485],{"class":241},", ids",[228,487,274],{"class":233},[228,489,482],{"class":258},[228,491,492],{"class":241},"):\n",[228,494,496,499,502,505,508],{"class":110,"line":495},12,[228,497,498],{"class":233},"        for",[228,500,501],{"class":241}," r ",[228,503,504],{"class":233},"in",[228,506,507],{"class":258}," self",[228,509,510],{"class":241},"._records:\n",[228,512,514,517,520,523,526,529,532,535,537,540,543,546],{"class":110,"line":513},13,[228,515,516],{"class":233},"            if",[228,518,519],{"class":241}," ids ",[228,521,522],{"class":233},"is",[228,524,525],{"class":233}," not",[228,527,528],{"class":258}," None",[228,530,531],{"class":233}," and",[228,533,534],{"class":241}," r[",[228,536,303],{"class":248},[228,538,539],{"class":241},"] ",[228,541,542],{"class":233},"not",[228,544,545],{"class":233}," in",[228,547,548],{"class":241}," ids:\n",[228,550,552],{"class":110,"line":551},14,[228,553,554],{"class":233},"                continue\n",[228,556,558,560,563,566,568,571,573,575,578,580,582,584,586,589,591,593,595,597,599,601,603,605,607,609,611,613,615],{"class":110,"line":557},15,[228,559,516],{"class":233},[228,561,562],{"class":241}," bbox ",[228,564,565],{"class":233},"and",[228,567,525],{"class":233},[228,569,570],{"class":241}," (bbox[",[228,572,59],{"class":258},[228,574,539],{"class":241},[228,576,577],{"class":233},"\u003C=",[228,579,534],{"class":241},[228,581,315],{"class":248},[228,583,539],{"class":241},[228,585,577],{"class":233},[228,587,588],{"class":241}," bbox[",[228,590,365],{"class":258},[228,592,539],{"class":241},[228,594,565],{"class":233},[228,596,588],{"class":241},[228,598,309],{"class":258},[228,600,539],{"class":241},[228,602,577],{"class":233},[228,604,534],{"class":241},[228,606,325],{"class":248},[228,608,539],{"class":241},[228,610,577],{"class":233},[228,612,588],{"class":241},[228,614,415],{"class":258},[228,616,617],{"class":241},"]):\n",[228,619,621],{"class":110,"line":620},16,[228,622,554],{"class":233},[228,624,626,629],{"class":110,"line":625},17,[228,627,628],{"class":233},"            yield",[228,630,631],{"class":241}," r\n",[14,633,634],{},[42,635,638,641,644,647,654,657,660,665,669,673,679,682,690,696,699,702,706,710,714,717,721,725],{"viewBox":636,"role":45,"ariaLabel":637,"xmlns":47},"0 0 760 222","Request filters split into those pushed down to the source — rectangle and ids — and those applied in Python — expression and reprojection",[49,639,640],{},"Push filters down to the source",[53,642,643],{},"A feature request from QGIS carries a rectangle, a list of feature ids, an expression and a destination CRS. The rectangle and ids are translated into the source's own query, so only matching records cross the network. The expression is evaluated in Python per record as a fallback, and the destination CRS is applied to each geometry before returning it.",[57,645],{"x":59,"y":59,"width":60,"height":646,"fill":62},"222",[64,648,649],{},[67,650,652],{"id":651,"viewBox":70,"refX":71,"refY":72,"markerWidth":73,"markerHeight":73,"orient":74},"dpPushArrow",[76,653],{"d":78,"fill":79},[81,655,656],{"x":83,"y":84,"style":85,"fill":86,"textAnchor":87},"Cheap filters at the source, the rest in Python",[57,658],{"x":91,"y":659,"width":120,"height":153,"rx":71,"fill":95,"stroke":96,"style":97},"60",[81,661,664],{"x":662,"y":663,"style":102,"fill":86,"textAnchor":87},"124","104.6","QgsFeatureRequest",[81,666,668],{"x":662,"y":667,"style":107,"fill":96,"textAnchor":87},"128.6","rect · fids",[81,670,672],{"x":662,"y":671,"style":107,"fill":96,"textAnchor":87},"152.6","expression · CRS",[110,674],{"x1":675,"y1":676,"x2":677,"y2":152,"stroke":79,"style":678},"224","100","266","stroke-width:1.8;marker-end:url(#dpPushArrow)",[110,680],{"x1":675,"y1":93,"x2":677,"y2":681,"stroke":79,"style":678},"170",[57,683],{"x":684,"y":685,"width":686,"height":687,"rx":71,"fill":688,"stroke":689,"style":97},"270","44","230","72","#e8efe6","#15803d",[81,691,695],{"x":692,"y":693,"style":102,"fill":694,"textAnchor":87},"385","72.6","#166534","pushed to source",[81,697,698],{"x":692,"y":127,"style":107,"fill":96,"textAnchor":87},"bbox, ids → query",[57,700],{"x":684,"y":701,"width":686,"height":687,"rx":71,"fill":176,"stroke":177,"style":97},"134",[81,703,705],{"x":692,"y":704,"style":102,"fill":177,"textAnchor":87},"162.6","applied in Python",[81,707,709],{"x":692,"y":708,"style":107,"fill":96,"textAnchor":87},"184.6","expression, transform",[110,711],{"x1":712,"y1":152,"x2":713,"y2":92,"stroke":79,"style":678},"500","542",[110,715],{"x1":712,"y1":681,"x2":713,"y2":716,"stroke":79,"style":678},"140",[57,718],{"x":719,"y":720,"width":121,"height":94,"rx":71,"fill":154,"stroke":155,"style":97},"546","90",[81,722,184],{"x":723,"y":724,"style":102,"fill":155,"textAnchor":87},"641","117.6",[81,726,728],{"x":723,"y":727,"style":107,"fill":96,"textAnchor":87},"139.6","one feature",[14,730,731,735,736,739,740,743],{},[732,733,734],"strong",{},"Breakdown:"," The ",[18,737,738],{},"records"," method accepts the two filters most sources can apply cheaply — a bounding box and a list of ids — so the provider can push them down instead of fetching everything. A real implementation would turn them into query parameters on an HTTP request or a ",[18,741,742],{},"WHERE"," clause. Ids must be stable integers, because QGIS uses them as feature ids.",[189,745,747],{"id":746},"the-feature-iterator","The feature iterator",[14,749,750,751,754],{},"The iterator does the actual reading. QGIS calls ",[18,752,753],{},"fetchFeature"," repeatedly until it returns False; each call fills one feature. The iterator must honour the request — feature ids, a filter rectangle, an expression — or QGIS will draw and select the wrong features.",[219,756,758],{"className":221,"code":757,"language":223,"meta":224,"style":224},"from qgis.core import (QgsAbstractFeatureIterator, QgsAbstractFeatureSource,\n                       QgsFeatureIterator, QgsFeatureRequest, QgsFeature, QgsGeometry,\n                       QgsPointXY, QgsCoordinateTransform)\n\nclass SensorIterator(QgsAbstractFeatureIterator):\n    def __init__(self, source, request):\n        super().__init__(request)\n        self._source = source\n        self._request = request if request is not None else QgsFeatureRequest()\n        self._transform = QgsCoordinateTransform()\n        dest = self._request.destinationCrs()\n        if dest.isValid() and dest != source.crs:\n            self._transform = QgsCoordinateTransform(source.crs, dest,\n                                                     self._request.transformContext())\n        rect = self._request.filterRect()\n        bbox = None\n        if not rect.isNull():\n            if self._transform.isValid():\n                rect = self._transform.transformBoundingBox(rect, QgsCoordinateTransform.ReverseTransform)\n            bbox = (rect.xMinimum(), rect.yMinimum(), rect.xMaximum(), rect.yMaximum())\n        ids = None\n        if self._request.filterType() == QgsFeatureRequest.FilterFid:\n            ids = {self._request.filterFid()}\n        elif self._request.filterType() == QgsFeatureRequest.FilterFids:\n            ids = set(self._request.filterFids())\n        self._records = iter(list(source.client.records(bbox=bbox, ids=ids)))\n\n    def fetchFeature(self, f):\n        for r in self._records:\n            f.setFields(self._source.fields, True)\n            f.setId(r[\"id\"])\n            f.setAttributes([r[\"id\"], r[\"name\"], r[\"pm25\"]])\n            geom = QgsGeometry.fromPointXY(QgsPointXY(r[\"x\"], r[\"y\"]))\n            if self._transform.isValid():\n                geom.transform(self._transform)\n            f.setGeometry(geom)\n            f.setValid(True)\n            if self._request.filterType() == QgsFeatureRequest.FilterExpression:\n                self._request.expressionContext().setFeature(f)\n                if not self._request.filterExpression().evaluate(self._request.expressionContext()):\n                    continue\n            return True\n        return False\n\n    def __iter__(self):\n        return self\n\n    def __next__(self):\n        feature = QgsFeature()\n        if not self.nextFeature(feature):\n            raise StopIteration\n        return feature\n\n    def rewind(self):\n        return False\n\n    def close(self):\n        self._records = iter(())\n        return True\n",[18,759,760,774,779,784,788,803,812,826,838,867,879,891,910,922,930,942,952,961,971,984,995,1005,1021,1038,1053,1070,1107,1112,1123,1136,1153,1164,1184,1204,1213,1224,1230,1240,1254,1263,1281,1287,1296,1305,1310,1321,1329,1334,1344,1355,1367,1376,1384,1389,1399,1406,1411,1421,1435],{"__ignoreMap":224},[228,761,762,765,768,771],{"class":110,"line":230},[228,763,764],{"class":233},"from",[228,766,767],{"class":241}," qgis.core ",[228,769,770],{"class":233},"import",[228,772,773],{"class":241}," (QgsAbstractFeatureIterator, QgsAbstractFeatureSource,\n",[228,775,776],{"class":110,"line":245},[228,777,778],{"class":241},"                       QgsFeatureIterator, QgsFeatureRequest, QgsFeature, QgsGeometry,\n",[228,780,781],{"class":110,"line":252},[228,782,783],{"class":241},"                       QgsPointXY, QgsCoordinateTransform)\n",[228,785,786],{"class":110,"line":265},[228,787,466],{"emptyLinePlaceholder":465},[228,789,790,792,795,798,801],{"class":110,"line":284},[228,791,234],{"class":233},[228,793,794],{"class":237}," SensorIterator",[228,796,797],{"class":241},"(",[228,799,800],{"class":237},"QgsAbstractFeatureIterator",[228,802,492],{"class":241},[228,804,805,807,809],{"class":110,"line":297},[228,806,255],{"class":233},[228,808,259],{"class":258},[228,810,811],{"class":241},"(self, source, request):\n",[228,813,814,817,820,823],{"class":110,"line":356},[228,815,816],{"class":258},"        super",[228,818,819],{"class":241},"().",[228,821,822],{"class":258},"__init__",[228,824,825],{"class":241},"(request)\n",[228,827,828,830,833,835],{"class":110,"line":406},[228,829,268],{"class":258},[228,831,832],{"class":241},"._source ",[228,834,274],{"class":233},[228,836,837],{"class":241}," source\n",[228,839,840,842,845,847,850,853,855,857,859,861,864],{"class":110,"line":456},[228,841,268],{"class":258},[228,843,844],{"class":241},"._request ",[228,846,274],{"class":233},[228,848,849],{"class":241}," request ",[228,851,852],{"class":233},"if",[228,854,849],{"class":241},[228,856,522],{"class":233},[228,858,525],{"class":233},[228,860,528],{"class":258},[228,862,863],{"class":233}," else",[228,865,866],{"class":241}," QgsFeatureRequest()\n",[228,868,869,871,874,876],{"class":110,"line":462},[228,870,268],{"class":258},[228,872,873],{"class":241},"._transform ",[228,875,274],{"class":233},[228,877,878],{"class":241}," QgsCoordinateTransform()\n",[228,880,881,884,886,888],{"class":110,"line":469},[228,882,883],{"class":241},"        dest ",[228,885,274],{"class":233},[228,887,507],{"class":258},[228,889,890],{"class":241},"._request.destinationCrs()\n",[228,892,893,896,899,901,904,907],{"class":110,"line":495},[228,894,895],{"class":233},"        if",[228,897,898],{"class":241}," dest.isValid() ",[228,900,565],{"class":233},[228,902,903],{"class":241}," dest ",[228,905,906],{"class":233},"!=",[228,908,909],{"class":241}," source.crs:\n",[228,911,912,915,917,919],{"class":110,"line":513},[228,913,914],{"class":258},"            self",[228,916,873],{"class":241},[228,918,274],{"class":233},[228,920,921],{"class":241}," QgsCoordinateTransform(source.crs, dest,\n",[228,923,924,927],{"class":110,"line":551},[228,925,926],{"class":258},"                                                     self",[228,928,929],{"class":241},"._request.transformContext())\n",[228,931,932,935,937,939],{"class":110,"line":557},[228,933,934],{"class":241},"        rect ",[228,936,274],{"class":233},[228,938,507],{"class":258},[228,940,941],{"class":241},"._request.filterRect()\n",[228,943,944,947,949],{"class":110,"line":620},[228,945,946],{"class":241},"        bbox ",[228,948,274],{"class":233},[228,950,951],{"class":258}," None\n",[228,953,954,956,958],{"class":110,"line":625},[228,955,895],{"class":233},[228,957,525],{"class":233},[228,959,960],{"class":241}," rect.isNull():\n",[228,962,964,966,968],{"class":110,"line":963},18,[228,965,516],{"class":233},[228,967,507],{"class":258},[228,969,970],{"class":241},"._transform.isValid():\n",[228,972,974,977,979,981],{"class":110,"line":973},19,[228,975,976],{"class":241},"                rect ",[228,978,274],{"class":233},[228,980,507],{"class":258},[228,982,983],{"class":241},"._transform.transformBoundingBox(rect, QgsCoordinateTransform.ReverseTransform)\n",[228,985,987,990,992],{"class":110,"line":986},20,[228,988,989],{"class":241},"            bbox ",[228,991,274],{"class":233},[228,993,994],{"class":241}," (rect.xMinimum(), rect.yMinimum(), rect.xMaximum(), rect.yMaximum())\n",[228,996,998,1001,1003],{"class":110,"line":997},21,[228,999,1000],{"class":241},"        ids ",[228,1002,274],{"class":233},[228,1004,951],{"class":258},[228,1006,1008,1010,1012,1015,1018],{"class":110,"line":1007},22,[228,1009,895],{"class":233},[228,1011,507],{"class":258},[228,1013,1014],{"class":241},"._request.filterType() ",[228,1016,1017],{"class":233},"==",[228,1019,1020],{"class":241}," QgsFeatureRequest.FilterFid:\n",[228,1022,1024,1027,1029,1032,1035],{"class":110,"line":1023},23,[228,1025,1026],{"class":241},"            ids ",[228,1028,274],{"class":233},[228,1030,1031],{"class":241}," {",[228,1033,1034],{"class":258},"self",[228,1036,1037],{"class":241},"._request.filterFid()}\n",[228,1039,1041,1044,1046,1048,1050],{"class":110,"line":1040},24,[228,1042,1043],{"class":233},"        elif",[228,1045,507],{"class":258},[228,1047,1014],{"class":241},[228,1049,1017],{"class":233},[228,1051,1052],{"class":241}," QgsFeatureRequest.FilterFids:\n",[228,1054,1056,1058,1060,1063,1065,1067],{"class":110,"line":1055},25,[228,1057,1026],{"class":241},[228,1059,274],{"class":233},[228,1061,1062],{"class":258}," set",[228,1064,797],{"class":241},[228,1066,1034],{"class":258},[228,1068,1069],{"class":241},"._request.filterFids())\n",[228,1071,1073,1075,1077,1079,1082,1084,1087,1090,1094,1096,1099,1102,1104],{"class":110,"line":1072},26,[228,1074,268],{"class":258},[228,1076,289],{"class":241},[228,1078,274],{"class":233},[228,1080,1081],{"class":258}," iter",[228,1083,797],{"class":241},[228,1085,1086],{"class":258},"list",[228,1088,1089],{"class":241},"(source.client.records(",[228,1091,1093],{"class":1092},"s9osk","bbox",[228,1095,274],{"class":233},[228,1097,1098],{"class":241},"bbox, ",[228,1100,1101],{"class":1092},"ids",[228,1103,274],{"class":233},[228,1105,1106],{"class":241},"ids)))\n",[228,1108,1110],{"class":110,"line":1109},27,[228,1111,466],{"emptyLinePlaceholder":465},[228,1113,1115,1117,1120],{"class":110,"line":1114},28,[228,1116,255],{"class":233},[228,1118,1119],{"class":237}," fetchFeature",[228,1121,1122],{"class":241},"(self, f):\n",[228,1124,1126,1128,1130,1132,1134],{"class":110,"line":1125},29,[228,1127,498],{"class":233},[228,1129,501],{"class":241},[228,1131,504],{"class":233},[228,1133,507],{"class":258},[228,1135,510],{"class":241},[228,1137,1139,1142,1144,1147,1150],{"class":110,"line":1138},30,[228,1140,1141],{"class":241},"            f.setFields(",[228,1143,1034],{"class":258},[228,1145,1146],{"class":241},"._source.fields, ",[228,1148,1149],{"class":258},"True",[228,1151,1152],{"class":241},")\n",[228,1154,1156,1159,1161],{"class":110,"line":1155},31,[228,1157,1158],{"class":241},"            f.setId(r[",[228,1160,303],{"class":248},[228,1162,1163],{"class":241},"])\n",[228,1165,1167,1170,1172,1175,1177,1179,1181],{"class":110,"line":1166},32,[228,1168,1169],{"class":241},"            f.setAttributes([r[",[228,1171,303],{"class":248},[228,1173,1174],{"class":241},"], r[",[228,1176,335],{"class":248},[228,1178,1174],{"class":241},[228,1180,345],{"class":248},[228,1182,1183],{"class":241},"]])\n",[228,1185,1187,1190,1192,1195,1197,1199,1201],{"class":110,"line":1186},33,[228,1188,1189],{"class":241},"            geom ",[228,1191,274],{"class":233},[228,1193,1194],{"class":241}," QgsGeometry.fromPointXY(QgsPointXY(r[",[228,1196,315],{"class":248},[228,1198,1174],{"class":241},[228,1200,325],{"class":248},[228,1202,1203],{"class":241},"]))\n",[228,1205,1207,1209,1211],{"class":110,"line":1206},34,[228,1208,516],{"class":233},[228,1210,507],{"class":258},[228,1212,970],{"class":241},[228,1214,1216,1219,1221],{"class":110,"line":1215},35,[228,1217,1218],{"class":241},"                geom.transform(",[228,1220,1034],{"class":258},[228,1222,1223],{"class":241},"._transform)\n",[228,1225,1227],{"class":110,"line":1226},36,[228,1228,1229],{"class":241},"            f.setGeometry(geom)\n",[228,1231,1233,1236,1238],{"class":110,"line":1232},37,[228,1234,1235],{"class":241},"            f.setValid(",[228,1237,1149],{"class":258},[228,1239,1152],{"class":241},[228,1241,1243,1245,1247,1249,1251],{"class":110,"line":1242},38,[228,1244,516],{"class":233},[228,1246,507],{"class":258},[228,1248,1014],{"class":241},[228,1250,1017],{"class":233},[228,1252,1253],{"class":241}," QgsFeatureRequest.FilterExpression:\n",[228,1255,1257,1260],{"class":110,"line":1256},39,[228,1258,1259],{"class":258},"                self",[228,1261,1262],{"class":241},"._request.expressionContext().setFeature(f)\n",[228,1264,1266,1269,1271,1273,1276,1278],{"class":110,"line":1265},40,[228,1267,1268],{"class":233},"                if",[228,1270,525],{"class":233},[228,1272,507],{"class":258},[228,1274,1275],{"class":241},"._request.filterExpression().evaluate(",[228,1277,1034],{"class":258},[228,1279,1280],{"class":241},"._request.expressionContext()):\n",[228,1282,1284],{"class":110,"line":1283},41,[228,1285,1286],{"class":233},"                    continue\n",[228,1288,1290,1293],{"class":110,"line":1289},42,[228,1291,1292],{"class":233},"            return",[228,1294,1295],{"class":258}," True\n",[228,1297,1299,1302],{"class":110,"line":1298},43,[228,1300,1301],{"class":233},"        return",[228,1303,1304],{"class":258}," False\n",[228,1306,1308],{"class":110,"line":1307},44,[228,1309,466],{"emptyLinePlaceholder":465},[228,1311,1313,1315,1318],{"class":110,"line":1312},45,[228,1314,255],{"class":233},[228,1316,1317],{"class":258}," __iter__",[228,1319,1320],{"class":241},"(self):\n",[228,1322,1324,1326],{"class":110,"line":1323},46,[228,1325,1301],{"class":233},[228,1327,1328],{"class":258}," self\n",[228,1330,1332],{"class":110,"line":1331},47,[228,1333,466],{"emptyLinePlaceholder":465},[228,1335,1337,1339,1342],{"class":110,"line":1336},48,[228,1338,255],{"class":233},[228,1340,1341],{"class":258}," __next__",[228,1343,1320],{"class":241},[228,1345,1347,1350,1352],{"class":110,"line":1346},49,[228,1348,1349],{"class":241},"        feature ",[228,1351,274],{"class":233},[228,1353,1354],{"class":241}," QgsFeature()\n",[228,1356,1358,1360,1362,1364],{"class":110,"line":1357},50,[228,1359,895],{"class":233},[228,1361,525],{"class":233},[228,1363,507],{"class":258},[228,1365,1366],{"class":241},".nextFeature(feature):\n",[228,1368,1370,1373],{"class":110,"line":1369},51,[228,1371,1372],{"class":233},"            raise",[228,1374,1375],{"class":258}," StopIteration\n",[228,1377,1379,1381],{"class":110,"line":1378},52,[228,1380,1301],{"class":233},[228,1382,1383],{"class":241}," feature\n",[228,1385,1387],{"class":110,"line":1386},53,[228,1388,466],{"emptyLinePlaceholder":465},[228,1390,1392,1394,1397],{"class":110,"line":1391},54,[228,1393,255],{"class":233},[228,1395,1396],{"class":237}," rewind",[228,1398,1320],{"class":241},[228,1400,1402,1404],{"class":110,"line":1401},55,[228,1403,1301],{"class":233},[228,1405,1304],{"class":258},[228,1407,1409],{"class":110,"line":1408},56,[228,1410,466],{"emptyLinePlaceholder":465},[228,1412,1414,1416,1419],{"class":110,"line":1413},57,[228,1415,255],{"class":233},[228,1417,1418],{"class":237}," close",[228,1420,1320],{"class":241},[228,1422,1424,1426,1428,1430,1432],{"class":110,"line":1423},58,[228,1425,268],{"class":258},[228,1427,289],{"class":241},[228,1429,274],{"class":233},[228,1431,1081],{"class":258},[228,1433,1434],{"class":241},"(())\n",[228,1436,1438,1440],{"class":110,"line":1437},59,[228,1439,1301],{"class":233},[228,1441,1295],{"class":258},[14,1443,1444,1446,1447,1449,1450,1453,1454,1457,1458,1461,1462,1465],{},[732,1445,734],{}," The constructor translates the request into what the source understands: a bounding box (transformed back into the source CRS if QGIS asked for features in another CRS) and a set of ids. ",[18,1448,753],{}," builds each feature with the provider's fields, a stable id, attributes in field order and a geometry, transformed to the destination CRS when requested — the map canvas often asks for features in the project CRS. Expression filters are evaluated per feature as a fallback, since most sources cannot evaluate QGIS expressions. ",[18,1451,1452],{},"__iter__"," and ",[18,1455,1456],{},"__next__"," make the iterator usable in Python ",[18,1459,1460],{},"for"," loops; ",[18,1463,1464],{},"rewind"," returning False tells QGIS to create a new iterator rather than restart this one.",[189,1467,1469],{"id":1468},"the-feature-source-and-the-provider","The feature source and the provider",[14,1471,1472],{},"The feature source is a snapshot of everything the iterator needs, safe to hand to rendering threads. The provider describes the layer and creates sources and iterators.",[14,1474,1475],{},[42,1476,1479,1482,1485,1488,1491,1495,1501,1505,1509,1513,1516,1519,1521,1523,1526,1529,1533,1536,1539],{"viewBox":1477,"role":45,"ariaLabel":1478,"xmlns":47},"0 0 760 232","The questions a provider answers: fields, geometry type, CRS, extent, feature count, validity, name and capabilities",[49,1480,1481],{},"What the provider must answer",[53,1483,1484],{},"The provider answers QGIS's questions about the layer: its fields, geometry type, CRS and extent for the layer properties and map, its feature count for the attribute table, whether it is valid, and its name and description. It also says what it can do through capabilities; a read-only provider reports only reading and selection capabilities.",[57,1486],{"x":59,"y":59,"width":60,"height":1487,"fill":62},"232",[81,1489,1490],{"x":83,"y":84,"style":85,"fill":86,"textAnchor":87},"Describe the layer, then serve features",[57,1492],{"x":91,"y":1493,"width":646,"height":1494,"rx":71,"fill":122,"stroke":123,"style":97},"56","160",[81,1496,1500],{"x":1497,"y":1498,"style":1499,"fill":123,"textAnchor":87},"135","97.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","schema",[81,1502,132],{"x":1497,"y":1503,"style":1504,"fill":79,"textAnchor":87},"125.78","text-anchor:middle;font-size:10.5px;font-family:sans-serif",[81,1506,1508],{"x":1497,"y":1507,"style":1504,"fill":79,"textAnchor":87},"153.78","wkbType()",[81,1510,1512],{"x":1497,"y":1511,"style":1504,"fill":79,"textAnchor":87},"181.78","crs()",[57,1514],{"x":1515,"y":1493,"width":646,"height":1494,"rx":71,"fill":154,"stroke":155,"style":97},"269",[81,1517,1518],{"x":83,"y":1498,"style":1499,"fill":155,"textAnchor":87},"size and place",[81,1520,140],{"x":83,"y":1503,"style":1504,"fill":79,"textAnchor":87},[81,1522,144],{"x":83,"y":1507,"style":1504,"fill":79,"textAnchor":87},[81,1524,1525],{"x":83,"y":1511,"style":1504,"fill":79,"textAnchor":87},"isValid()",[57,1527],{"x":1528,"y":1493,"width":646,"height":1494,"rx":71,"fill":176,"stroke":177,"style":97},"514",[81,1530,1532],{"x":1531,"y":1498,"style":1499,"fill":177,"textAnchor":87},"625","identity",[81,1534,1535],{"x":1531,"y":1503,"style":1504,"fill":79,"textAnchor":87},"name() · description()",[81,1537,1538],{"x":1531,"y":1507,"style":1504,"fill":79,"textAnchor":87},"capabilities()",[81,1540,1541],{"x":1531,"y":1511,"style":1504,"fill":96,"textAnchor":87},"read-only here",[219,1543,1545],{"className":221,"code":1544,"language":223,"meta":224,"style":224},"from qgis.core import (QgsVectorDataProvider, QgsDataProvider, QgsFields, QgsField,\n                       QgsWkbTypes, QgsCoordinateReferenceSystem, QgsRectangle)\nfrom qgis.PyQt.QtCore import QVariant\n\nclass SensorFeatureSource(QgsAbstractFeatureSource):\n    def __init__(self, provider):\n        super().__init__()\n        self.client = provider._client\n        self.fields = provider.fields()\n        self.crs = provider.crs()\n\n    def getFeatures(self, request):\n        return QgsFeatureIterator(SensorIterator(self, request))\n\nclass SensorProvider(QgsVectorDataProvider):\n    KEY = \"pysensors\"\n\n    @classmethod\n    def providerKey(cls):\n        return cls.KEY\n\n    @classmethod\n    def description(cls):\n        return \"Sensor network (Python provider)\"\n\n    @classmethod\n    def createProvider(cls, uri, options, flags=QgsDataProvider.ReadFlags()):\n        return SensorProvider(uri, options, flags)\n\n    def __init__(self, uri=\"\", options=QgsDataProvider.ProviderOptions(),\n                 flags=QgsDataProvider.ReadFlags()):\n        super().__init__(uri)\n        self._uri = uri\n        self._client = SensorSource(uri)\n        self._fields = QgsFields()\n        self._fields.append(QgsField(\"id\", QVariant.Int))\n        self._fields.append(QgsField(\"name\", QVariant.String))\n        self._fields.append(QgsField(\"pm25\", QVariant.Double))\n        self._crs = QgsCoordinateReferenceSystem(\"EPSG:4326\")\n\n    def featureSource(self):\n        return SensorFeatureSource(self)\n\n    def getFeatures(self, request=QgsFeatureRequest()):\n        return QgsFeatureIterator(SensorIterator(SensorFeatureSource(self), request))\n\n    def dataSourceUri(self, expandAuthConfig=False):\n        return self._uri\n\n    def storageType(self):\n        return \"Python sensor API\"\n\n    def wkbType(self):\n        return QgsWkbTypes.Point\n\n    def fields(self):\n        return self._fields\n\n    def crs(self):\n        return self._crs\n\n    def featureCount(self):\n        return sum(1 for _ in self._client.records())\n\n    def extent(self):\n        rect = QgsRectangle()\n        for r in self._client.records():\n            rect.combineExtentWith(r[\"x\"], r[\"y\"])\n        return rect\n\n    def isValid(self):\n        return True\n\n    def name(self):\n        return self.KEY\n\n    def capabilities(self):\n        return QgsVectorDataProvider.SelectAtId\n",[18,1546,1547,1558,1563,1575,1579,1593,1602,1613,1625,1637,1649,1653,1663,1675,1679,1693,1704,1708,1716,1726,1738,1742,1748,1757,1764,1768,1774,1789,1796,1800,1822,1831,1842,1854,1866,1878,1890,1901,1912,1929,1933,1942,1953,1957,1971,1983,1987,2004,2013,2017,2026,2033,2037,2046,2053,2057,2066,2075,2079,2088,2098,2103,2113,2138,2143,2153,2163,2177,2191,2199,2204,2214,2221,2226,2236,2247,2252,2262],{"__ignoreMap":224},[228,1548,1549,1551,1553,1555],{"class":110,"line":230},[228,1550,764],{"class":233},[228,1552,767],{"class":241},[228,1554,770],{"class":233},[228,1556,1557],{"class":241}," (QgsVectorDataProvider, QgsDataProvider, QgsFields, QgsField,\n",[228,1559,1560],{"class":110,"line":245},[228,1561,1562],{"class":241},"                       QgsWkbTypes, QgsCoordinateReferenceSystem, QgsRectangle)\n",[228,1564,1565,1567,1570,1572],{"class":110,"line":252},[228,1566,764],{"class":233},[228,1568,1569],{"class":241}," qgis.PyQt.QtCore ",[228,1571,770],{"class":233},[228,1573,1574],{"class":241}," QVariant\n",[228,1576,1577],{"class":110,"line":265},[228,1578,466],{"emptyLinePlaceholder":465},[228,1580,1581,1583,1586,1588,1591],{"class":110,"line":284},[228,1582,234],{"class":233},[228,1584,1585],{"class":237}," SensorFeatureSource",[228,1587,797],{"class":241},[228,1589,1590],{"class":237},"QgsAbstractFeatureSource",[228,1592,492],{"class":241},[228,1594,1595,1597,1599],{"class":110,"line":297},[228,1596,255],{"class":233},[228,1598,259],{"class":258},[228,1600,1601],{"class":241},"(self, provider):\n",[228,1603,1604,1606,1608,1610],{"class":110,"line":356},[228,1605,816],{"class":258},[228,1607,819],{"class":241},[228,1609,822],{"class":258},[228,1611,1612],{"class":241},"()\n",[228,1614,1615,1617,1620,1622],{"class":110,"line":406},[228,1616,268],{"class":258},[228,1618,1619],{"class":241},".client ",[228,1621,274],{"class":233},[228,1623,1624],{"class":241}," provider._client\n",[228,1626,1627,1629,1632,1634],{"class":110,"line":456},[228,1628,268],{"class":258},[228,1630,1631],{"class":241},".fields ",[228,1633,274],{"class":233},[228,1635,1636],{"class":241}," provider.fields()\n",[228,1638,1639,1641,1644,1646],{"class":110,"line":462},[228,1640,268],{"class":258},[228,1642,1643],{"class":241},".crs ",[228,1645,274],{"class":233},[228,1647,1648],{"class":241}," provider.crs()\n",[228,1650,1651],{"class":110,"line":469},[228,1652,466],{"emptyLinePlaceholder":465},[228,1654,1655,1657,1660],{"class":110,"line":495},[228,1656,255],{"class":233},[228,1658,1659],{"class":237}," getFeatures",[228,1661,1662],{"class":241},"(self, request):\n",[228,1664,1665,1667,1670,1672],{"class":110,"line":513},[228,1666,1301],{"class":233},[228,1668,1669],{"class":241}," QgsFeatureIterator(SensorIterator(",[228,1671,1034],{"class":258},[228,1673,1674],{"class":241},", request))\n",[228,1676,1677],{"class":110,"line":551},[228,1678,466],{"emptyLinePlaceholder":465},[228,1680,1681,1683,1686,1688,1691],{"class":110,"line":557},[228,1682,234],{"class":233},[228,1684,1685],{"class":237}," SensorProvider",[228,1687,797],{"class":241},[228,1689,1690],{"class":237},"QgsVectorDataProvider",[228,1692,492],{"class":241},[228,1694,1695,1698,1701],{"class":110,"line":620},[228,1696,1697],{"class":258},"    KEY",[228,1699,1700],{"class":233}," =",[228,1702,1703],{"class":248}," \"pysensors\"\n",[228,1705,1706],{"class":110,"line":625},[228,1707,466],{"emptyLinePlaceholder":465},[228,1709,1710,1713],{"class":110,"line":963},[228,1711,1712],{"class":237},"    @",[228,1714,1715],{"class":258},"classmethod\n",[228,1717,1718,1720,1723],{"class":110,"line":973},[228,1719,255],{"class":233},[228,1721,1722],{"class":237}," providerKey",[228,1724,1725],{"class":241},"(cls):\n",[228,1727,1728,1730,1733,1735],{"class":110,"line":986},[228,1729,1301],{"class":233},[228,1731,1732],{"class":258}," cls",[228,1734,210],{"class":241},[228,1736,1737],{"class":258},"KEY\n",[228,1739,1740],{"class":110,"line":997},[228,1741,466],{"emptyLinePlaceholder":465},[228,1743,1744,1746],{"class":110,"line":1007},[228,1745,1712],{"class":237},[228,1747,1715],{"class":258},[228,1749,1750,1752,1755],{"class":110,"line":1023},[228,1751,255],{"class":233},[228,1753,1754],{"class":237}," description",[228,1756,1725],{"class":241},[228,1758,1759,1761],{"class":110,"line":1040},[228,1760,1301],{"class":233},[228,1762,1763],{"class":248}," \"Sensor network (Python provider)\"\n",[228,1765,1766],{"class":110,"line":1055},[228,1767,466],{"emptyLinePlaceholder":465},[228,1769,1770,1772],{"class":110,"line":1072},[228,1771,1712],{"class":237},[228,1773,1715],{"class":258},[228,1775,1776,1778,1781,1784,1786],{"class":110,"line":1109},[228,1777,255],{"class":233},[228,1779,1780],{"class":237}," createProvider",[228,1782,1783],{"class":241},"(cls, uri, options, flags",[228,1785,274],{"class":233},[228,1787,1788],{"class":241},"QgsDataProvider.ReadFlags()):\n",[228,1790,1791,1793],{"class":110,"line":1114},[228,1792,1301],{"class":233},[228,1794,1795],{"class":241}," SensorProvider(uri, options, flags)\n",[228,1797,1798],{"class":110,"line":1125},[228,1799,466],{"emptyLinePlaceholder":465},[228,1801,1802,1804,1806,1809,1811,1814,1817,1819],{"class":110,"line":1138},[228,1803,255],{"class":233},[228,1805,259],{"class":258},[228,1807,1808],{"class":241},"(self, uri",[228,1810,274],{"class":233},[228,1812,1813],{"class":248},"\"\"",[228,1815,1816],{"class":241},", options",[228,1818,274],{"class":233},[228,1820,1821],{"class":241},"QgsDataProvider.ProviderOptions(),\n",[228,1823,1824,1827,1829],{"class":110,"line":1155},[228,1825,1826],{"class":241},"                 flags",[228,1828,274],{"class":233},[228,1830,1788],{"class":241},[228,1832,1833,1835,1837,1839],{"class":110,"line":1166},[228,1834,816],{"class":258},[228,1836,819],{"class":241},[228,1838,822],{"class":258},[228,1840,1841],{"class":241},"(uri)\n",[228,1843,1844,1846,1849,1851],{"class":110,"line":1186},[228,1845,268],{"class":258},[228,1847,1848],{"class":241},"._uri ",[228,1850,274],{"class":233},[228,1852,1853],{"class":241}," uri\n",[228,1855,1856,1858,1861,1863],{"class":110,"line":1206},[228,1857,268],{"class":258},[228,1859,1860],{"class":241},"._client ",[228,1862,274],{"class":233},[228,1864,1865],{"class":241}," SensorSource(uri)\n",[228,1867,1868,1870,1873,1875],{"class":110,"line":1215},[228,1869,268],{"class":258},[228,1871,1872],{"class":241},"._fields ",[228,1874,274],{"class":233},[228,1876,1877],{"class":241}," QgsFields()\n",[228,1879,1880,1882,1885,1887],{"class":110,"line":1226},[228,1881,268],{"class":258},[228,1883,1884],{"class":241},"._fields.append(QgsField(",[228,1886,303],{"class":248},[228,1888,1889],{"class":241},", QVariant.Int))\n",[228,1891,1892,1894,1896,1898],{"class":110,"line":1232},[228,1893,268],{"class":258},[228,1895,1884],{"class":241},[228,1897,335],{"class":248},[228,1899,1900],{"class":241},", QVariant.String))\n",[228,1902,1903,1905,1907,1909],{"class":110,"line":1242},[228,1904,268],{"class":258},[228,1906,1884],{"class":241},[228,1908,345],{"class":248},[228,1910,1911],{"class":241},", QVariant.Double))\n",[228,1913,1914,1916,1919,1921,1924,1927],{"class":110,"line":1256},[228,1915,268],{"class":258},[228,1917,1918],{"class":241},"._crs ",[228,1920,274],{"class":233},[228,1922,1923],{"class":241}," QgsCoordinateReferenceSystem(",[228,1925,1926],{"class":248},"\"EPSG:4326\"",[228,1928,1152],{"class":241},[228,1930,1931],{"class":110,"line":1265},[228,1932,466],{"emptyLinePlaceholder":465},[228,1934,1935,1937,1940],{"class":110,"line":1283},[228,1936,255],{"class":233},[228,1938,1939],{"class":237}," featureSource",[228,1941,1320],{"class":241},[228,1943,1944,1946,1949,1951],{"class":110,"line":1289},[228,1945,1301],{"class":233},[228,1947,1948],{"class":241}," SensorFeatureSource(",[228,1950,1034],{"class":258},[228,1952,1152],{"class":241},[228,1954,1955],{"class":110,"line":1298},[228,1956,466],{"emptyLinePlaceholder":465},[228,1958,1959,1961,1963,1966,1968],{"class":110,"line":1307},[228,1960,255],{"class":233},[228,1962,1659],{"class":237},[228,1964,1965],{"class":241},"(self, request",[228,1967,274],{"class":233},[228,1969,1970],{"class":241},"QgsFeatureRequest()):\n",[228,1972,1973,1975,1978,1980],{"class":110,"line":1312},[228,1974,1301],{"class":233},[228,1976,1977],{"class":241}," QgsFeatureIterator(SensorIterator(SensorFeatureSource(",[228,1979,1034],{"class":258},[228,1981,1982],{"class":241},"), request))\n",[228,1984,1985],{"class":110,"line":1323},[228,1986,466],{"emptyLinePlaceholder":465},[228,1988,1989,1991,1994,1997,1999,2002],{"class":110,"line":1331},[228,1990,255],{"class":233},[228,1992,1993],{"class":237}," dataSourceUri",[228,1995,1996],{"class":241},"(self, expandAuthConfig",[228,1998,274],{"class":233},[228,2000,2001],{"class":258},"False",[228,2003,492],{"class":241},[228,2005,2006,2008,2010],{"class":110,"line":1336},[228,2007,1301],{"class":233},[228,2009,507],{"class":258},[228,2011,2012],{"class":241},"._uri\n",[228,2014,2015],{"class":110,"line":1346},[228,2016,466],{"emptyLinePlaceholder":465},[228,2018,2019,2021,2024],{"class":110,"line":1357},[228,2020,255],{"class":233},[228,2022,2023],{"class":237}," storageType",[228,2025,1320],{"class":241},[228,2027,2028,2030],{"class":110,"line":1369},[228,2029,1301],{"class":233},[228,2031,2032],{"class":248}," \"Python sensor API\"\n",[228,2034,2035],{"class":110,"line":1378},[228,2036,466],{"emptyLinePlaceholder":465},[228,2038,2039,2041,2044],{"class":110,"line":1386},[228,2040,255],{"class":233},[228,2042,2043],{"class":237}," wkbType",[228,2045,1320],{"class":241},[228,2047,2048,2050],{"class":110,"line":1391},[228,2049,1301],{"class":233},[228,2051,2052],{"class":241}," QgsWkbTypes.Point\n",[228,2054,2055],{"class":110,"line":1401},[228,2056,466],{"emptyLinePlaceholder":465},[228,2058,2059,2061,2064],{"class":110,"line":1408},[228,2060,255],{"class":233},[228,2062,2063],{"class":237}," fields",[228,2065,1320],{"class":241},[228,2067,2068,2070,2072],{"class":110,"line":1413},[228,2069,1301],{"class":233},[228,2071,507],{"class":258},[228,2073,2074],{"class":241},"._fields\n",[228,2076,2077],{"class":110,"line":1423},[228,2078,466],{"emptyLinePlaceholder":465},[228,2080,2081,2083,2086],{"class":110,"line":1437},[228,2082,255],{"class":233},[228,2084,2085],{"class":237}," crs",[228,2087,1320],{"class":241},[228,2089,2091,2093,2095],{"class":110,"line":2090},60,[228,2092,1301],{"class":233},[228,2094,507],{"class":258},[228,2096,2097],{"class":241},"._crs\n",[228,2099,2101],{"class":110,"line":2100},61,[228,2102,466],{"emptyLinePlaceholder":465},[228,2104,2106,2108,2111],{"class":110,"line":2105},62,[228,2107,255],{"class":233},[228,2109,2110],{"class":237}," featureCount",[228,2112,1320],{"class":241},[228,2114,2116,2118,2121,2123,2125,2128,2131,2133,2135],{"class":110,"line":2115},63,[228,2117,1301],{"class":233},[228,2119,2120],{"class":258}," sum",[228,2122,797],{"class":241},[228,2124,309],{"class":258},[228,2126,2127],{"class":233}," for",[228,2129,2130],{"class":241}," _ ",[228,2132,504],{"class":233},[228,2134,507],{"class":258},[228,2136,2137],{"class":241},"._client.records())\n",[228,2139,2141],{"class":110,"line":2140},64,[228,2142,466],{"emptyLinePlaceholder":465},[228,2144,2146,2148,2151],{"class":110,"line":2145},65,[228,2147,255],{"class":233},[228,2149,2150],{"class":237}," extent",[228,2152,1320],{"class":241},[228,2154,2156,2158,2160],{"class":110,"line":2155},66,[228,2157,934],{"class":241},[228,2159,274],{"class":233},[228,2161,2162],{"class":241}," QgsRectangle()\n",[228,2164,2166,2168,2170,2172,2174],{"class":110,"line":2165},67,[228,2167,498],{"class":233},[228,2169,501],{"class":241},[228,2171,504],{"class":233},[228,2173,507],{"class":258},[228,2175,2176],{"class":241},"._client.records():\n",[228,2178,2180,2183,2185,2187,2189],{"class":110,"line":2179},68,[228,2181,2182],{"class":241},"            rect.combineExtentWith(r[",[228,2184,315],{"class":248},[228,2186,1174],{"class":241},[228,2188,325],{"class":248},[228,2190,1163],{"class":241},[228,2192,2194,2196],{"class":110,"line":2193},69,[228,2195,1301],{"class":233},[228,2197,2198],{"class":241}," rect\n",[228,2200,2202],{"class":110,"line":2201},70,[228,2203,466],{"emptyLinePlaceholder":465},[228,2205,2207,2209,2212],{"class":110,"line":2206},71,[228,2208,255],{"class":233},[228,2210,2211],{"class":237}," isValid",[228,2213,1320],{"class":241},[228,2215,2217,2219],{"class":110,"line":2216},72,[228,2218,1301],{"class":233},[228,2220,1295],{"class":258},[228,2222,2224],{"class":110,"line":2223},73,[228,2225,466],{"emptyLinePlaceholder":465},[228,2227,2229,2231,2234],{"class":110,"line":2228},74,[228,2230,255],{"class":233},[228,2232,2233],{"class":237}," name",[228,2235,1320],{"class":241},[228,2237,2239,2241,2243,2245],{"class":110,"line":2238},75,[228,2240,1301],{"class":233},[228,2242,507],{"class":258},[228,2244,210],{"class":241},[228,2246,1737],{"class":258},[228,2248,2250],{"class":110,"line":2249},76,[228,2251,466],{"emptyLinePlaceholder":465},[228,2253,2255,2257,2260],{"class":110,"line":2254},77,[228,2256,255],{"class":233},[228,2258,2259],{"class":237}," capabilities",[228,2261,1320],{"class":241},[228,2263,2265,2267],{"class":110,"line":2264},78,[228,2266,1301],{"class":233},[228,2268,2269],{"class":241}," QgsVectorDataProvider.SelectAtId\n",[14,2271,2272,2274,2275,1453,2278,2281,2282,2285,2286,2289],{},[732,2273,734],{}," The feature source copies the provider's client, fields and CRS so iterators created in other threads never touch the provider itself. The provider's class methods give the registry its key, a description and a factory. ",[18,2276,2277],{},"extent",[18,2279,2280],{},"featureCount"," are computed from the source; for a large remote source, return cached or server-reported values instead, because QGIS calls them often. ",[18,2283,2284],{},"capabilities"," advertises only ",[18,2287,2288],{},"SelectAtId",", so QGIS treats the layer as read-only and never offers editing. Fields, geometry type and CRS must be stable for the provider's lifetime.",[189,2291,2293],{"id":2292},"register-the-provider-and-load-a-layer","Register the provider and load a layer",[14,2295,2296],{},"The provider registry needs metadata linking the key to the factory. After that, layers are created like any other.",[219,2298,2300],{"className":221,"code":2299,"language":223,"meta":224,"style":224},"from qgis.core import QgsProviderRegistry, QgsProviderMetadata, QgsVectorLayer, QgsProject\n\nmetadata = QgsProviderMetadata(SensorProvider.providerKey(), SensorProvider.description(),\n                               SensorProvider.createProvider)\nQgsProviderRegistry.instance().registerProvider(metadata)\n\nlayer = QgsVectorLayer(\"https:\u002F\u002Fsensors.example.org\u002Fapi?network=air\", \"air sensors\",\n                       SensorProvider.providerKey())\nprint(layer.isValid(), layer.featureCount(), layer.extent().toString(3))\nQgsProject.instance().addMapLayer(layer)\n",[18,2301,2302,2313,2317,2327,2332,2337,2341,2362,2367,2380],{"__ignoreMap":224},[228,2303,2304,2306,2308,2310],{"class":110,"line":230},[228,2305,764],{"class":233},[228,2307,767],{"class":241},[228,2309,770],{"class":233},[228,2311,2312],{"class":241}," QgsProviderRegistry, QgsProviderMetadata, QgsVectorLayer, QgsProject\n",[228,2314,2315],{"class":110,"line":245},[228,2316,466],{"emptyLinePlaceholder":465},[228,2318,2319,2322,2324],{"class":110,"line":252},[228,2320,2321],{"class":241},"metadata ",[228,2323,274],{"class":233},[228,2325,2326],{"class":241}," QgsProviderMetadata(SensorProvider.providerKey(), SensorProvider.description(),\n",[228,2328,2329],{"class":110,"line":265},[228,2330,2331],{"class":241},"                               SensorProvider.createProvider)\n",[228,2333,2334],{"class":110,"line":284},[228,2335,2336],{"class":241},"QgsProviderRegistry.instance().registerProvider(metadata)\n",[228,2338,2339],{"class":110,"line":297},[228,2340,466],{"emptyLinePlaceholder":465},[228,2342,2343,2346,2348,2351,2354,2356,2359],{"class":110,"line":356},[228,2344,2345],{"class":241},"layer ",[228,2347,274],{"class":233},[228,2349,2350],{"class":241}," QgsVectorLayer(",[228,2352,2353],{"class":248},"\"https:\u002F\u002Fsensors.example.org\u002Fapi?network=air\"",[228,2355,312],{"class":241},[228,2357,2358],{"class":248},"\"air sensors\"",[228,2360,2361],{"class":241},",\n",[228,2363,2364],{"class":110,"line":406},[228,2365,2366],{"class":241},"                       SensorProvider.providerKey())\n",[228,2368,2369,2372,2375,2377],{"class":110,"line":456},[228,2370,2371],{"class":258},"print",[228,2373,2374],{"class":241},"(layer.isValid(), layer.featureCount(), layer.extent().toString(",[228,2376,415],{"class":258},[228,2378,2379],{"class":241},"))\n",[228,2381,2382],{"class":110,"line":462},[228,2383,2384],{"class":241},"QgsProject.instance().addMapLayer(layer)\n",[14,2386,2387,2389,2390,2393],{},[732,2388,734],{}," Registration happens once per session — in a plugin's ",[18,2391,2392],{},"initGui",", before any project using the provider opens. The layer source string is passed to the provider as its URI; design it to carry everything the provider needs, such as an endpoint and a network name, so projects can store and reload it. Once loaded, the layer works with styling, labels, the attribute table, expressions and Processing, because they all go through the iterator. Keep a reference to the metadata object for the session.",[189,2395,2397],{"id":2396},"test-the-provider-like-a-layer","Test the provider like a layer",[14,2399,2400],{},"The best test of a provider is to use it the way QGIS will: iterate with filters, request another CRS, and check counts and extents.",[219,2402,2404],{"className":221,"code":2403,"language":223,"meta":224,"style":224},"from qgis.core import QgsRectangle\n\nassert layer.featureCount() == 3\nwest = list(layer.getFeatures(QgsFeatureRequest().setFilterRect(QgsRectangle(13.2, 52.4, 13.35, 52.6))))\nassert [f[\"name\"] for f in west] == [\"Charlottenburg\"]\nhigh = list(layer.getFeatures('\"pm25\" > 12'))\nassert [f.id() for f in high] == [2]\nreq = QgsFeatureRequest().setDestinationCrs(QgsCoordinateReferenceSystem(\"EPSG:25833\"),\n                                            QgsProject.instance().transformContext())\nx = next(layer.getFeatures(req)).geometry().asPoint().x()\nassert 380000 \u003C x \u003C 400000\nprint(\"provider behaves\")\n",[18,2405,2406,2417,2421,2434,2468,2499,2516,2540,2556,2561,2574,2593],{"__ignoreMap":224},[228,2407,2408,2410,2412,2414],{"class":110,"line":230},[228,2409,764],{"class":233},[228,2411,767],{"class":241},[228,2413,770],{"class":233},[228,2415,2416],{"class":241}," QgsRectangle\n",[228,2418,2419],{"class":110,"line":245},[228,2420,466],{"emptyLinePlaceholder":465},[228,2422,2423,2426,2429,2431],{"class":110,"line":252},[228,2424,2425],{"class":233},"assert",[228,2427,2428],{"class":241}," layer.featureCount() ",[228,2430,1017],{"class":233},[228,2432,2433],{"class":258}," 3\n",[228,2435,2436,2439,2441,2444,2447,2450,2452,2455,2457,2460,2462,2465],{"class":110,"line":265},[228,2437,2438],{"class":241},"west ",[228,2440,274],{"class":233},[228,2442,2443],{"class":258}," list",[228,2445,2446],{"class":241},"(layer.getFeatures(QgsFeatureRequest().setFilterRect(QgsRectangle(",[228,2448,2449],{"class":258},"13.2",[228,2451,312],{"class":241},[228,2453,2454],{"class":258},"52.4",[228,2456,312],{"class":241},[228,2458,2459],{"class":258},"13.35",[228,2461,312],{"class":241},[228,2463,2464],{"class":258},"52.6",[228,2466,2467],{"class":241},"))))\n",[228,2469,2470,2472,2475,2477,2479,2481,2484,2486,2489,2491,2494,2496],{"class":110,"line":284},[228,2471,2425],{"class":233},[228,2473,2474],{"class":241}," [f[",[228,2476,335],{"class":248},[228,2478,539],{"class":241},[228,2480,1460],{"class":233},[228,2482,2483],{"class":241}," f ",[228,2485,504],{"class":233},[228,2487,2488],{"class":241}," west] ",[228,2490,1017],{"class":233},[228,2492,2493],{"class":241}," [",[228,2495,442],{"class":248},[228,2497,2498],{"class":241},"]\n",[228,2500,2501,2504,2506,2508,2511,2514],{"class":110,"line":297},[228,2502,2503],{"class":241},"high ",[228,2505,274],{"class":233},[228,2507,2443],{"class":258},[228,2509,2510],{"class":241},"(layer.getFeatures(",[228,2512,2513],{"class":248},"'\"pm25\" > 12'",[228,2515,2379],{"class":241},[228,2517,2518,2520,2523,2525,2527,2529,2532,2534,2536,2538],{"class":110,"line":356},[228,2519,2425],{"class":233},[228,2521,2522],{"class":241}," [f.id() ",[228,2524,1460],{"class":233},[228,2526,2483],{"class":241},[228,2528,504],{"class":233},[228,2530,2531],{"class":241}," high] ",[228,2533,1017],{"class":233},[228,2535,2493],{"class":241},[228,2537,365],{"class":258},[228,2539,2498],{"class":241},[228,2541,2542,2545,2547,2550,2553],{"class":110,"line":406},[228,2543,2544],{"class":241},"req ",[228,2546,274],{"class":233},[228,2548,2549],{"class":241}," QgsFeatureRequest().setDestinationCrs(QgsCoordinateReferenceSystem(",[228,2551,2552],{"class":248},"\"EPSG:25833\"",[228,2554,2555],{"class":241},"),\n",[228,2557,2558],{"class":110,"line":456},[228,2559,2560],{"class":241},"                                            QgsProject.instance().transformContext())\n",[228,2562,2563,2566,2568,2571],{"class":110,"line":462},[228,2564,2565],{"class":241},"x ",[228,2567,274],{"class":233},[228,2569,2570],{"class":258}," next",[228,2572,2573],{"class":241},"(layer.getFeatures(req)).geometry().asPoint().x()\n",[228,2575,2576,2578,2581,2584,2587,2590],{"class":110,"line":469},[228,2577,2425],{"class":233},[228,2579,2580],{"class":258}," 380000",[228,2582,2583],{"class":233}," \u003C",[228,2585,2586],{"class":241}," x ",[228,2588,2589],{"class":233},"\u003C",[228,2591,2592],{"class":258}," 400000\n",[228,2594,2595,2597,2599,2602],{"class":110,"line":495},[228,2596,2371],{"class":258},[228,2598,797],{"class":241},[228,2600,2601],{"class":248},"\"provider behaves\"",[228,2603,1152],{"class":241},[14,2605,2606,2608,2609,2613],{},[732,2607,734],{}," Each assertion exercises one part of the contract: the count, a spatial filter pushed to the source, an expression filter evaluated in the iterator, and a reprojection requested by the caller. These are exactly the requests the canvas, the identify tool, the attribute table and Processing will make. Adding them to a pytest suite, as in ",[34,2610,2612],{"href":2611},"\u002Fqgis-plugin-development\u002Ftesting-and-ci-for-plugins\u002Funit-test-qgis-plugin-with-pytest\u002F","unit testing a QGIS plugin",", protects the provider as it grows.",[189,2615,2617],{"id":2616},"when-a-memory-layer-is-simpler","When a memory layer is simpler",[14,2619,2620,2621,2625],{},"A Python provider is the right tool when data is large and viewed piecemeal, when it changes and should be read live, or when the layer must reopen in projects pointing at its source. When the data is small and a snapshot is acceptable, a memory layer filled from the same client is far less code — see ",[34,2622,2624],{"href":2623},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fcreate-memory-layer-pyqgis\u002F","creating a memory layer",". Python providers also run Python for every feature fetched, including during rendering, so they are slower than native providers; cache aggressively and push filters to the source.",[189,2627,2629],{"id":2628},"qgis-version-compatibility","QGIS version compatibility",[14,2631,2632,2633,312,2636,312,2639,2642,2643,2646,2647,2650,2651,210],{},"Python vector data providers work on QGIS 3.34 LTR, 3.40 LTR and QGIS 4 using the pattern from QGIS's own provider test suite. On QGIS 4, enums are scoped — ",[18,2634,2635],{},"QgsFeatureRequest.FilterType.Fid",[18,2637,2638],{},"QgsVectorDataProvider.Capability.SelectAtId",[18,2640,2641],{},"Qgis.WkbType.Point"," — and ",[18,2644,2645],{},"QgsField"," takes ",[18,2648,2649],{},"QMetaType.Type"," values. Check the exact enum names on your target version with ",[18,2652,2653],{},"help()",[189,2655,2657],{"id":2656},"troubleshooting","Troubleshooting",[194,2659,2660,2670,2680,2686],{},[197,2661,2662,2665,2666,2669],{},[732,2663,2664],{},"The layer is invalid."," The provider key was not registered before the layer was created, or ",[18,2667,2668],{},"isValid"," returned False.",[197,2671,2672,2675,2676,2679],{},[732,2673,2674],{},"Features appear in the wrong place."," The iterator ignored ",[18,2677,2678],{},"destinationCrs"," and returned source coordinates.",[197,2681,2682,2685],{},[732,2683,2684],{},"Selections or identify return the wrong feature."," Feature ids are not stable or the fid filters are ignored.",[197,2687,2688,2691],{},[732,2689,2690],{},"QGIS crashes during rendering."," The iterator touches the provider object from a worker thread; use the feature source snapshot.",[189,2693,2695],{"id":2694},"conclusion","Conclusion",[14,2697,2698],{},"Wrap the data access in a small client, write an iterator that honours fid, rectangle, expression and CRS requests, hand iterators a thread-safe feature source, describe fields, geometry type, CRS, extent and capabilities in the provider, register the key from a plugin, and test with the same requests QGIS will make — or use a memory layer when a snapshot is enough.",[189,2700,2702],{"id":2701},"frequently-asked-questions","Frequently Asked Questions",[14,2704,2705,2708,2709,312,2712,2715],{},[732,2706,2707],{},"Can a Python provider support editing?","\nYes, by implementing ",[18,2710,2711],{},"addFeatures",[18,2713,2714],{},"changeAttributeValues"," and friends and advertising the capabilities. Start read-only and add editing only if needed.",[14,2717,2718,2721],{},[732,2719,2720],{},"Will the layer work in QGIS Server?","\nOnly where the plugin registering the provider is loaded on the server.",[14,2723,2724,2727,2728,2731],{},[732,2725,2726],{},"How do I add a source-select dialog?","\nImplement a ",[18,2729,2730],{},"QgsSourceSelectProvider"," so users can add layers from the Data Source Manager.",[14,2733,2734,2737],{},[732,2735,2736],{},"Is a Python provider faster than a memory layer?","\nNo, usually slower per feature. Its advantage is fetching only what is needed, live.",[189,2739,2741],{"id":2740},"related","Related",[194,2743,2744,2749,2755,2760,2766],{},[197,2745,2746,2748],{},[34,2747,37],{"href":36}," — the guide this recipe belongs to",[197,2750,2751],{},[34,2752,2754],{"href":2753},"\u002Fspatial-data-processing-automation\u002Flayer-data-sources-and-formats\u002Fdecode-and-build-data-source-uris-pyqgis\u002F","Decode and Build Data Source URIs in PyQGIS",[197,2756,2757],{},[34,2758,2759],{"href":2623},"Create a Memory Layer in PyQGIS",[197,2761,2762],{},[34,2763,2765],{"href":2764},"\u002Fspatial-data-processing-automation\u002Fweb-services-and-remote-data\u002Fmake-http-requests-with-qgsnetworkaccessmanager-pyqgis\u002F","Make HTTP Requests with QgsNetworkAccessManager",[197,2767,2768],{},[34,2769,2771],{"href":2770},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Fspeed-up-feature-iteration-with-qgsfeaturerequest-pyqgis\u002F","Speed Up Feature Iteration with QgsFeatureRequest",[2773,2774,2775],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}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 pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}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}",{"title":224,"searchDepth":245,"depth":245,"links":2777},[2778,2779,2780,2781,2782,2783,2784,2785,2786,2787,2788,2789],{"id":191,"depth":245,"text":192},{"id":213,"depth":245,"text":214},{"id":746,"depth":245,"text":747},{"id":1468,"depth":245,"text":1469},{"id":2292,"depth":245,"text":2293},{"id":2396,"depth":245,"text":2397},{"id":2616,"depth":245,"text":2617},{"id":2628,"depth":245,"text":2629},{"id":2656,"depth":245,"text":2657},{"id":2694,"depth":245,"text":2695},{"id":2701,"depth":245,"text":2702},{"id":2740,"depth":245,"text":2741},"Make any data source appear as a native QGIS layer by writing a read-only vector data provider in Python — the provider, feature source and iterator classes, honouring request filters, extent and CRS, registering the provider key, and knowing when a memory layer is simpler.","md",{"slug":2793,"type":2794,"breadcrumb":2795,"datePublished":2796,"dateModified":2796},"write-python-vector-data-provider-pyqgis","article","Write a Python Vector Data Provider","2026-10-02","\u002Fqgis-plugin-development\u002Fextending-qgis-with-custom-classes\u002Fwrite-python-vector-data-provider-pyqgis",{"title":5,"description":2790},"qgis-plugin-development\u002Fextending-qgis-with-custom-classes\u002Fwrite-python-vector-data-provider-pyqgis\u002Findex","1QzMpc07x_rMhKoU1IsfhDUdSTjDUwWVxZgx2k251WM",1790966257323]