Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: external enhanced product types metadata #1008

Merged
merged 14 commits into from
May 24, 2024
Merged
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
1 change: 1 addition & 0 deletions eodag/api/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ def build_index(self) -> None:
missionStartDate=fields.ID,
missionEndDate=fields.ID,
keywords=fields.KEYWORD(analyzer=kw_analyzer),
stacCollection=fields.STORED,
)
self._product_types_index = create_in(index_dir, product_types_schema)
ix_writer = self._product_types_index.writer()
Expand Down
1 change: 1 addition & 0 deletions eodag/rest/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,7 @@ def crunch_products(
def eodag_api_init() -> None:
"""Init EODataAccessGateway server instance, pre-running all time consuming tasks"""
eodag_api.fetch_product_types_list()
StacCollection.fetch_external_stac_collections(eodag_api)

# pre-build search plugins
for provider in eodag_api.available_providers():
Expand Down
45 changes: 45 additions & 0 deletions eodag/rest/stac.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

from eodag.api.product.metadata_mapping import (
DEFAULT_METADATA_MAPPING,
NOT_AVAILABLE,
format_metadata,
get_metadata_path,
)
Expand All @@ -53,8 +54,11 @@
from eodag.utils.exceptions import (
NoMatchingProductType,
NotAvailableError,
RequestError,
TimeOutError,
ValidationError,
)
from eodag.utils.requests import fetch_json

if TYPE_CHECKING:
from eodag.api.core import EODataAccessGateway
Expand Down Expand Up @@ -610,6 +614,34 @@ class StacCollection(StacCommon):
:type root: str
"""

# External STAC collections
ext_stac_collections: Dict[str, Dict[str, Any]] = dict()

@classmethod
def fetch_external_stac_collections(cls, eodag_api: EODataAccessGateway) -> None:
"""Load external STAC collections

:param eodag_api: EODAG python API instance
:type eodag_api: :class:`eodag.api.core.EODataAccessGateway`
"""
list_product_types = eodag_api.list_product_types(fetch_providers=False)
for product_type in list_product_types:
ext_stac_collection_path = product_type.get("stacCollection")
if not ext_stac_collection_path:
continue
logger.info(f"Fetching external STAC collection for {product_type['ID']}")

try:
ext_stac_collection = fetch_json(ext_stac_collection_path)
except (RequestError, TimeOutError) as e:
logger.debug(e)
logger.warning(
f"Could not read remote external STAC collection from {ext_stac_collection_path}",
)
ext_stac_collection = {}

cls.ext_stac_collections[product_type["ID"]] = ext_stac_collection

def __init__(
self,
url: str,
Expand Down Expand Up @@ -717,6 +749,19 @@ def __get_collection_list(
"providers": providers_models,
},
)
# override EODAG's collection with the external collection
product_type_id = product_type.get("_id", None) or product_type["ID"]
ext_stac_collection = self.ext_stac_collections.get(product_type_id, {})
# merge "keywords" list
merged_keywords = product_type_collection.get("keywords", [])
if "keywords" in ext_stac_collection:
new_keywords = ext_stac_collection["keywords"]
for v in new_keywords:
if v != NOT_AVAILABLE and v not in merged_keywords:
merged_keywords.append(v)
product_type_collection.update(ext_stac_collection)
if merged_keywords:
product_type_collection["keywords"] = merged_keywords
# parse f-strings
format_args = deepcopy(self.stac_config)
format_args["collection"] = dict(
Expand Down
43 changes: 43 additions & 0 deletions tests/units/test_stac_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from pygeofilter.values import Geometry

import eodag.rest.utils.rfc3339 as rfc3339
from eodag.rest.stac import StacCollection
from eodag.rest.types.stac_search import SearchPostRequest
from eodag.rest.utils.cql_evaluate import EodagEvaluator
from eodag.utils.exceptions import ValidationError
Expand Down Expand Up @@ -54,6 +55,12 @@ def setUpClass(cls):

cls.rest_utils = rest_utils

import eodag.rest.core as rest_core

importlib.reload(rest_core)

cls.rest_core = rest_core

search_results_file = os.path.join(
TEST_RESOURCES_PATH, "eodag_search_result_peps.geojson"
)
Expand Down Expand Up @@ -246,6 +253,42 @@ def test_get_datetime(self):
self.assertEqual(dtstart, start)
self.assertEqual(dtend, end)

def test_fetch_external_stac_collections(self):
"""Load external STAC collections"""
external_json = """{
"new_field":"New Value",
"title":"A different title for Sentinel 2 MSI Level 1C",
"keywords":["New Keyword"]
}"""
product_type_conf = self.rest_core.eodag_api.product_types_config["S2_MSI_L1C"]
ext_stac_collection_path = "/path/to/external/stac/collections/S2_MSI_L1C.json"
product_type_conf["stacCollection"] = ext_stac_collection_path

with mock.patch(
"eodag.rest.stac.fetch_json",
autospec=True,
return_value=json.loads(external_json),
) as mock_fetch_json:
# Check if the returned STAC collection contains updated data
StacCollection.fetch_external_stac_collections(self.rest_core.eodag_api)
stac_coll = self.rest_core.get_stac_collection_by_id(
url="", root="", collection_id="S2_MSI_L1C"
)
mock_fetch_json.assert_called_with(ext_stac_collection_path)
# New field
self.assertIn("new_field", stac_coll)
# Merge keywords
self.assertListEqual(
["MSI", "SENTINEL2", "S2A,S2B", "L1", "OPTICAL", "New Keyword"],
stac_coll["keywords"],
)
# Override existing fields
self.assertEqual(
"A different title for Sentinel 2 MSI Level 1C", stac_coll["title"]
)
# Restore previous state
StacCollection.ext_stac_collections.clear()


class TestEodagCql2jsonEvaluator(unittest.TestCase):
def setUp(self):
Expand Down
Loading