diff --git a/openeo_driver/backend.py b/openeo_driver/backend.py index 70c002bd..7d7695f6 100644 --- a/openeo_driver/backend.py +++ b/openeo_driver/backend.py @@ -442,6 +442,7 @@ def to_api_dict(self, full=True, api_version: ComparableVersion = None) -> dict: class BatchJobResultMetadata: # Basic dataclass based wrapper for batch job result metadata (allows cleaner code navigation and discovery) assets: Dict[str, dict] = dataclasses.field(default_factory=dict) + items: Dict[str, dict] = dataclasses.field(default_factory=dict) links: List[dict] = dataclasses.field(default_factory=list) providers: List[dict] = dataclasses.field(default_factory=list) # TODO: more fields diff --git a/openeo_driver/views.py b/openeo_driver/views.py index 9e1df4a1..3eaa699f 100644 --- a/openeo_driver/views.py +++ b/openeo_driver/views.py @@ -1161,16 +1161,20 @@ def job_results_canonical_url() -> str: if TREAT_JOB_RESULTS_V100_LIKE_V110 or requested_api_version().at_least("1.1.0"): ml_model_metadata = None - def job_result_item_url(item_id) -> str: + def job_result_item_url(item_id, is11 = False) -> str: signer = get_backend_config().url_signer + + method_start = ".get_job_result_item" + if is11: + method_start = method_start + "11" if not signer: - return url_for(".get_job_result_item", job_id=job_id, item_id=item_id, _external=True) + return url_for(method_start, job_id=job_id, item_id=item_id, _external=True) expires = signer.get_expires() secure_key = signer.sign_job_item(job_id=job_id, user_id=user_id, item_id=item_id, expires=expires) user_base64 = user_id_b64_encode(user_id) return url_for( - ".get_job_result_item_signed", + method_start + "_signed", job_id=job_id, user_base64=user_base64, secure_key=secure_key, @@ -1179,19 +1183,27 @@ def job_result_item_url(item_id) -> str: _external=True, ) - for filename, metadata in result_assets.items(): - if ("data" in metadata.get("roles", []) and - any(media_type in metadata.get("type", "") for media_type in - ["geotiff", "netcdf", "text/csv", "application/parquet"])): - links.append( - {"rel": "item", "href": job_result_item_url(item_id=filename), "type": stac_item_media_type} - ) - elif metadata.get("ml_model_metadata", False): - # TODO: Currently we only support one ml_model per batch job. - ml_model_metadata = metadata + + if len(result_metadata.items) > 0 : + for item_id, metadata in result_metadata.items.items(): links.append( - {"rel": "item", "href": job_result_item_url(item_id=filename), "type": "application/json"} + {"rel": "item", "href": job_result_item_url(item_id=item_id, is11=True), "type": stac_item_media_type} ) + else: + + for filename, metadata in result_assets.items(): + if ("data" in metadata.get("roles", []) and + any(media_type in metadata.get("type", "") for media_type in + ["geotiff", "netcdf", "text/csv", "application/parquet"])): + links.append( + {"rel": "item", "href": job_result_item_url(item_id=filename), "type": stac_item_media_type} + ) + elif metadata.get("ml_model_metadata", False): + # TODO: Currently we only support one ml_model per batch job. + ml_model_metadata = metadata + links.append( + {"rel": "item", "href": job_result_item_url(item_id=filename), "type": "application/json"} + ) result = dict_no_none( { @@ -1359,12 +1371,44 @@ def get_job_result_item_signed(job_id, user_base64, secure_key, item_id): signer.verify_job_item(signature=secure_key, job_id=job_id, user_id=user_id, item_id=item_id, expires=expires) return _get_job_result_item(job_id, item_id, user_id) + @api_endpoint + @blueprint.route('/jobs//results/items11///', methods=['GET']) + def get_job_result_item11_signed(job_id, user_base64, secure_key, item_id): + expires = request.args.get('expires') + signer = get_backend_config().url_signer + user_id = user_id_b64_decode(user_base64) + signer.verify_job_item(signature=secure_key, job_id=job_id, user_id=user_id, item_id=item_id, expires=expires) + return _get_job_result_item11(job_id, item_id, user_id) + @api_endpoint(version=ComparableVersion("1.1.0").or_higher) @blueprint.route('/jobs//results/items/', methods=['GET']) @auth_handler.requires_bearer_auth def get_job_result_item(job_id: str, item_id: str, user: User) -> flask.Response: return _get_job_result_item(job_id, item_id, user.user_id) + @api_endpoint(version=ComparableVersion("1.1.0").or_higher) + @blueprint.route('/jobs//results/items11/', methods=['GET']) + @auth_handler.requires_bearer_auth + def get_job_result_item11(job_id: str, item_id: str, user: User) -> flask.Response: + return _get_job_result_item11(job_id, item_id, user.user_id) + + def _get_job_result_item11(job_id, item_id, user_id): + if item_id == DriverMlModel.METADATA_FILE_NAME: + return _download_ml_model_metadata(job_id, item_id, user_id) + + metadata = backend_implementation.batch_jobs.get_result_metadata( + job_id=job_id, user_id=user_id + ) + + if item_id not in metadata.items: + raise OpenEOApiException("Item with id {item_id!r} not found in job {job_id!r}".format(item_id=item_id, job_id=job_id)) + item_metadata = metadata.items.get(item_id,None) + + resp = jsonify(item_metadata) + resp.mimetype = stac_item_media_type + return resp + + def _get_job_result_item(job_id, item_id, user_id): if item_id == DriverMlModel.METADATA_FILE_NAME: return _download_ml_model_metadata(job_id, item_id, user_id)