Skip to content

Commit abcad01

Browse files
committedJun 1, 2018
Python provider tests passing
Except for QgsLayerDefinition.exportLayerDefinitionLayers and QgsLayerDefinition.loadLayerDefinitionLayers
1 parent 29575b5 commit abcad01

File tree

2 files changed

+157
-71
lines changed

2 files changed

+157
-71
lines changed
 

‎tests/src/python/provider_python.py

Lines changed: 142 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@
2323
QgsFeatureRequest,
2424
QgsFeature,
2525
QgsGeometry,
26+
QgsProject,
2627
QgsWkbTypes,
28+
QgsExpression,
29+
QgsExpressionContext,
30+
QgsExpressionContextUtils,
2731
NULL,
32+
QgsCoordinateTransform,
2833
QgsMemoryProviderUtils,
2934
QgsCoordinateReferenceSystem,
3035
QgsRectangle,
@@ -36,39 +41,83 @@
3641
QgsApplication,
3742
QgsProviderRegistry,
3843
QgsProviderMetadata,
44+
QgsGeometryEngine,
3945
)
4046

4147
from qgis.PyQt.QtCore import QVariant
4248

4349

44-
class PyFeatureIterator(QgsFeatureIterator):
50+
class PyFeatureIterator(QgsAbstractFeatureIterator):
4551

4652
def __init__(self, source, request):
47-
super(PyFeatureIterator, self).__init__()
48-
self._request = request
49-
self._index = 0
53+
super(PyFeatureIterator, self).__init__(request)
54+
self._request = request if request is not None else QgsFeatureRequest()
5055
self._source = source
56+
self._index = 0
57+
self._transform = QgsCoordinateTransform()
58+
if self._request.destinationCrs().isValid() and self._request.destinationCrs() != self._source._provider.crs():
59+
self._transform = QgsCoordinateTransform(self._source._provider.crs(), self._request.destinationCrs(), self._request.transformContext())
60+
self._filter_rect = self.filterRectToSourceCrs(self._transform)
61+
if not self._filter_rect.isNull():
62+
self._select_rect_geom = QgsGeometry.fromRect(self._filter_rect)
63+
self._select_rect_engine = QgsGeometry.createGeometryEngine(self._select_rect_geom.constGet())
64+
self._select_rect_engine.prepareGeometry()
65+
else:
66+
self._select_rect_engine = None
67+
self._select_rect_geom = None
5168

52-
def nextFeature(self, f):
69+
def fetchFeature(self, f):
5370
"""fetch next feature, return true on success"""
5471
#virtual bool nextFeature( QgsFeature &f );
72+
if self._index < 0:
73+
f.setValid(False)
74+
return False
5575
try:
56-
_f = self._source._features[self._index]
57-
self._index += 1
58-
f.setAttributes(_f.attributes())
59-
f.setGeometry(_f.geometry())
60-
f.setValid(_f.isValid())
61-
return True
62-
except Exception as e:
76+
found = False
77+
while not found:
78+
_f = self._source._features[list(self._source._features.keys())[self._index]]
79+
self._index += 1
80+
self._source._expression_context.setFeature(_f)
81+
if self._request.filterType() == QgsFeatureRequest.FilterExpression:
82+
if not self._request.filterExpression().evaluate(self._source._expression_context):
83+
continue
84+
if self._source._subset_expression:
85+
if not self._source._subset_expression.evaluate(self._source._expression_context):
86+
continue
87+
elif self._request.filterType() == QgsFeatureRequest.FilterFids:
88+
if not _f.id() in self._request.filterFids():
89+
continue
90+
elif self._request.filterType() == QgsFeatureRequest.FilterFid:
91+
if _f.id() != self._request.filterFid():
92+
continue
93+
if not self._filter_rect.isNull():
94+
if not _f.hasGeometry():
95+
continue
96+
if self._request.flags() & QgsFeatureRequest.ExactIntersect:
97+
# do exact check in case we're doing intersection
98+
if not self._select_rect_engine.intersects(_f.geometry().constGet()):
99+
continue
100+
else:
101+
if not _f.geometry().boundingBox().intersects(self._filter_rect):
102+
continue
103+
f.setGeometry(_f.geometry())
104+
self.geometryToDestinationCrs(f, self._transform)
105+
f.setFields(_f.fields())
106+
f.setAttributes(_f.attributes())
107+
f.setValid(_f.isValid())
108+
f.setId(_f.id())
109+
return True
110+
except IndexError as e:
63111
f.setValid(False)
64112
return False
65113

66114
def __iter__(self):
67-
'Returns itself as an iterator object'
115+
"""Returns self as an iterator object"""
116+
self._index = 0
68117
return self
69118

