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
56 changes: 46 additions & 10 deletions openeo_driver/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import flask
import flask_cors
import pystac
from flask import (
Blueprint,
Flask,
Expand Down Expand Up @@ -868,17 +869,22 @@ def _properties_from_job_info(job_info: BatchJobMetadata) -> dict:
"card4l:specification": "SR",
"card4l:specification_version": "5.0",
"processing:facility": get_backend_config().processing_facility,
"processing:software": get_backend_config().processing_software,
"processing:software": {
get_backend_config().processing_software: get_backend_config().capabilities_backend_version
},
}
)
properties["datetime"] = None

start_datetime = to_datetime(job_info.start_datetime)
end_datetime = to_datetime(job_info.end_datetime)

if start_datetime == end_datetime:
if start_datetime is None and end_datetime is None:
# No temporal range available: fall back to job created time to produce a valid STAC item
# (STAC requires start_datetime+end_datetime when datetime is null)
properties["datetime"] = to_datetime(job_info.created)
elif start_datetime == end_datetime:
properties["datetime"] = start_datetime
else:
properties["datetime"] = None
if start_datetime:
properties["start_datetime"] = start_datetime
if end_datetime:
Expand Down Expand Up @@ -1122,6 +1128,8 @@ def job_results_canonical_url() -> str:
}
],
}
pystac_item = pystac.Collection.from_dict(result)
pystac_item.validate()
return jsonify(result)

with TimingLogger(f"backend_implementation.batch_jobs.get_result_metadata({job_id=}, {user_id=})", _log):
Expand Down Expand Up @@ -1333,8 +1341,9 @@ def intersect_dicts(dict1, dict2):
"interval": [[to_datetime(job_info.start_datetime), to_datetime(job_info.end_datetime)]]
},
},
"summaries": {"instruments": job_info.instruments } if job_info.instruments else {},
"providers": providers or None,
"summaries": {"instruments": job_info.instruments} if job_info.instruments else {},
"providers": providers
or backend_implementation.batch_jobs._get_providers(job_id=job_id, user_id=user_id),
"links": links,
"assets": assets,
"item_assets": item_assets,
Expand Down Expand Up @@ -1378,7 +1387,6 @@ def intersect_dicts(dict1, dict2):

result["stac_extensions"] = [
STAC_EXTENSION.PROCESSING,
STAC_EXTENSION.CARD4LOPTICAL,
STAC_EXTENSION.FILEINFO,
]

Expand All @@ -1391,6 +1399,15 @@ def intersect_dicts(dict1, dict2):
result["stac_extensions"].append(STAC_EXTENSION.PROJECTION_V120)

# TODO "OpenEO-Costs" header?

stac_type = result.get("type")
if stac_type == "Feature":
pystac_obj = pystac.Item.from_dict(result)
elif stac_type == "Collection":
pystac_obj = pystac.Collection.from_dict(result)
else:
pystac_obj = pystac.read_dict(result)
pystac_obj.validate()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the PySTAC validation dependency before validating

When openeo_driver is installed normally, setup.py only pulls in pystac>=1.8.0; jsonschema is currently only listed in the test extras. PySTAC's validate() path requires its validation extra/jsonschema, so this new runtime call makes /jobs/{id}/results raise an ImportError/500 in a minimal production install as soon as a finished job result is returned. Either promote the validation dependency to runtime dependencies or avoid validating on the request path.

Useful? React with 👍 / 👎.

return jsonify(result)

# TODO: Issue #232, TBD: refactor download functionality? more abstract, just stream blocks of bytes from S3 or from a directory.
Expand Down Expand Up @@ -1630,6 +1647,8 @@ def _get_job_result_item11(job_id, item_id, user_id):
)
)

pystac_item = pystac.Item.from_dict(stac_item)
pystac_item.validate()
resp = jsonify(stac_item)
resp.mimetype = stac_item_media_type
return resp
Expand Down Expand Up @@ -1808,6 +1827,8 @@ def _get_job_result_item(job_id, item_id, user_id):
}
)
)
pystac_item = pystac.Item.from_dict(stac_item)
pystac_item.validate()

resp = jsonify(stac_item)
resp.mimetype = stac_item_media_type
Expand All @@ -1825,6 +1846,18 @@ def _download_ml_model_metadata(job_id: str, file_name: str, user_id) -> flask.R
asset["href"] = backend_implementation.config.asset_url.build_url(
asset_metadata=asset, asset_name=asset_file_name, job_id=job_id, user_id=user_id
)
links = [
{
"rel": "self",
"href": url_for(".get_job_result_item", job_id=job_id, item_id=file_name, _external=True),
"type": stac_item_media_type,
},
{
"rel": "collection",
"href": url_for(".list_job_results", job_id=job_id, _external=True),
"type": "application/json",
},
] + ml_model_metadata.get("links", [])
stac_item = {
"stac_version": ml_model_metadata.get("stac_version", "1.0.0"),
"stac_extensions": ml_model_metadata.get("stac_extensions", []),
Expand All @@ -1833,10 +1866,13 @@ def _download_ml_model_metadata(job_id: str, file_name: str, user_id) -> flask.R
"collection": job_id,
"bbox": ml_model_metadata.get("bbox", []),
"geometry": ml_model_metadata.get("geometry", {}),
'properties': ml_model_metadata.get("properties", {}),
'links': ml_model_metadata.get("links", []),
'assets': ml_model_metadata.get("assets", {})
"properties": ml_model_metadata.get("properties", {}),
"links": links,
"assets": ml_model_metadata.get("assets", {}),
}

