Skip to content

Commit

Permalink
Merge pull request #529 from Open-EO/issue499-execute-json-decode
Browse files Browse the repository at this point in the history
catch json decode errors in connection.execute
  • Loading branch information
soxofaan authored Feb 6, 2024
2 parents 7ce9292 + c63eb75 commit fee4511
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

### Changed
- `Connection.execute()` and `DataCube.execute()` now have a `auto_decode` argument. If set to True (default) the response will be decoded as a JSON and throw an exception if this fails, if set to False the raw `requests.Response` object will be returned. ([#499](https://github.com/Open-EO/openeo-python-client/issues/499))

### Removed

Expand Down
21 changes: 16 additions & 5 deletions openeo/rest/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1576,25 +1576,36 @@ def execute(
process_graph: Union[dict, str, Path],
timeout: Optional[int] = None,
validate: Optional[bool] = None,
):
auto_decode: bool = True,
) -> Union[dict, requests.Response]:
"""
Execute a process graph synchronously and return the result (assumed to be JSON).
Execute a process graph synchronously and return the result. If the result is a JSON object, it will be parsed.
:param process_graph: (flat) dict representing a process graph, or process graph as raw JSON string,
or as local file path or URL
:param validate: Optional toggle to enable/prevent validation of the process graphs before execution
(overruling the connection's ``auto_validate`` setting).
:param auto_decode: Boolean flag to enable/disable automatic JSON decoding of the response. Defaults to True.
:return: parsed JSON response
:return: parsed JSON response as a dict if auto_decode is True, otherwise response object
"""
pg_with_metadata = self._build_request_with_process_graph(process_graph=process_graph)
self._preflight_validation(pg_with_metadata=pg_with_metadata, validate=validate)
return self.post(
response = self.post(
path="/result",
json=pg_with_metadata,
expected_status=200,
timeout=timeout or DEFAULT_TIMEOUT_SYNCHRONOUS_EXECUTE,
).json() # TODO: only do JSON decoding when mimetype is actually JSON?
)
if auto_decode:
try:
return response.json()
except requests.exceptions.JSONDecodeError as e:
raise OpenEoClientException(
"Failed to decode response as JSON. For other data types use `download` method instead of `execute`."
) from e
else:
return response

def create_job(
self,
Expand Down
15 changes: 12 additions & 3 deletions openeo/rest/datacube.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Union, Callable

import numpy as np
import requests
import shapely.geometry
import shapely.geometry.base
from shapely.geometry import MultiPolygon, Polygon, mapping
Expand Down Expand Up @@ -2306,9 +2307,17 @@ def save_user_defined_process(
returns=returns, categories=categories, examples=examples, links=links,
)

def execute(self, *, validate: Optional[bool] = None) -> dict:
"""Executes the process graph."""
return self._connection.execute(self.flat_graph(), validate=validate)
def execute(self, *, validate: Optional[bool] = None, auto_decode: bool = True) -> Union[dict, requests.Response]:
"""
Execute a process graph synchronously and return the result. If the result is a JSON object, it will be parsed.
:param validate: Optional toggle to enable/prevent validation of the process graphs before execution
(overruling the connection's ``auto_validate`` setting).
:param auto_decode: Boolean flag to enable/disable automatic JSON decoding of the response. Defaults to True.
:return: parsed JSON response as a dict if auto_decode is True, otherwise response object
"""
return self._connection.execute(self.flat_graph(), validate=validate, auto_decode=auto_decode)

@staticmethod
@deprecated(reason="Use :py:func:`openeo.udf.run_code.execute_local_udf` instead", version="0.7.0")
Expand Down
32 changes: 31 additions & 1 deletion tests/rest/datacube/test_datacube.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@

import numpy as np
import pytest
import requests
import shapely
import shapely.geometry

from openeo.rest import BandMathException
from openeo.rest import BandMathException, OpenEoClientException
from openeo.rest._testing import build_capabilities
from openeo.rest.connection import Connection
from openeo.rest.datacube import DataCube
Expand Down Expand Up @@ -546,6 +547,35 @@ def test_download_pathlib(connection, requests_mock, tmp_path):
assert path.read_bytes() == b"tiffdata"


def test_execute_json_decode(connection, requests_mock):
requests_mock.get(API_URL + "/collections/S2", json={})
requests_mock.post(API_URL + "/result", content=b'{"foo": "bar"}')
result = connection.load_collection("S2").execute(auto_decode=True)
assert result == {"foo": "bar"}


def test_execute_decode_error(connection, requests_mock):
requests_mock.get(API_URL + "/collections/S2", json={})
requests_mock.post(API_URL + "/result", content=b"tiffdata")
with pytest.raises(OpenEoClientException, match="Failed to decode response as JSON.*$"):
connection.load_collection("S2").execute(auto_decode=True)


def test_execute_json_raw(connection, requests_mock):
requests_mock.get(API_URL + "/collections/S2", json={})
requests_mock.post(API_URL + "/result", content=b'{"foo": "bar"}')
result = connection.load_collection("S2").execute(auto_decode=False)
assert isinstance(result, requests.Response)
assert result.content == b'{"foo": "bar"}'


def test_execute_tiff_raw(connection, requests_mock):
requests_mock.get(API_URL + "/collections/S2", json={})
requests_mock.post(API_URL + "/result", content=b"tiffdata")
result = connection.load_collection("S2").execute(auto_decode=False)
assert isinstance(result, requests.Response)
assert result.content == b"tiffdata"

@pytest.mark.parametrize(["filename", "expected_format"], [
("result.tiff", "GTiff"),
("result.tif", "GTiff"),
Expand Down

0 comments on commit fee4511

Please sign in to comment.