70119
def __next__(self):
71-
'Returns the next value till current is lower than high'
120+
"""Returns the next value till current is lower than high"""
72121
f = QgsFeature()
73122
if not self.nextFeature(f):
74123
raise StopIteration
@@ -78,12 +127,15 @@ def __next__(self):
78127
def rewind(self):
79128
"""reset the iterator to the starting position"""
80129
#virtual bool rewind() = 0;
130+
if self._index < 0:
131+
return False
81132
self._index = 0
133+
return True
82134

83135
def close(self):
84136
"""end of iterating: free the resources / lock"""
85137
#virtual bool close() = 0;
86-
self._index = 0
138+
self._index = -1
87139
return True
88140

89141

@@ -94,10 +146,20 @@ def __init__(self, provider):
94146
self._provider = provider
95147
self._features = provider._features
96148

149+
self._expression_context = QgsExpressionContext()
150+
self._expression_context.appendScope(QgsExpressionContextUtils.globalScope())
151+
self._expression_context.appendScope(QgsExpressionContextUtils.projectScope(QgsProject.instance()))
152+
self._expression_context.setFields(self._provider.fields())
153+
if self._provider.subsetString():
154+
self._subset_expression = QgsExpression(self._provider.subsetString())
155+
self._subset_expression.prepare(self._expression_context)
156+
else:
157+
self._subset_expression = None
158+
97159
def getFeatures(self, request):
98160
# QgsFeatureIterator getFeatures( const QgsFeatureRequest &request ) override;
99161
# NOTE: this is the same as PyProvider.getFeatures
100-
return PyFeatureIterator(self, request)
162+
return QgsFeatureIterator(PyFeatureIterator(self, request))
101163

102164

103165
class PyProvider(QgsVectorDataProvider):
@@ -127,56 +189,81 @@ def __init__(self, uri=''):
127189
super(PyProvider, self).__init__(uri)
128190
# Use the memory layer to parse the uri
129191
mlayer = QgsVectorLayer(uri, 'ml', 'memory')
192+
self.setNativeTypes(mlayer.dataProvider().nativeTypes())
130193
self._uri = uri
131194
self._fields = mlayer.fields()
132195
self._wkbType = mlayer.wkbType()
133196
self._features = {}
134197
self._extent = QgsRectangle()
135198
self._extent.setMinimal()
136199
self._subset_string = ''
200+
self._crs = mlayer.crs()
137201

138202
def featureSource(self):
139203
return PyFeatureSource(self)
140204

141205
def dataSourceUri(self, expandAuthConfig=True):
142-
#QString dataSourceUri( bool expandAuthConfig = true ) const override;
143206
return self._uri
144207

145208
def storageType(self):
146-
#QString storageType() const override;
147209
return "Python test memory storage"
148210

149-
def getFeatures(self, request=None):
150-
#QgsFeatureIterator getFeatures( const QgsFeatureRequest &request ) const override;
151-
return PyFeatureIterator(PyFeatureSource(self), request)
211+
def getFeatures(self, request=QgsFeatureRequest()):
212+
return QgsFeatureIterator(PyFeatureIterator(PyFeatureSource(self), request))
213+
214+
def uniqueValues(self, fieldIndex, limit=1):
215+
results = set()
216+
if fieldIndex >= 0 and fieldIndex < self.fields().count():
217+
req = QgsFeatureRequest()
218+
req.setFlags(QgsFeatureRequest.NoGeometry)
219+
req.setSubsetOfAttributes([fieldIndex])
220+
for f in self.getFeatures(req):
221+
results.add(f.attributes()[fieldIndex])
222+
return results
152223

153224
def wkbType(self):
154-
#QgsWkbTypes::Type wkbType() const override;
155225
return self._wkbType
156226

157227
def featureCount(self):
158-
# TODO: get from source
159-
# long featureCount() const override;
160-
return len(self._features)
228+
if not self.subsetString():
229+
return len(self._features)
230+
else:
231+
req = QgsFeatureRequest()
232+
req.setFlags(QgsFeatureRequest.NoGeometry)
233+
req.setSubsetOfAttributes([])
234+
return len([f for f in self.getFeatures(req)])
161235

162236
def fields(self):
163-
#QgsFields fields() const override;
164237
return self._fields
165238

