diff --git a/stac_fastapi/eodag/core.py b/stac_fastapi/eodag/core.py index a0592231..a6aeb48d 100644 --- a/stac_fastapi/eodag/core.py +++ b/stac_fastapi/eodag/core.py @@ -210,7 +210,7 @@ async def _search_base(self, search_request: BaseSearchPostRequest, request: Req extension_names = [type(ext).__name__ for ext in self.extensions] for product in search_result: - feature = create_stac_item(product, self.extension_is_enabled, request, extension_names, request_json) + feature = create_stac_item(product, request, extension_names, request_json) features.append(feature) feature_collection = ItemCollection( diff --git a/stac_fastapi/eodag/extensions/collection_order.py b/stac_fastapi/eodag/extensions/collection_order.py index 8cc33830..1d5ec9f0 100644 --- a/stac_fastapi/eodag/extensions/collection_order.py +++ b/stac_fastapi/eodag/extensions/collection_order.py @@ -65,10 +65,6 @@ class BaseCollectionOrderClient: stac_metadata_model: type[BaseModel] = attr.ib(default=CommonStacMetadata) extensions: list[ApiExtension] = attr.ib(default=[]) - def extension_is_enabled(self, extension: str) -> bool: - """Check if an api extension is enabled.""" - return any(type(ext).__name__ == extension for ext in self.extensions) - def order_collection( self, collection_id: str, @@ -135,7 +131,7 @@ def order_collection( ) extension_names = [type(ext).__name__ for ext in self.extensions] - return create_stac_item(product, self.extension_is_enabled, request, extension_names) + return create_stac_item(product, request, extension_names) @attr.s diff --git a/stac_fastapi/eodag/models/item.py b/stac_fastapi/eodag/models/item.py index 68fc9de4..c8090cba 100644 --- a/stac_fastapi/eodag/models/item.py +++ b/stac_fastapi/eodag/models/item.py @@ -17,7 +17,7 @@ # limitations under the License. """stac item.""" -from typing import Any, Callable, Optional +from typing import Any, Optional from urllib.parse import quote, unquote_plus, urlparse import orjson @@ -25,20 +25,18 @@ from stac_fastapi.types.errors import NotFoundError from stac_fastapi.types.requests import get_base_url from stac_fastapi.types.stac import Item -from stac_pydantic.api.version import STAC_API_VERSION -from stac_pydantic.shared import Asset from eodag.api.product._product import EOProduct -from eodag.api.product.metadata_mapping import OFFLINE_STATUS, ONLINE_STATUS +from eodag.api.product.metadata_mapping import ONLINE_STATUS from eodag.utils import deepcopy, guess_file_type from stac_fastapi.eodag.config import Settings, get_settings from stac_fastapi.eodag.errors import MisconfiguredError from stac_fastapi.eodag.models.links import ItemLinks -def _get_retrieve_body_for_order(product: EOProduct) -> dict[str, Any]: - """returns the body of the request used to order a product""" - parts = urlparse(product.properties["eodag:order_link"]) +def _get_retrieve_body_for_order(order_link: str) -> dict[str, Any]: + """Return the body of the request used to order a product.""" + parts = urlparse(order_link) keys = ["request", "inputs", "location"] # keys used by different providers request_dict = orjson.loads(parts.query) retrieve_body = None @@ -57,7 +55,6 @@ def _get_retrieve_body_for_order(product: EOProduct) -> dict[str, Any]: def create_stac_item( product: EOProduct, - extension_is_enabled: Callable[[str], bool], request: Request, extension_names: Optional[list[str]], request_json: Optional[Any] = None, @@ -68,118 +65,87 @@ def create_stac_item( settings: Settings = get_settings() - collection_obj = request.app.state.dag.collections_config.get(product.collection) - collection = collection_obj.id if collection_obj else product.collection - - feature = Item( - type="Feature", - assets={}, - id=product.properties["id"], - geometry=product.geometry.__geo_interface__, - bbox=product.geometry.bounds, - collection=collection, - stac_version=STAC_API_VERSION, - ) + feature = product.as_dict() + properties = feature["properties"] + assets = feature["assets"] + provider = properties["federation:backends"][0] + collection = feature["collection"] + enabled_extensions = set(extension_names or []) - download_base_url = settings.download_base_url - if not download_base_url: - download_base_url = get_base_url(request) + download_base_url = settings.download_base_url or get_base_url(request) quoted_id = quote(feature["id"]) asset_proxy_url = ( - (download_base_url + f"data/{product.provider}/{collection}/{quoted_id}") - if extension_is_enabled("DataDownload") + (download_base_url + f"data/{provider}/{collection}/{quoted_id}") + if "DataDownload" in enabled_extensions else None ) - settings = get_settings() auto_order_whitelist = settings.auto_order_whitelist - if product.provider in auto_order_whitelist: + if provider in auto_order_whitelist: # a product from a whitelisted federation backend is considered as online - product.properties["order:status"] = ONLINE_STATUS - - # create assets only if product is not offline - if ( - product.properties.get("order:status", ONLINE_STATUS) != OFFLINE_STATUS - or product.provider in auto_order_whitelist - ): - for k, v in product.assets.items(): - # TODO: download extension with origin link (make it optional ?) - asset_model = Asset.model_validate(v) - feature["assets"][k] = asset_model.model_dump(exclude_none=True) - - if asset_proxy_url: - origin = deepcopy(feature["assets"][k]) - quoted_key = quote(k) - feature["assets"][k]["href"] = asset_proxy_url + "/" + quoted_key - - origin_href = origin.get("href") - if ( - settings.keep_origin_url - and origin_href - and not origin_href.startswith(tuple(settings.origin_url_blacklist)) - ): - feature["assets"][k]["alternate"] = {"origin": origin} - - # TODO: remove downloadLink asset after EODAG assets rework - if (download_link := product.properties.get("eodag:download_link")) and not any( - key.endswith(".parquet") for key in product.assets - ): - origin_href = download_link - if asset_proxy_url: - download_link = asset_proxy_url + "/downloadLink" - - mime_type = guess_file_type(origin_href) or "application/octet-stream" - - feature["assets"]["downloadLink"] = { - "title": "Download link", - "href": download_link, - # TODO: download link is not always a ZIP archive - "type": mime_type, + properties["order:status"] = ONLINE_STATUS + + keep_origin_url = settings.keep_origin_url + origin_url_blacklist = tuple(settings.origin_url_blacklist) + + if asset_proxy_url: + for asset_name, asset in assets.items(): + should_keep_origin = keep_origin_url and not asset["href"].startswith(origin_url_blacklist) + origin = deepcopy(asset) if should_keep_origin else None + + asset["href"] = f"{asset_proxy_url}/{quote(asset_name)}" + asset.pop("storage:refs", None) + + if origin: + asset["alternate"] = {"origin": origin} + + # TODO: remove downloadLink asset after EODAG assets rework + has_parquet_asset = any(asset_name.endswith(".parquet") for asset_name in assets) + if (download_link := properties.get("eodag:download_link")) and not has_parquet_asset: + origin_href = download_link + proxied_href = f"{asset_proxy_url}/downloadLink" if asset_proxy_url else origin_href + mime_type = guess_file_type(origin_href) or "application/octet-stream" + + download_asset = {"title": "Download link", "href": proxied_href, "type": mime_type, "roles": ["data"]} + + if asset_proxy_url and keep_origin_url and not origin_href.startswith(origin_url_blacklist): + download_asset["alternate"] = { + "origin": { + "title": "Origin asset link", + "href": origin_href, + "type": mime_type, + }, } - if settings.keep_origin_url and not origin_href.startswith(tuple(settings.origin_url_blacklist)): - feature["assets"]["downloadLink"]["alternate"] = { - "origin": { - "title": "Origin asset link", - "href": origin_href, - # TODO: download link is not always a ZIP archive - "type": mime_type, - }, - } - - product_dict = product.as_dict() + assets["downloadLink"] = download_asset + # filter properties we do not want to expose - feature["properties"] = {k: v for k, v in product_dict["properties"].items() if not k.startswith("eodag:")} + feature["properties"] = {k: v for k, v in properties.items() if not k.startswith("eodag:")} feature["properties"].pop("qs", None) - feature["stac_extensions"] = product_dict["stac_extensions"] - - if extension_names and product.provider not in auto_order_whitelist: - if "CollectionOrderExtension" in extension_names and ( - not product.properties.get("eodag:order_link", False) - or feature["properties"].get("order:status", "") != "orderable" - ): - extension_names.remove("CollectionOrderExtension") - else: - extension_names = [] + link_extensions = list(enabled_extensions) + if "CollectionOrderExtension" in enabled_extensions: + is_orderable = ( + bool(properties.get("eodag:order_link")) and feature["properties"].get("order:status") == "orderable" + ) + if provider in auto_order_whitelist or not is_orderable: + link_extensions.remove("CollectionOrderExtension") # get request body for retrieve link (if product has to be ordered) - if "eodag:order_link" in product.properties: - retrieve_body = _get_retrieve_body_for_order(product) - else: - retrieve_body = {} + retrieve_body = ( + _get_retrieve_body_for_order(order_link) if (order_link := properties.get("eodag:order_link")) else {} + ) - if eodag_args := getattr(request.state, "eodag_args", None): - if provider := eodag_args.get("provider", None): - retrieve_body["federation:backends"] = [provider] + collection_obj = request.app.state.dag.collections_config.get(product.collection) + collection_title = (collection_obj.title if collection_obj else None) or collection feature["links"] = ItemLinks( collection_id=collection, - collection_title=collection_obj.title if collection_obj else collection, + collection_title=collection_title, item_id=quoted_id, retrieve_body=retrieve_body, request=request, - ).get_links(extensions=extension_names, extra_links=feature.get("links"), request_json=request_json) + ).get_links(extensions=link_extensions, extra_links=feature.get("links"), request_json=request_json) return feature diff --git a/uv.lock b/uv.lock index 202627d2..c9caf565 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.9" resolution-markers = [ "python_full_version >= '3.15'", @@ -4685,7 +4685,7 @@ wheels = [ [[package]] name = "stac-fastapi-eodag" -version = "0.2.0" +version = "0.4.0" source = { virtual = "." } dependencies = [ { name = "attr" },