Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Warn when `save_result` uses output format options that are not advertised by the backend. ([#649](https://github.com/Open-EO/openeo-python-client/issues/649))
Comment thread
Silvren marked this conversation as resolved.

### Changed

### Removed
Expand Down
10 changes: 8 additions & 2 deletions openeo/rest/_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,17 @@ def setup_collection(
)
return self

def setup_file_format(self, name: str, type: str = "output", gis_data_types: Iterable[str] = ("raster",)):
def setup_file_format(
self,
name: str,
type: str = "output",
gis_data_types: Iterable[str] = ("raster",),
parameters: Optional[dict] = None,
):
self.file_formats[type][name] = {
"title": name,
"gis_data_types": list(gis_data_types),
"parameters": {},
"parameters": parameters or {},
}
self._requests_mock.get(self.connection.build_url("/file_formats"), json=self.file_formats)
return self
Expand Down
30 changes: 30 additions & 0 deletions openeo/rest/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,36 @@ def list_input_formats(self) -> dict:
def list_output_formats(self) -> dict:
return self.list_file_formats().get("output", {})

def _check_output_format(
self,
*,
format: str,
options: Optional[Mapping],
) -> None:
"""Validate an output format and warn about options not advertised by the backend."""
output_formats = self.list_output_formats()
format_name = next(
(
name
for name in output_formats
if isinstance(name, str) and name.lower() == format.lower()
),
None,
)
if format_name is None:
raise ValueError(f"Invalid format {format!r}. Should be one of {set(output_formats)}")

format_metadata = output_formats[format_name]
parameters = format_metadata.get("parameters") if isinstance(format_metadata, Mapping) else None
if options and isinstance(parameters, Mapping):
unsupported = sorted(set(options).difference(parameters))
if unsupported:
warnings.warn(
f"Unsupported options {unsupported} for output format {format!r} "
f"(supported options: {sorted(parameters)}).",
stacklevel=3,
)

list_file_types = legacy_alias(
list_output_formats, "list_file_types", since="0.4.6"
)
Expand Down
8 changes: 4 additions & 4 deletions openeo/rest/datacube.py
Original file line number Diff line number Diff line change
Expand Up @@ -2403,10 +2403,10 @@ def save_result(
of another :py:class:`~openeo.rest.datacube.DataCube` instance.
"""
if self._connection:
formats = set(self._connection.list_output_formats().keys())
# TODO: map format to correct casing too?
if format.lower() not in {f.lower() for f in formats}:
raise ValueError("Invalid format {f!r}. Should be one of {s}".format(f=format, s=formats))
self._connection._check_output_format(
format=format,
options=options,
)

pg = self._build_pgnode(
process_id="save_result",
Expand Down
6 changes: 5 additions & 1 deletion openeo/rest/vectorcube.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,11 +211,15 @@ def save_result(
returns a :py:class:`~openeo.rest.result.SaveResult` instance instead
of another :py:class:`~openeo.rest.vectorcube.VectorCube` instance.
"""
format = format or "GeoJSON"
if self._connection and options:
self._connection._check_output_format(format=format, options=options)

pg = self._build_pgnode(
process_id="save_result",
arguments={
"data": self,
"format": format or "GeoJSON",
"format": format,
# TODO: leave out options if unset?
"options": options or {},
},
Expand Down
10 changes: 10 additions & 0 deletions tests/rest/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ def dummy_backend(requests_mock, con120) -> DummyBackend:
dummy_backend.setup_collection("S2")
dummy_backend.setup_file_format("GTiff")
dummy_backend.setup_file_format("netCDF")
dummy_backend.setup_file_format(
"GeoJSON",
gis_data_types=("vector",),
parameters={"precision": {"type": "integer"}},
)
return dummy_backend


Expand All @@ -100,6 +105,11 @@ def another_dummy_backend(requests_mock) -> DummyBackend:
another_dummy_backend.setup_collection("S2")
another_dummy_backend.setup_file_format("GTiff")
another_dummy_backend.setup_file_format("netCDF")
another_dummy_backend.setup_file_format(
"GeoJSON",
gis_data_types=("vector",),
parameters={"precision": {"type": "integer"}},
)
return another_dummy_backend


Expand Down
4 changes: 4 additions & 0 deletions tests/rest/datacube/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ def _setup_connection(api_version, requests_mock, build_capabilities_kwargs: Opt
"GTiff": {"gis_data_types": ["raster"]},
"netCDF": {"gis_data_types": ["raster"]},
"csv": {"gis_data_types": ["table"]},
"GeoJSON": {
"gis_data_types": ["vector"],
"parameters": {"precision": {"type": "integer"}},
},
}
},
)
Expand Down
33 changes: 33 additions & 0 deletions tests/rest/datacube/test_datacube100.py
Original file line number Diff line number Diff line change
Expand Up @@ -2965,6 +2965,39 @@ def test_save_result_format(con100, requests_mock):
cube.save_result(format="pNg")


def test_save_result_format_options_warning(con100, requests_mock):
requests_mock.get(
API_URL + "/file_formats",
json={
"output": {
"GTiff": {
"gis_data_types": ["raster"],
"parameters": {
"compression": {"type": "string"},
"tile_size": {"type": "integer"},
},
}
}
},
)

cube = con100.load_collection("S2")
with pytest.warns(
UserWarning,
match=r"Unsupported options \['overview_level'\] for output format 'gtiff' "
r"\(supported options: \['compression', 'tile_size'\]\)\.",
):
result = cube.save_result(
format="gtiff",
options={"compression": "DEFLATE", "overview_level": 4},
)

assert result.flat_graph()["saveresult1"]["arguments"]["options"] == {
"compression": "DEFLATE",
"overview_level": 4,
}


EXPECTED_JSON_EXPORT_S2_NDVI = textwrap.dedent(
"""\
{
Expand Down
22 changes: 22 additions & 0 deletions tests/rest/datacube/test_vectorcube.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,28 @@ def test_save_result_and_download_filename(
assert output_path.read_bytes() == DummyBackend.DEFAULT_RESULT


def test_save_result_format_options_warning(vector_cube, dummy_backend):
dummy_backend.setup_file_format(
"GeoJSON",
gis_data_types=("vector",),
parameters={"indent": {"type": "integer"}},
)

with pytest.warns(
UserWarning,
match=r"Unsupported options \['invalid'\] for output format 'geojson' "
r"\(supported options: \['indent'\]\)\.",
):
result = vector_cube.save_result(format="geojson", options={"indent": 2, "invalid": True})

assert result.flat_graph()["saveresult1"]["arguments"]["options"] == {"indent": 2, "invalid": True}


def test_save_result_invalid_format(vector_cube):
with pytest.raises(ValueError, match="Invalid format 'invalid'"):
vector_cube.save_result(format="invalid", options={"indent": 2})


@pytest.mark.parametrize(
["save_result_format", "execute_format", "output_file", "expected"],
[
Expand Down