166239
def addFeatures(self, flist, flags=None):
167-
# bool addFeatures( QgsFeatureList &flist, QgsFeatureSink::Flags flags = nullptr ) override;
168240
added = False
241+
f_added = []
242+
for f in flist:
243+
if f.hasGeometry() and (f.geometry().wkbType() != self.wkbType()):
244+
return added, f_added
245+
169246
for f in flist:
170-
f.setId(self.next_feature_id)
171-
self._features[self.next_feature_id] = f
247+
_f = QgsFeature(self.fields())
248+
_f.setGeometry(f.geometry())
249+
attrs = [None for i in range(_f.fields().count())]
250+
for i in range(min(len(attrs), len(f.attributes()))):
251+
attrs[i] = f.attributes()[i]
252+
_f.setAttributes(attrs)
253+
_f.setId(self.next_feature_id)
254+
self._features[self.next_feature_id] = _f
172255
self.next_feature_id += 1
173256
added = True
174-
if added:
257+
f_added.append(_f)
258+
259+
if len(f_added):
175260
self.updateExtents()
261+
176262
return added, flist
177263

178264
def deleteFeatures(self, ids):
179-
#bool deleteFeatures( const QgsFeatureIds &id ) override;
265+
if not ids:
266+
return True
180267
removed = False
181268
for id in ids:
182269
if id in self._features:
@@ -187,21 +274,20 @@ def deleteFeatures(self, ids):
187274
return removed
188275

189276
def addAttributes(self, attrs):
190-
#bool addAttributes( const QList<QgsField> &attributes ) override;
191277
try:
192-
self._fields.append(attrs)
193278
for new_f in attrs:
279+
if new_f.type() not in (QVariant.Int, QVariant.Double, QVariant.String, QVariant.Date, QVariant.Time, QVariant.DateTime, QVariant.LongLong, QVariant.StringList, QVariant.List):
280+
continue
281+
self._fields.append(new_f)
194282
for f in self._features.values():
195283
old_attrs = f.attributes()
196-
old_attrs.app
197284
old_attrs.append(None)
198285
f.setAttributes(old_attrs)
199286
return True
200-
except:
287+
except Exception:
201288
return False
202289

203-
def renameAttributes(self, renameAttributes):
204-
#bool renameAttributes( const QgsFieldNameMap &renamedAttributes ) override;
290+
def renameAttributes(self, renamedAttributes):
205291
result = True
206292
for key, new_name in renamedAttributes:
207293
fieldIndex = key
@@ -216,7 +302,6 @@ def renameAttributes(self, renameAttributes):
216302
return True
217303

218304
def deleteAttributes(self, attributes):
219-
#bool deleteAttributes( const QgsAttributeIds &attributes ) override;
220305
attrIdx = sorted(attributes.toList(), reverse=True)
221306

222307
# delete attributes one-by-one with decreasing index
@@ -229,52 +314,55 @@ def deleteAttributes(self, attributes):
229314
return True
230315

231316
def changeAttributeValues(self, attr_map):
232-
#bool changeAttributeValues( const QgsChangedAttributesMap &attr_map ) override;
233317
for feature_id, attrs in attr_map.items():
234318
try:
235319
f = self._features[feature_id]
236320
except KeyError:
237321
continue
238-
for k, v in attrs:
322+
for k, v in attrs.items():
239323
f.setAttribute(k, v)
240324
return True
241325

242326
def changeGeometryValues(self, geometry_map):
243-
#bool changeGeometryValues( const QgsGeometryMap &geometry_map ) override;
244-
#TODO
327+
for feature_id, geometry in geometry_map.items():
328+
try:
329+
f = self._features[feature_id]
330+
f.setGeometry(geometry)
331+
except KeyError:
332+
continue
333+
self.updateExtents()
245334
return True
246335

336+
def allFeatureIds(self):
337+
return list(self._features.keys())
338+
247339
def subsetString(self):
248-
#QString subsetString() const override;
249340
return self._subset_string
250341

251342
def setSubsetString(self, subsetString):
252-
#bool setSubsetString( const QString &theSQL, bool updateFeatureCount = true ) override;
253343
self._subset_string = subsetString
344+
self.updateExtents()
345+
self.clearMinMaxCache()
346+
self.dataChanged.emit()
254347

255348
def supportsSubsetString(self):
256-
#bool supportsSubsetString() const override { return true; }
257349
return True
258350

259351
def createSpatialIndex(self):
260-
#bool createSpatialIndex() override;
261352
return True
262353

263354
def capabilities(self):
264-
#QgsVectorDataProvider::Capabilities capabilities() const override;
265355
return QgsVectorDataProvider.AddFeatures | QgsVectorDataProvider.DeleteFeatures | QgsVectorDataProvider.ChangeGeometries | QgsVectorDataProvider.ChangeAttributeValues | QgsVectorDataProvider.AddAttributes | QgsVectorDataProvider.DeleteAttributes | QgsVectorDataProvider.RenameAttributes | QgsVectorDataProvider.SelectAtId | QgsVectorDataProvider. CircularGeometries
266356

