Skip to content

Commit 74104e1

Browse files
authored
Merge pull request #28 from geo-engine/wfs-error-check
Wfs-error-check
2 parents 8c8b610 + 8cabd02 commit 74104e1

File tree

8 files changed

+301
-18
lines changed

8 files changed

+301
-18
lines changed

geoengine/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from .auth import Session, get_session, initialize, reset
44
from .error import GeoEngineException, InputException, UninitializedException, TypeException, \
55
MethodNotCalledOnPlotException, MethodNotCalledOnRasterException, MethodNotCalledOnVectorException, \
6-
SpatialReferenceMismatchException
6+
SpatialReferenceMismatchException, check_response_for_error
77
from .types import QueryRectangle
88
from .workflow import WorkflowId, Workflow, workflow_by_id, register_workflow
99
from .datasets import upload_dataframe, StoredDataset

geoengine/error.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
from typing import Dict
66

7+
from requests import Response, HTTPError
8+
79

810
class GeoEngineException(Exception):
911
'''
@@ -107,3 +109,28 @@ def __init__(self, spatial_reference_a: str, spatial_reference_b: str) -> None:
107109

108110
def __str__(self) -> str:
109111
return f"Spatial reference mismatch {self.__spatial_reference_a} != {self.__spatial_reference_b}"
112+
113+
114+
def check_response_for_error(response: Response):
115+
'''
116+
Checks a `Response` for an error and raises it if there is one.
117+
'''
118+
119+
try:
120+
response.raise_for_status()
121+
122+
return # no error
123+
except HTTPError as http_error:
124+
exception = http_error
125+
126+
# try to parse it as a Geo Engine error
127+
try:
128+
response_json = response.json()
129+
if 'error' in response_json:
130+
# override exception with `GeoEngineException`
131+
exception = GeoEngineException(response_json)
132+
except Exception: # pylint: disable=broad-except
133+
pass # ignore errors, it seemed not to be JSON
134+
135+
# either raise the `GeoEngineException` or any other `HTTPError`
136+
raise exception

geoengine/workflow.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from uuid import UUID
99
from logging import debug
10-
from io import StringIO, BytesIO
10+
from io import BytesIO
1111
import urllib.parse
1212
import json
1313

@@ -26,7 +26,7 @@
2626
from geoengine.types import InternalDatasetId, ProvenanceOutput, QueryRectangle, ResultDescriptor
2727
from geoengine.auth import get_session
2828
from geoengine.error import GeoEngineException, MethodNotCalledOnPlotException, MethodNotCalledOnRasterException, \
29-
MethodNotCalledOnVectorException, SpatialReferenceMismatchException
29+
MethodNotCalledOnVectorException, SpatialReferenceMismatchException, check_response_for_error
3030
from geoengine.datasets import StoredDataset, UploadId
3131

3232

@@ -165,17 +165,20 @@ def get_dataframe(self, bbox: QueryRectangle) -> gpd.GeoDataFrame:
165165

166166
data_response = req.get(wfs_url, headers=session.auth_header)
167167

168-
def geo_json_with_time_to_geopandas(data_response):
168+
check_response_for_error(data_response)
169+
170+
data = data_response.json()
171+
172+
def geo_json_with_time_to_geopandas(geo_json):
169173
'''
170174
GeoJson has no standard for time, so we parse the when field
171175
separately and attach it to the data frame as columns `start`
172176
and `end`.
173177
'''
174178

175-
data = gpd.read_file(StringIO(data_response.text))
179+
data = gpd.GeoDataFrame.from_features(geo_json)
176180
data = data.set_crs(bbox.srs, allow_override=True)
177181

178-
geo_json = data_response.json()
179182
start = [f['when']['start'] for f in geo_json['features']]
180183
end = [f['when']['end'] for f in geo_json['features']]
181184

@@ -186,7 +189,7 @@ def geo_json_with_time_to_geopandas(data_response):
186189

187190
return data
188191

189-
return geo_json_with_time_to_geopandas(data_response)
192+
return geo_json_with_time_to_geopandas(data)
190193

191194
def plot_image(self, bbox: QueryRectangle, ax: plt.Axes = None, timeout=3600) -> plt.Axes:
192195
'''
@@ -244,6 +247,8 @@ def wms_get_map_as_image(self, bbox: QueryRectangle, colorizer_min_max: Tuple[fl
244247
wms_request = self.__wms_get_map_request(bbox, colorizer_min_max)
245248
response = req.Session().send(wms_request)
246249

250+
check_response_for_error(response)
251+
247252
return Image.open(BytesIO(response.content))
248253

249254
def __wms_get_map_request(self,
@@ -321,7 +326,11 @@ def plot_chart(self, bbox: QueryRectangle) -> VegaLite:
321326

322327
plot_url = f'{session.server_url}/plot/{self}?bbox={spatial_bounds}&time={time}&spatialResolution={resolution}'
323328

324-
response = req.get(plot_url, headers=session.auth_header).json()
329+
response = req.get(plot_url, headers=session.auth_header)
330+
331+
check_response_for_error(response)
332+
333+
response = response.json()
325334

326335
vega_spec = json.loads(response['data']['vegaString'])
327336

@@ -406,10 +415,11 @@ def save_as_dataset(self, bbox: QueryRectangle, name: str, description: str = ''
406415
url=f'{session.server_url}/datasetFromWorkflow/{self.__workflow_id}',
407416
json=request_body,
408417
headers=session.auth_header,
409-
).json()
418+
)
410419

411-
if 'error' in response:
412-
raise GeoEngineException(response)
420+
check_response_for_error(response)
421+
422+
response = response.json()
413423

414424
return StoredDataset(
415425
dataset_id=InternalDatasetId.from_response(response['dataset']),

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
build
2+
filelock # for cartopy mpl tests
23
flufl.lock # for cartopy mpl tests
34
pycodestyle # formatter
45
pylint # code linter

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ package_dir =
1818
packages = find:
1919
python_requires = >=3.7
2020
install_requires =
21-
cartopy
21+
cartopy==0.19.0.post1
2222
geopandas
2323
matplotlib
2424
numpy

tests/test_plot.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,79 @@ def test_wrong_request(self):
179179
[time, time]
180180
))
181181

182+
def test_plot_error(self):
183+
with requests_mock.Mocker() as m:
184+
m.post('http://mock-instance/anonymous', json={
185+
"id": "c4983c3e-9b53-47ae-bda9-382223bd5081",
186+
"project": None,
187+
"view": None
188+
})
189+
190+
m.post('http://mock-instance/workflow',
191+
json={
192+
"id": "5b9508a8-bd34-5a1c-acd6-75bb832d2d38"
193+
},
194+
request_headers={'Authorization': 'Bearer c4983c3e-9b53-47ae-bda9-382223bd5081'})
195+
196+
m.get('http://mock-instance/workflow/5b9508a8-bd34-5a1c-acd6-75bb832d2d38/metadata',
197+
json={
198+
"type": "plot"
199+
},
200+
request_headers={'Authorization': 'Bearer c4983c3e-9b53-47ae-bda9-382223bd5081'})
201+
202+
m.get(
203+
# pylint: disable=line-too-long
204+
'http://mock-instance/plot/5b9508a8-bd34-5a1c-acd6-75bb832d2d38?bbox=-180.0%2C-90.0%2C180.0%2C90.0&time=2004-04-01T12%3A00%3A00.000%2B00%3A00&spatialResolution=0.1,0.1',
205+
json={
206+
"error": "Operator",
207+
"message": 'Operator: Could not open gdal dataset for file path '
208+
'"test_data/raster/modis_ndvi/MOD13A2_M_NDVI_2004-04-01.TIFF"'
209+
},
210+
status_code=400,
211+
request_headers={
212+
'Authorization': 'Bearer c4983c3e-9b53-47ae-bda9-382223bd5081'}
213+
)
214+
215+
ge.initialize("http://mock-instance")
216+
217+
workflow_definition = {
218+
"type": "Plot",
219+
"operator": {
220+
"type": "Histogram",
221+
"params": {
222+
"bounds": "data",
223+
"buckets": 20
224+
},
225+
"sources": {
226+
"source": {
227+
"type": "GdalSource",
228+
"params": {
229+
"dataset": {
230+
"internal": "36574dc3-560a-4b09-9d22-d5945f2b8093"
231+
}
232+
}
233+
}
234+
}
235+
}
236+
}
237+
238+
time = datetime.strptime(
239+
'2004-04-01T12:00:00.000Z', "%Y-%m-%dT%H:%M:%S.%f%z")
240+
241+
workflow = ge.register_workflow(workflow_definition)
242+
243+
with self.assertRaises(ge.GeoEngineException) as ctx:
244+
workflow.plot_chart(
245+
QueryRectangle(
246+
[-180.0, -90.0, 180.0, 90.0],
247+
[time, time]
248+
)
249+
)
250+
251+
self.assertEqual(str(ctx.exception),
252+
'Operator: Operator: Could not open gdal dataset for file path '
253+
'"test_data/raster/modis_ndvi/MOD13A2_M_NDVI_2004-04-01.TIFF"')
254+
182255

183256
if __name__ == '__main__':
184257
unittest.main()

tests/test_wfs.py

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -292,12 +292,6 @@ def test_geopandas(self):
292292
expected_df = gpd.GeoDataFrame(
293293
# pylint: disable=line-too-long
294294
{
295-
"scalerank": [7, 7, 6, 5, 5, 5, 3],
296-
"website": ["www.ghanaports.gov.gh", "www.nationalportauthorityliberia.org", None, "www.paa-ci.org", None, None, "www.paa-ci.org"],
297-
"NDVI": [nan, 178.0, 108.0, 99.0, 159.0, 128.0, 126.0],
298-
"natlscale": [10.0, 10.0, 20.0, 30.0, 30.0, 30.0, 75.0],
299-
"featurecla": ["Port", "Port", "Port", "Port", "Port", "Port", "Port"],
300-
"name": ["Tema", "Buchanan", "Nieuw Nickerie", "Abidjan", "Kourou", "Paramaribo", "Abidjan"],
301295
"geometry": [
302296
Point(0.007420495, 5.631944444),
303297
Point(-10.05265018, 5.858055556),
@@ -307,6 +301,12 @@ def test_geopandas(self):
307301
Point(-55.13898704, 5.82),
308302
Point(-4.021260306, 5.283333333),
309303
],
304+
"scalerank": [7, 7, 6, 5, 5, 5, 3],
305+
"website": ["www.ghanaports.gov.gh", "www.nationalportauthorityliberia.org", None, "www.paa-ci.org", None, None, "www.paa-ci.org"],
306+
"NDVI": [nan, 178.0, 108.0, 99.0, 159.0, 128.0, 126.0],
307+
"natlscale": [10.0, 10.0, 20.0, 30.0, 30.0, 30.0, 75.0],
308+
"featurecla": ["Port", "Port", "Port", "Port", "Port", "Port", "Port"],
309+
"name": ["Tema", "Buchanan", "Nieuw Nickerie", "Abidjan", "Kourou", "Paramaribo", "Abidjan"],
310310
"start": [datetime.strptime(
311311
'2014-04-01T00:00:00.000Z', "%Y-%m-%dT%H:%M:%S.%f%z") for _ in range(7)],
312312
"end": [datetime.strptime(
@@ -404,6 +404,103 @@ def test_wfs_curl(self):
404404
"""curl -X GET -H "Authorization: Bearer e327d9c3-a4f3-4bd7-a5e1-30b26cae8064" 'http://mock-instance/wfs/956d3656-2d14-5951-96a0-f962b92371cd?service=WFS&version=2.0.0&request=GetFeature&outputFormat=application%2Fjson&typeNames=956d3656-2d14-5951-96a0-f962b92371cd&bbox=-60.0%2C5.0%2C61.0%2C6.0&time=2014-04-01T12%3A00%3A00.000%2B00%3A00&srsName=EPSG%3A4326&queryResolution=0.1%2C0.1'"""
405405
)
406406

407+
def test_wfs_error(self):
408+
with requests_mock.Mocker() as m:
409+
m.post('http://mock-instance/anonymous', json={
410+
"id": "e327d9c3-a4f3-4bd7-a5e1-30b26cae8064",
411+
"user": {
412+
"id": "328ca8d1-15d7-4f59-a989-5d5d72c98744",
413+
},
414+
"created": "2021-06-08T15:22:22.605891994Z",
415+
"validUntil": "2021-06-08T16:22:22.605892183Z",
416+
"project": None,
417+
"view": None
418+
})
419+
420+
m.post('http://mock-instance/workflow',
421+
json={
422+
"id": "956d3656-2d14-5951-96a0-f962b92371cd"
423+
},
424+
request_headers={'Authorization': 'Bearer e327d9c3-a4f3-4bd7-a5e1-30b26cae8064'})
425+
426+
m.get('http://mock-instance/workflow/956d3656-2d14-5951-96a0-f962b92371cd/metadata',
427+
json={
428+
"type": "vector",
429+
"dataType": "MultiPoint",
430+
"spatialReference": "EPSG:4326",
431+
"columns": {
432+
"natlscale": "float",
433+
"featurecla": "text",
434+
"scalerank": "int",
435+
"name": "text",
436+
"NDVI": "int",
437+
"website": "text"
438+
}
439+
},
440+
request_headers={'Authorization': 'Bearer e327d9c3-a4f3-4bd7-a5e1-30b26cae8064'})
441+
442+
m.get('http://mock-instance/wfs/956d3656-2d14-5951-96a0-f962b92371cd',
443+
json={
444+
"error": "Operator",
445+
"message": 'Operator: Could not open gdal dataset for file path '
446+
'"test_data/raster/modis_ndvi/MOD13A2_M_NDVI_2004-04-01.TIFF"'
447+
},
448+
status_code=400,
449+
request_headers={'Authorization': 'Bearer e327d9c3-a4f3-4bd7-a5e1-30b26cae8064'}
450+
)
451+
452+
ge.initialize("http://mock-instance")
453+
454+
workflow_definition = {
455+
"type": "Vector",
456+
"operator": {
457+
"type": "RasterVectorJoin",
458+
"params": {
459+
"names": ["NDVI"],
460+
"featureAggregation": "first",
461+
"temporalAggregation": "none"
462+
},
463+
"sources": {
464+
"vector": {
465+
"type": "OgrSource",
466+
"params": {
467+
"dataset": {
468+
"type": "internal",
469+
"datasetId": "a9623a5b-b6c5-404b-bc5a-313ff72e4e75"
470+
},
471+
"attributeProjection": None
472+
}
473+
},
474+
"rasters": [{
475+
"type": "GdalSource",
476+
"params": {
477+
"dataset": {
478+
"type": "internal",
479+
"datasetId": "36574dc3-560a-4b09-9d22-d5945f2b8093"
480+
}
481+
}
482+
}]
483+
}
484+
}
485+
}
486+
487+
time = datetime.strptime(
488+
'2004-04-01T12:00:00.000Z', "%Y-%m-%dT%H:%M:%S.%f%z")
489+
490+
workflow = ge.register_workflow(workflow_definition)
491+
492+
with self.assertRaises(ge.GeoEngineException) as ctx:
493+
workflow.get_dataframe(
494+
QueryRectangle(
495+
[-60.0, 5.0, 61.0, 6.0],
496+
[time, time]
497+
)
498+
)
499+
500+
self.assertEqual(str(ctx.exception),
501+
'Operator: Operator: Could not open gdal dataset for file path '
502+
'"test_data/raster/modis_ndvi/MOD13A2_M_NDVI_2004-04-01.TIFF"')
503+
407504
def test_repr(self):
408505
with requests_mock.Mocker() as m:
409506
m.post('http://mock-instance/anonymous', json={

0 commit comments

Comments
 (0)