diff --git a/CHANGELOG.md b/CHANGELOG.md index fffa9f41..c1ced2f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ and start a new "In Progress" section above it. - `run_udf` on the result of `read_vector`/`get_geometries` no longer passes the geometries as feature collection to the UDF, but follows the generic vector cube handling (like `load_uploaded_files`). - Use `save_result` node id in synchronous download filename hint (content-disposition header), and align filename extension handling with batch jobs ([#548](https://github.com/Open-EO/openeo-python-driver/issues/548)) - Support extraction of default job options from predefined (and "custom") processes, and do basic merging ([#483](https://github.com/Open-EO/openeo-python-driver/issues/483)) +- `load_stac` with STAC metadata where "bands" dimension is erroneously missing: "bands" dimension will still be forced in dry run if "bands" argument is given to `load_stac` ([Open-EO/openeo-geopyspark-driver#1907](https://github.com/Open-EO/openeo-geopyspark-driver/issues/1907)) + ## 0.141.0 diff --git a/openeo_driver/dry_run.py b/openeo_driver/dry_run.py index 44598825..78944998 100644 --- a/openeo_driver/dry_run.py +++ b/openeo_driver/dry_run.py @@ -446,7 +446,7 @@ def load_result(self, job_id: str, arguments: dict) -> "DryRunDataCube": @staticmethod @lru_cache(maxsize=20) - def _stac_metadata(stac_ref): + def _stac_metadata(stac_ref) -> CubeMetadata: try: return openeo_driver.stac.datacube.stac_to_cube_metadata(stac_ref=stac_ref) except Exception as e: @@ -474,6 +474,14 @@ def load_stac( metadata = DryRunDataTracer._stac_metadata(stac_ref=url) + if arguments.get("bands") and not metadata.has_band_dimension(): + _log.warning( + "No band dimension detected in dry-run `load_stac` metadata extraction, but bands were requested. Forcing 'bands' dimension in metadata." + ) + band_dimension = BandDimension(name="bands", bands=[Band(name=b) for b in arguments["bands"]]) + # TODO: possible to avoid usage of `_dimensions` internal? + metadata = CubeMetadata(dimensions=metadata._dimensions + [band_dimension]) + cube = DryRunDataCube(traces=[trace], data_tracer=self, metadata=metadata) if "temporal_extent" in arguments: cube = cube.filter_temporal(*arguments["temporal_extent"]) diff --git a/openeo_driver/stac/datacube.py b/openeo_driver/stac/datacube.py index 08835709..f3a2d0d0 100644 --- a/openeo_driver/stac/datacube.py +++ b/openeo_driver/stac/datacube.py @@ -41,6 +41,7 @@ def stac_to_cube_metadata(stac_ref: StacRef) -> openeo.metadata.CubeMetadata: """ Parse STAC metadata and convert it to :py:class:`openeo.metadata.CubeMetadata`. """ + # TODO: replace/unify with dimension parsing from python client introduced by https://github.com/Open-EO/openeo-python-client/pull/867 # Dimensions as pystac objects pystac_dimensions = _get_dimensions(stac_ref=stac_ref) diff --git a/tests/test_dry_run.py b/tests/test_dry_run.py index fb2a5fe5..e696582a 100644 --- a/tests/test_dry_run.py +++ b/tests/test_dry_run.py @@ -1659,6 +1659,77 @@ def test_load_stac_spatial_extent_requires_a_polygon( evaluate(pg, dry_run_env) +@pytest.mark.parametrize( + ["load_stac_kwargs", "expected"], + [ + ({"bands": ["B01", "B02"]}, "warning"), + ({}, "error"), + ({"bands": None}, "error"), + ], +) +def test_load_stac_no_bands_in_metadata_but_user_forced( + dry_run_tracer, backend_implementation, tmp_path, caplog, load_stac_kwargs, expected +): + """ + No bands in the STAC metadata, but user forced a band selection. + https://github.com/Open-EO/openeo-geopyspark-driver/issues/1907 + """ + stac_ref = tmp_path / "stac.json" + stac_metadata = StacDummyBuilder.collection( + cube_dimensions={ + "x": {"type": "spatial", "axis": "x", "extent": [0, 10], "reference_system": 4326}, + "y": {"type": "spatial", "axis": "y", "extent": [0, 20], "reference_system": 4326}, + "t": {"type": "temporal", "extent": ["2020-01-01T00:00:00Z", "2020-02-01T00:00:00Z"]}, + } + ) + stac_ref.write_text(json.dumps(stac_metadata)) + + pg = { + "loadstac1": { + "process_id": "load_stac", + "arguments": { + "url": str(stac_ref), + **load_stac_kwargs, + }, + }, + "reducebands": { + "process_id": "reduce_dimension", + "arguments": { + "data": {"from_node": "loadstac1"}, + "dimension": "bands", + "reducer": { + "process_graph": { + "mean": { + "process_id": "mean", + "arguments": {"data": {"from_parameter": "data"}}, + "result": True, + } + } + }, + }, + "result": True, + }, + } + + dry_run_env = EvalEnv( + {ENV_DRY_RUN_TRACER: dry_run_tracer, "backend_implementation": backend_implementation, "version": "2.0.0"} + ) + + if expected == "warning": + # `reduce_dimension` of "bands" works, but with warning + evaluate(pg, dry_run_env) + assert "Forcing 'bands' dimension in metadata" in caplog.text + elif expected == "error": + # Fails with error about missing bands dimension in metadata + with pytest.raises( + ProcessParameterInvalidException, + match=r"parameter.*dimension.*is invalid.*Must be one of.*'x', 'y', 't'.* but got 'bands'", + ): + evaluate(pg, dry_run_env) + else: + raise ValueError(f"Unexpected expected value: {expected}") + + @pytest.mark.parametrize( ["arguments", "expected"], [