Skip to content

Commit e341b3a

Browse files
committed
Tests for Python vector data provider (still failing but not crashing!)
1 parent 839a889 commit e341b3a

File tree

1 file changed

+324
-0
lines changed

1 file changed

+324
-0
lines changed
Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
# -*- coding: utf-8 -*-
2+
"""QGIS Unit tests for the python layer provider.
3+
4+
.. note:: This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation; either version 2 of the License, or
7+
(at your option) any later version.
8+
"""
9+
__author__ = 'Alessandro Pasotti'
10+
__date__ = '2018-03-18'
11+
__copyright__ = 'Copyright 2018, The QGIS Project'
12+
# This will get replaced with a git SHA1 when you do a git archive
13+
__revision__ = '$Format:%H$'
14+
15+
16+
from qgis.core import (
17+
QgsField,
18+
QgsFields,
19+
QgsLayerDefinition,
20+
QgsPointXY,
21+
QgsReadWriteContext,
22+
QgsVectorLayer,
23+
QgsFeatureRequest,
24+
QgsFeature,
25+
QgsGeometry,
26+
QgsWkbTypes,
27+
NULL,
28+
QgsMemoryProviderUtils,
29+
QgsCoordinateReferenceSystem,
30+
QgsRectangle,
31+
QgsTestUtils,
32+
QgsVectorDataProvider,
33+
QgsAbstractFeatureSource,
34+
QgsAbstractFeatureIterator,
35+
QgsFeatureIterator,
36+
QgsApplication,
37+
QgsProviderRegistry,
38+
QgsProviderMetadata,
39+
)
40+
41+
from qgis.testing import (
42+
start_app,
43+
unittest
44+
)
45+
46+
from utilities import (
47+
unitTestDataPath,
48+
compareWkt
49+
)
50+
51+
from providertestbase import ProviderTestCase
52+
from qgis.PyQt.QtCore import QVariant
53+
54+
start_app()
55+
TEST_DATA_DIR = unitTestDataPath()
56+
57+
58+
class PyFeatureIterator(QgsFeatureIterator):
59+
60+
def __init__(self, source, request):
61+
super(PyFeatureIterator, self).__init__()
62+
self._request = request
63+
self._index = 0
64+
self._source = source
65+
66+
def nextFeature(self, f):
67+
"""fetch next feature, return true on success"""
68+
#virtual bool nextFeature( QgsFeature &f );
69+
try:
70+
_f = self._source._features[self._index]
71+
self._index += 1
72+
f.setAttributes(_f.attributes())
73+
f.setGeometry(_f.geometry())
74+
f.setValid(_f.isValid())
75+
return True
76+
except Exception as e:
77+
f.setValid(False)
78+
return False
79+
80+
def __iter__(self):
81+
'Returns itself as an iterator object'
82+
return self
83+
84+
def __next__(self):
85+
'Returns the next value till current is lower than high'
86+
f = QgsFeature()
87+
if not self.nextFeature(f):
88+
raise StopIteration
89+
else:
90+
return f
91+
92+
def rewind(self):
93+
"""reset the iterator to the starting position"""
94+
#virtual bool rewind() = 0;
95+
self._index = 0
96+
97+
def close(self):
98+
"""end of iterating: free the resources / lock"""
99+
#virtual bool close() = 0;
100+
self._index = 0
101+
return True
102+
103+
104+
class PyFeatureSource(QgsAbstractFeatureSource):
105+
106+
def __init__(self, provider):
107+
super(PyFeatureSource, self).__init__()
108+
self._provider = provider
109+
self._features = provider._features
110+
111+
def getFeatures(self, request):
112+
# QgsFeatureIterator getFeatures( const QgsFeatureRequest &request ) override;
113+
# NOTE: this is the same as PyProvider.getFeatures
114+
return PyFeatureIterator(self, request)
115+
116+
117+
class PyProvider(QgsVectorDataProvider):
118+
119+
@classmethod
120+
def providerKey(cls):
121+
"""Returns the memory provider key"""
122+
#static QString providerKey();
123+
return 'py'
124+
125+
@classmethod
126+
def description(cls):
127+
"""Returns the memory provider description"""
128+
#static QString providerDescription();
129+
return 'py provider'
130+
131+
@classmethod
132+
def createProvider(cls, uri):
133+
#static QgsMemoryProvider *createProvider( const QString &uri );
134+
return PyProvider(uri)
135+
136+
# Implementation of functions from QgsVectorDataProvider
137+
138+
def __init__(self, uri=''):
139+
super(PyProvider, self).__init__(uri)
140+
self._fields = QgsFields()
141+
self._fields.append(QgsField('id', QVariant.Int, 'id', 10, 2, 'an id'))
142+
self._features = []
143+
144+
def featureSource(self):
145+
return PyFeatureSource(self)
146+
147+
def dataSourceUri(self, expandAuthConfig=True):
148+
#QString dataSourceUri( bool expandAuthConfig = true ) const override;
149+
# TODO: add some params
150+
return 'py'
151+
152+
def storageType(self):
153+
#QString storageType() const override;
154+
return "Py storage"
155+
156+
def getFeatures(self, request):
157+
#QgsFeatureIterator getFeatures( const QgsFeatureRequest &request ) const override;
158+
return PyFeatureIterator(PyFeatureSource(self), request)
159+
160+
def wkbType(self):
161+
#QgsWkbTypes::Type wkbType() const override;
162+
return QgsWkbTypes.Point
163+
164+
def featureCount(self):
165+
# TODO: get from source
166+
# long featureCount() const override;
167+
return len(self._features)
168+
169+
def fields(self):
170+
#QgsFields fields() const override;
171+
return self._fields
172+
173+
def addFeatures(self, flist, flags=None):
174+
# bool addFeatures( QgsFeatureList &flist, QgsFeatureSink::Flags flags = nullptr ) override;
175+
self._features += flist
176+
177+
def deleteFeatures(self, ids):
178+
#bool deleteFeatures( const QgsFeatureIds &id ) override;
179+
removed = False
180+
for f in self._features:
181+
if f.id() in ids:
182+
self._features.remove(f)
183+
removed = True
184+
return removed
185+
186+
def addAttributes(self, attrs):
187+
#bool addAttributes( const QList<QgsField> &attributes ) override;
188+
# TODO
189+
return True
190+
191+
def renameAttributes(self, renameAttributes):
192+
#bool renameAttributes( const QgsFieldNameMap &renamedAttributes ) override;
193+
# TODO:
194+
return True
195+
196+
def deleteAttributes(self, attributes):
197+
#bool deleteAttributes( const QgsAttributeIds &attributes ) override;
198+
# TODO
199+
return True
200+
201+
def changeAttributeValues(self, attr_map):
202+
#bool changeAttributeValues( const QgsChangedAttributesMap &attr_map ) override;
203+
# TODO
204+
return True
205+
206+
def changeGeometryValues(self, geometry_map):
207+
#bool changeGeometryValues( const QgsGeometryMap &geometry_map ) override;
208+
#TODO
209+
return True
210+
211+
def subsetString(self):
212+
#QString subsetString() const override;
213+
return self._subset_string
214+
215+
def setSubsetString(self, subsetString):
216+
#bool setSubsetString( const QString &theSQL, bool updateFeatureCount = true ) override;
217+
self._subset_string = subsetString
218+
219+
def supportsSubsetString(self):
220+
#bool supportsSubsetString() const override { return true; }
221+
return True
222+
223+
def createSpatialIndex(self):
224+
#bool createSpatialIndex() override;
225+
return True
226+
227+
def capabilities(self):
228+
#QgsVectorDataProvider::Capabilities capabilities() const override;
229+
return QgsVectorDataProvider.AddFeatures | QgsVectorDataProvider.DeleteFeatures | QgsVectorDataProvider.ChangeGeometries | QgsVectorDataProvider.ChangeAttributeValues | QgsVectorDataProvider.AddAttributes | QgsVectorDataProvider.DeleteAttributes | QgsVectorDataProvider.RenameAttributes | QgsVectorDataProvider.CreateSpatialIndex | QgsVectorDataProvider.SelectAtId | QgsVectorDataProvider. CircularGeometries
230+
231+
#/* Implementation of functions from QgsDataProvider */
232+
233+
def name(self):
234+
#QString name() const override;
235+
return self.providerKey()
236+
237+
def extent(self):
238+
#QgsRectangle extent() const override;
239+
return QgsRectangle(1, 1, 2, 2)
240+
241+
def updateExtents(self):
242+
#void updateExtents() override;
243+
pass
244+
245+
def isValid(self):
246+
#bool isValid() const override;
247+
return True
248+
249+
def crs(self):
250+
#QgsCoordinateReferenceSystem crs() const override;
251+
return QgsCoordinateReferenceSystem(4326)
252+
253+
254+
class TestPyQgsPythonProvider(unittest.TestCase, ProviderTestCase):
255+
256+
@classmethod
257+
def populateLayer(cls, provider):
258+
f1 = QgsFeature(provider.fields(), 1)
259+
f1.setAttributes([1])
260+
f1.setGeometry(QgsGeometry.fromWkt('Point (1 1)'))
261+
f2 = QgsFeature(provider.fields(), 2)
262+
f2.setAttributes([2])
263+
f2.setGeometry(QgsGeometry.fromWkt('Point (2 2)'))
264+
provider.addFeatures([f1, f2])
265+
266+
@classmethod
267+
def createLayer(cls):
268+
vl = QgsVectorLayer(
269+
'my_data_source_specification',
270+
'test', 'py')
271+
assert (vl.isValid())
272+
273+
return vl
274+
275+
@classmethod
276+
def setUpClass(cls):
277+
"""Run before all tests"""
278+
# Register provider
279+
280+
r = QgsProviderRegistry.instance()
281+
metadata = QgsProviderMetadata(PyProvider.providerKey(), PyProvider.description(), PyProvider.createProvider)
282+
r.registerProvider(metadata)
283+
284+
assert r.providerMetadata(PyProvider.providerKey()) == metadata
285+
286+
# Create test layer
287+
cls.vl = cls.createLayer()
288+
cls.populateLayer(cls.vl.dataProvider())
289+
assert (cls.vl.isValid())
290+
cls.source = cls.vl.dataProvider()
291+
292+
def test_Provider(self):
293+
p = PyProvider('my_uri')
294+
self.populateLayer(p)
295+
features = [f for f in p.getFeatures(QgsFeatureRequest())]
296+
self.assertTrue(features[0].isValid())
297+
self.assertTrue(features[1].isValid())
298+
self.assertEqual(features[0].attributes(), [1])
299+
self.assertEqual(features[1].attributes(), [2])
300+
self.assertEqual(features[0].geometry().asWkt(), 'Point (1 1)')
301+
self.assertEqual(features[1].geometry().asWkt(), 'Point (2 2)')
302+
303+
self.assertEqual(p.featureCount(), 2)
304+
305+
f3 = QgsFeature(p.fields())
306+
f3.setAttributes([3])
307+
f3.setGeometry(QgsGeometry.fromWkt('Point (3 3)'))
308+
p.addFeatures(f3)
309+
self.assertEqual(p.featureCount(), 3)
310+
311+
def test_featureSource(self):
312+
p = PyProvider('my_uri')
313+
self.populateLayer(p)
314+
s = p.featureSource()
315+
it = s.getFeatures(QgsFeatureRequest())
316+
f = QgsFeature()
317+
it.nextFeature(f)
318+
self.assertTrue(f.isValid())
319+
self.assertEqual(f.attributes(), [1])
320+
self.assertEqual(f.geometry().asWkt(), 'Point (1 1)')
321+
322+
323+
if __name__ == '__main__':
324+
unittest.main()

0 commit comments

Comments
 (0)