pystac_item = pystac.Item.from_dict(stac_item)
pystac_item.validate()
resp = jsonify(stac_item)
resp.mimetype = stac_item_media_type
return resp
Expand Down
39 changes: 27 additions & 12 deletions tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1948,17 +1948,18 @@ def test_get_job_results_100(self, api100):
],
"properties": {
"created": "2017-01-01T09:32:12Z",
"datetime": None,
"datetime": "2017-01-01T09:32:12Z",
"card4l:processing_chain": {"process_graph": {"foo": {"process_id": "foo", "arguments": {}}}},
"card4l:specification": "SR",
"card4l:specification_version": "5.0",
"processing:facility": "Dummy openEO API",
"processing:software": "openeo-python-driver",
"processing:software": {
dummy_config.config.processing_software: dummy_config.config.capabilities_backend_version
},
},
"providers": EXPECTED_PROVIDERS,
"stac_extensions": [
"https://stac-extensions.github.io/processing/v1.1.0/schema.json",
"https://stac-extensions.github.io/card4l/v0.1.0/optical/schema.json",
"https://stac-extensions.github.io/file/v2.1.0/schema.json",
"https://stac-extensions.github.io/eo/v1.1.0/schema.json",
],
Expand Down Expand Up @@ -2043,12 +2044,13 @@ def test_get_job_results_100(self, api100):
"card4l:specification": "SR",
"card4l:specification_version": "5.0",
"processing:facility": "Dummy openEO API",
"processing:software": "openeo-python-driver",
"processing:software": {
dummy_config.config.processing_software: dummy_config.config.capabilities_backend_version
},
},
"providers": EXPECTED_PROVIDERS,
"stac_extensions": [
"https://stac-extensions.github.io/processing/v1.1.0/schema.json",
"https://stac-extensions.github.io/card4l/v0.1.0/optical/schema.json",
"https://stac-extensions.github.io/file/v2.1.0/schema.json",
"https://stac-extensions.github.io/eo/v1.1.0/schema.json",
"https://stac-extensions.github.io/projection/v1.2.0/schema.json",
Expand Down Expand Up @@ -2394,17 +2396,18 @@ def test_get_job_results_signed_100(self, api100, flask_app, backend_config_over
],
"properties": {
"created": "2017-01-01T09:32:12Z",
"datetime": None,
"datetime": "2017-01-01T09:32:12Z",
"card4l:processing_chain": {"process_graph": {"foo": {"process_id": "foo", "arguments": {}}}},
"card4l:specification": "SR",
"card4l:specification_version": "5.0",
"processing:facility": "Dummy openEO API",
"processing:software": "openeo-python-driver",
"processing:software": {
dummy_config.config.processing_software: dummy_config.config.capabilities_backend_version
},
},
"providers": EXPECTED_PROVIDERS,
"stac_extensions": [
"https://stac-extensions.github.io/processing/v1.1.0/schema.json",
"https://stac-extensions.github.io/card4l/v0.1.0/optical/schema.json",
"https://stac-extensions.github.io/file/v2.1.0/schema.json",
"https://stac-extensions.github.io/eo/v1.1.0/schema.json",
],
Expand Down Expand Up @@ -2678,17 +2681,18 @@ def test_get_job_results_signed_with_expiration_100(self, api100, flask_app, bac
],
"properties": {
"created": "2017-01-01T09:32:12Z",
"datetime": None,
"datetime": "2017-01-01T09:32:12Z",
"card4l:processing_chain": {"process_graph": {"foo": {"process_id": "foo", "arguments": {}}}},
"card4l:specification": "SR",
"card4l:specification_version": "5.0",
"processing:facility": "Dummy openEO API",
"processing:software": "openeo-python-driver",
"processing:software": {
dummy_config.config.processing_software: dummy_config.config.capabilities_backend_version
},
},
"providers": EXPECTED_PROVIDERS,
"stac_extensions": [
"https://stac-extensions.github.io/processing/v1.1.0/schema.json",
"https://stac-extensions.github.io/card4l/v0.1.0/optical/schema.json",
"https://stac-extensions.github.io/file/v2.1.0/schema.json",
"https://stac-extensions.github.io/eo/v1.1.0/schema.json",
],
Expand Down Expand Up @@ -4135,7 +4139,18 @@ def test_download_ml_model_metadata(self, flask_app, api110, backend_config_over
],
"type": "Polygon",
},
"links": [],
"links": [
{
"rel": "self",
"href": "http://oeo.net/openeo/1.1.0/jobs/53c71345-09b4-46b4-b6b0-03fd6fe1f199/results/items/ml_model_metadata.json",
"type": "application/geo+json",
},
{
"rel": "collection",
"href": "http://oeo.net/openeo/1.1.0/jobs/53c71345-09b4-46b4-b6b0-03fd6fe1f199/results",
"type": "application/json",
},
],
"properties": {
"datetime": None,
"end_datetime": "9999-12-31T23:59:59Z",
Expand Down