From d0ba9d83e3a15c6670468d8e1ea12d10c81e1088 Mon Sep 17 00:00:00 2001 From: Stefaan Lippens Date: Thu, 9 Apr 2020 10:21:00 +0200 Subject: [PATCH] EP-3352 #34 Update process graph handling for POST /result --- openeo_driver/views.py | 29 +++- tests/data/pg/1.0/no_nested_json_result.json | 146 ++++++++++--------- tests/test_views_execute.py | 27 +++- 3 files changed, 123 insertions(+), 79 deletions(-) diff --git a/openeo_driver/views.py b/openeo_driver/views.py index 32e9b423..5e98c27d 100644 --- a/openeo_driver/views.py +++ b/openeo_driver/views.py @@ -19,7 +19,7 @@ get_batch_job_result_filenames, get_batch_job_result_output_dir, get_batch_job_log_entries, backend_implementation, summarize_exception) -from openeo_driver.errors import OpenEOApiException +from openeo_driver.errors import OpenEOApiException, ProcessGraphMissingException from openeo_driver.save_result import SaveResult from openeo_driver.users import HttpAuthHandler, User from openeo_driver.utils import replace_nan_values, smart_bool @@ -337,16 +337,17 @@ def me(user: User): @openeo_bp.route('/timeseries' ) def timeseries(): - # TODO: is this deprecated? + # TODO: deprecated? do we still need this endpoint? #35 return 'OpenEO GeoPyspark backend. ' + url_for('.point') @openeo_bp.route('/timeseries/point', methods=['POST']) def point(): + # TODO: deprecated? do we still need this endpoint? #35 x = float(request.args.get('x', '')) y = float(request.args.get('y', '')) srs = request.args.get('srs', None) - process_graph = request.get_json()['process_graph'] + process_graph = _extract_process_graph(request.json) image_collection = evaluate(process_graph, viewingParameters={'version': g.version}) return jsonify(image_collection.timeseries(x, y, srs)) @@ -367,14 +368,30 @@ def download(): return 'Usage: Download image using POST.' +def _extract_process_graph(post_data: dict) -> dict: + """ + Extract process graph dictionary from POST data + + see https://github.com/Open-EO/openeo-api/pull/262 + """ + try: + if requested_api_version().at_least("1.0.0"): + return post_data["process"]["process_graph"] + else: + # API v0.4 style + return post_data['process_graph'] + except KeyError: + raise ProcessGraphMissingException + + @api_endpoint -@openeo_bp.route('/result' , methods=['POST']) +@openeo_bp.route('/result', methods=['POST']) def result(): return execute() @api_endpoint(version=ComparableVersion("0.3.1").or_lower) -@openeo_bp.route('/preview' , methods=['GET', 'POST']) +@openeo_bp.route('/preview', methods=['GET', 'POST']) def preview(): # TODO: is this an old endpoint/shortcut or a custom extension of the API? return execute() @@ -384,7 +401,7 @@ def preview(): def execute(): # TODO: This is not an official endpoint, does this "/execute" still have to be exposed as route? post_data = request.get_json() - process_graph = post_data['process_graph'] + process_graph = _extract_process_graph(post_data) result = evaluate(process_graph, viewingParameters={'version': g.version}) # TODO unify all this output handling within SaveResult logic? diff --git a/tests/data/pg/1.0/no_nested_json_result.json b/tests/data/pg/1.0/no_nested_json_result.json index dce22693..caef4fa9 100644 --- a/tests/data/pg/1.0/no_nested_json_result.json +++ b/tests/data/pg/1.0/no_nested_json_result.json @@ -1,79 +1,81 @@ { - "budget": null, - "title": null, - "description": null, - "plan": null, - "process_graph": { - "loadcollection1": { - "result": null, - "process_id": "load_collection", - "arguments": { - "id": "PROBAV_L3_S10_TOC_NDVI_333M_V2", - "spatial_extent": null, - "temporal_extent": null - } - }, - "zonalstatistics1": { - "result": null, - "process_id": "zonal_statistics", - "arguments": { - "func": "mean", - "data": { - "from_node": "filtertemporal1" - }, - "scale": 1000, - "interval": "day", - "regions": { - "coordinates": [ - [ - [ - 7.022705078125007, - 51.75432477678571 - ], - [ - 7.659912109375007, - 51.74333844866071 - ], - [ - 7.659912109375007, - 51.29289899553571 - ], - [ - 7.044677734375007, - 51.31487165178571 - ], + "process": { + "budget": null, + "title": null, + "description": null, + "plan": null, + "process_graph": { + "loadcollection1": { + "result": null, + "process_id": "load_collection", + "arguments": { + "id": "PROBAV_L3_S10_TOC_NDVI_333M_V2", + "spatial_extent": null, + "temporal_extent": null + } + }, + "zonalstatistics1": { + "result": null, + "process_id": "zonal_statistics", + "arguments": { + "func": "mean", + "data": { + "from_node": "filtertemporal1" + }, + "scale": 1000, + "interval": "day", + "regions": { + "coordinates": [ [ - 7.022705078125007, - 51.75432477678571 + [ + 7.022705078125007, + 51.75432477678571 + ], + [ + 7.659912109375007, + 51.74333844866071 + ], + [ + 7.659912109375007, + 51.29289899553571 + ], + [ + 7.044677734375007, + 51.31487165178571 + ], + [ + 7.022705078125007, + 51.75432477678571 + ] ] - ] - ], - "type": "Polygon" + ], + "type": "Polygon" + } + } + }, + "saveresult1": { + "result": true, + "process_id": "save_result", + "arguments": { + "format": "GTIFF", + "data": { + "from_node": "zonalstatistics1" + }, + "options": {} + } + }, + "filtertemporal1": { + "result": false, + "process_id": "filter_temporal", + "arguments": { + "data": { + "from_node": "loadcollection1" + }, + "extent": [ + "2017-01-01", + "2017-11-21" + ] } - } - }, - "saveresult1": { - "result": true, - "process_id": "save_result", - "arguments": { - "format": "GTIFF", - "data": { - "from_node": "zonalstatistics1" - }, - "options": {} - } - }, - "filtertemporal1": { - "result": false, - "process_id": "filter_temporal", - "arguments": { - "data": { - "from_node": "loadcollection1" - }, - "extent": [ - "2017-01-01", - "2017-11-21" - ] } } } diff --git a/tests/test_views_execute.py b/tests/test_views_execute.py index d527dce7..31702a3d 100644 --- a/tests/test_views_execute.py +++ b/tests/test_views_execute.py @@ -7,7 +7,9 @@ from typing import Union, Callable import dummy_impl +from openeo.capabilities import ComparableVersion from openeo.internal.process_graph_visitor import ProcessGraphVisitor +from openeo_driver.errors import ProcessGraphMissingException from openeo_driver.views import app from .data import load_json, get_path @@ -44,10 +46,14 @@ def load_json(self, filename, preprocess: Callable = None) -> dict: def post_result(self, process_graph: dict, path="/result") -> Response: """Post process graph to API and get response""" + if ComparableVersion("1.0.0").or_higher(self.api_version): + data = {"process": {'process_graph': process_graph}} + else: + data = {'process_graph': process_graph} return self.client.post( path=self.url(path), content_type='application/json', - json={'process_graph': process_graph}, + json=data, ) def check_result(self, process_graph: Union[dict, str], path="/result"): @@ -334,3 +340,22 @@ def test_mask_with_vector_file(api): def test_aggregate_feature_collection(api): api.check_result("aggregate_feature_collection.json") + + +def test_post_result_process_100(client): + api = ApiTester(api_version="1.0.0", client=client, impl=dummy_impl) + response = api.client.post( + path=api.url('/result'), + json={"process": {"process_graph": api.load_json("basic.json")}}, + ) + assert response.status_code == 200 + assert response.content_length > 0 + + +def test_missing_process_graph(api): + response = api.client.post( + path=api.url('/result'), + json={"foo": "bar"}, + ) + assert response.status_code == ProcessGraphMissingException.status_code + assert response.json['code'] == 'ProcessGraphMissing'