267357
#/* Implementation of functions from QgsDataProvider */
268358

269359
def name(self):
270-
#QString name() const override;
271360
return self.providerKey()
272361

273362
def extent(self):
274-
#QgsRectangle extent() const override;
275-
if self._extent.isEmpty() and not self._features:
363+
if self._extent.isEmpty() and self._features:
276364
self._extent.setMinimal()
277-
if self._subset_string.isEmpty():
365+
if not self._subset_string:
278366
# fast way - iterate through all features
279367
for feat in self._features.values():
280368
if feat.hasGeometry():
@@ -284,18 +372,15 @@ def extent(self):
284372
if f.hasGeometry():
285373
self._extent.combineExtentWith(f.geometry().boundingBox())
286374

287-
elif self._features:
375+
elif not self._features:
288376
self._extent.setMinimal()
289-
return self._extent
377+
return QgsRectangle(self._extent)
290378

291379
def updateExtents(self):
292-
#void updateExtents() override;
293380
self._extent.setMinimal()
294381

295382
def isValid(self):
296-
#bool isValid() const override;
297383
return True
298384

299385
def crs(self):
300-
#QgsCoordinateReferenceSystem crs() const override;
301-
return QgsCoordinateReferenceSystem(4326)
386+
return self._crs

‎tests/src/python/test_provider_python.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
__revision__ = '$Format:%H$'
1414

1515
# -*- coding: utf-8 -*-
16-
"""QGIS Unit tests for the memory layer provider.
16+
"""QGIS Unit tests for the py layerprovider.
1717
1818
.. note:: This program is free software; you can redistribute it and/or modify
1919
it under the terms of the GNU General Public License as published by
@@ -279,29 +279,30 @@ def testGetFields(self):
279279

280280
def testFromUri(self):
281281
"""Test we can construct the mem provider from a uri"""
282-
myMemoryLayer = QgsVectorLayer(
282+
myPyLayer = QgsVectorLayer(
283283
('Point?crs=epsg:4326&field=name:string(20)&'
284284
'field=age:integer&field=size:double&index=yes'),
285285
'test',
286286
'pythonprovider')
287287

288-
assert myMemoryLayer is not None, 'Provider not initialized'
289-
myProvider = myMemoryLayer.dataProvider()
288+
assert myPyLayer is not None, 'Provider not initialized'
289+
myProvider = myPyLayer.dataProvider()
290290
assert myProvider is not None
291291

292292
def testLengthPrecisionFromUri(self):
293293
"""Test we can assign length and precision from a uri"""
294-
myMemoryLayer = QgsVectorLayer(
294+
myPyLayer = QgsVectorLayer(
295295
('Point?crs=epsg:4326&field=size:double(12,9)&index=yes'),
296296
'test',
297297
'pythonprovider')
298298

299-
self.assertEqual(myMemoryLayer.fields().field('size').length(), 12)
300-
self.assertEqual(myMemoryLayer.fields().field('size').precision(), 9)
299+
self.assertEqual(myPyLayer.fields().field('size').length(), 12)
300+
self.assertEqual(myPyLayer.fields().field('size').precision(), 9)
301301

302+
@unittest.expectedFailure("Handled layers are hardcoded")
302303
def testSaveFields(self):
303-
# Create a new memory layer with no fields
304-
myMemoryLayer = QgsVectorLayer(
304+
# Create a new py layerwith no fields
305+
myPyLayer = QgsVectorLayer(
305306
('Point?crs=epsg:4326&index=yes'),
306307
'test',
307308
'pythonprovider')
@@ -314,14 +315,14 @@ def testSaveFields(self):
314315
QgsField('TestDate', QVariant.Date, 'date'),
315316
QgsField('TestTime', QVariant.Time, 'time'),
316317
QgsField('TestDateTime', QVariant.DateTime, 'datetime')]
317-
assert myMemoryLayer.startEditing()
318+
assert myPyLayer.startEditing()
318319
for f in myFields:
319-
assert myMemoryLayer.addAttribute(f)
320-
assert myMemoryLayer.commitChanges()
321-
myMemoryLayer.updateFields()
320+
assert myPyLayer.addAttribute(f)
321+
assert myPyLayer.commitChanges()
322+
myPyLayer.updateFields()
322323

323324
# Export the layer to a layer-definition-XML
324-
qlr = QgsLayerDefinition.exportLayerDefinitionLayers([myMemoryLayer], QgsReadWriteContext())
325+
qlr = QgsLayerDefinition.exportLayerDefinitionLayers([myPyLayer], QgsReadWriteContext())
325326
assert qlr is not None
326327

327328
# Import the layer from the layer-definition-XML

0 commit comments

Comments
 (0)
Please sign in to comment.