Skip to content

Commit

Permalink
feat: implement new core method get_queryables (#917)
Browse files Browse the repository at this point in the history
Co-authored-by: LAMBARE Aubin <aubin.lambare@csgroup.eu>
Co-authored-by: Sylvain Brunato <sylvain.brunato@c-s.fr>
  • Loading branch information
3 people committed Dec 12, 2023
1 parent 92a97bf commit 79a17b6
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 11 deletions.
1 change: 1 addition & 0 deletions docs/api_reference/core.rst
Expand Up @@ -80,6 +80,7 @@ Misc

EODataAccessGateway.group_by_extent
EODataAccessGateway.guess_product_type
EODataAccessGateway.get_queryables

.. autoclass:: eodag.api.core.EODataAccessGateway
:members: set_preferred_provider, get_preferred_provider, update_providers_config, list_product_types,
Expand Down
58 changes: 57 additions & 1 deletion eodag/api/core.py
Expand Up @@ -22,7 +22,7 @@
import re
import shutil
from operator import itemgetter
from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Tuple, Union
from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Set, Tuple, Union

import geojson
import pkg_resources
Expand Down Expand Up @@ -71,6 +71,7 @@
NoMatchingProductType,
PluginImplementationError,
RequestError,
UnsupportedProductType,
UnsupportedProvider,
)
from eodag.utils.stac_reader import fetch_stac_items
Expand Down Expand Up @@ -1997,3 +1998,58 @@ def get_cruncher(self, name: str, **options: Any) -> Crunch:
plugin_conf = {"name": name}
plugin_conf.update({key.replace("-", "_"): val for key, val in options.items()})
return self._plugins_manager.get_crunch_plugin(name, **plugin_conf)

def get_queryables(
self, provider: Optional[str] = None, product_type: Optional[str] = None
) -> Set[str]:
"""Fetch the queryable properties for a given product type and/or provider.
:param product_type: (optional) The EODAG product type.
:type product_type: str
:param provider: (optional) The provider.
:type provider: str
:returns: A set containing the EODAG queryable properties.
:rtype: set
"""
default_queryables = {"productType", "start", "end", "geom", "locations", "id"}
if provider is None and product_type is None:
return default_queryables

plugins = self._plugins_manager.get_search_plugins(product_type, provider)

# dictionary of the queryable properties of the providers supporting the given product type
all_queryable_properties = dict()
for plugin in plugins:
if (
product_type
and product_type not in plugin.config.products.keys()
and provider is None
):
raise UnsupportedProductType(product_type)
elif product_type and product_type not in plugin.config.products.keys():
raise UnsupportedProductType(
f"{product_type} is not available for provider {provider}"
)

provider_queryables = set(default_queryables)

metadata_mapping = deepcopy(getattr(plugin.config, "metadata_mapping", {}))

# product_type-specific metadata-mapping
metadata_mapping.update(
getattr(plugin.config, "products", {})
.get(product_type, {})
.get("metadata_mapping", {})
)

for key, value in metadata_mapping.items():
if isinstance(value, list) and "TimeFromAscendingNode" not in key:
provider_queryables.add(key)

all_queryable_properties[plugin.provider] = provider_queryables

if provider is None:
# intersection of the queryables among the providers
return set.intersection(*all_queryable_properties.values())
else:
return all_queryable_properties[provider]
4 changes: 2 additions & 2 deletions eodag/rest/server.py
Expand Up @@ -467,8 +467,8 @@ def list_collection_queryables(
fetch_collection_queryable_properties(*conf_args)
)

for prop in provider_properties:
titled_name = re.sub(CAMEL_TO_SPACE_TITLED, " ", prop).title()
for prop in sorted(provider_properties):
titled_name = re.sub(CAMEL_TO_SPACE_TITLED, " ", prop.split(":")[-1]).title()
queryables[prop] = QueryableProperty(description=titled_name)

return queryables
Expand Down
23 changes: 15 additions & 8 deletions eodag/rest/utils.py
Expand Up @@ -1068,7 +1068,6 @@ class Queryables(BaseModel):
description="Datetime",
ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#/properties/datetime",
),
"ids": QueryableProperty(description="IDs"),
}
)
additional_properties: bool = Field(
Expand Down Expand Up @@ -1103,8 +1102,14 @@ def rename_to_stac_standard(key: str) -> str:
stac_config_properties: Dict[str, Any] = stac_config["item"]["properties"]

for stac_property, value in stac_config_properties.items():
if isinstance(value, list):
value = value[0]
if str(value).endswith(key):
return stac_property

if key in OSEO_METADATA_MAPPING:
return "oseo:" + key

return key


Expand All @@ -1121,15 +1126,17 @@ def fetch_collection_queryable_properties(
:rtype queryable_properties: set
"""
# Fetch the metadata mapping for collection-specific queryables
args = [collection_id, provider] if provider else [collection_id]
search_plugin = next(eodag_api._plugins_manager.get_search_plugins(*args))
search_plugin.config
mapping: Dict[str, Any] = dict(search_plugin.config.metadata_mapping)
kwargs = {"product_type": collection_id}
if provider is not None:
kwargs["provider"] = provider
eodag_queryable_properties = eodag_api.get_queryables(**kwargs)

# list of all the STAC standardized collection-specific queryables
queryable_properties: Set[str] = set()
for key, value in mapping.items():
if isinstance(value, list) and "TimeFromAscendingNode" not in key:
queryable_properties.add(rename_to_stac_standard(key))
for prop in eodag_queryable_properties:
# remove pure eodag properties
if prop not in ["start", "end", "geom", "locations", "id"]:
queryable_properties.add(rename_to_stac_standard(prop))
return queryable_properties


Expand Down
1 change: 1 addition & 0 deletions tests/context.py
Expand Up @@ -97,6 +97,7 @@
PluginImplementationError,
RequestError,
UnsupportedDatasetAddressScheme,
UnsupportedProductType,
UnsupportedProvider,
ValidationError,
STACOpenerError,
Expand Down
89 changes: 89 additions & 0 deletions tests/units/test_core.py
Expand Up @@ -43,6 +43,7 @@
ProviderConfig,
RequestError,
SearchResult,
UnsupportedProductType,
UnsupportedProvider,
get_geometry_from_various,
load_default_config,
Expand Down Expand Up @@ -980,6 +981,94 @@ def test_update_providers_config(self):
# run a 2nd time: check that it does not raise an error
self.dag.update_providers_config(new_config)

def test_get_queryables(self):
"""get_queryables must return queryables list adapted to provider and product-type"""
with self.assertRaises(UnsupportedProvider):
self.dag.get_queryables(provider="not_existing_provider")

with self.assertRaises(UnsupportedProductType):
self.dag.get_queryables(product_type="not_existing_product_type")

expected_result = {"productType", "start", "end", "geom", "locations", "id"}
queryables = self.dag.get_queryables()
self.assertSetEqual(queryables, expected_result)

expected_result = {
"start",
"end",
"geom",
"locations",
"productType",
"platformSerialIdentifier",
"instrument",
"processingLevel",
"resolution",
"organisationName",
"parentIdentifier",
"orbitNumber",
"orbitDirection",
"swathIdentifier",
"cloudCover",
"snowCover",
"sensorMode",
"polarizationMode",
"id",
"tileIdentifier",
"geometry",
}
queryables = self.dag.get_queryables(provider="peps")
self.assertSetEqual(queryables, expected_result)

expected_result = {
"start",
"end",
"geom",
"locations",
"productType",
"platformSerialIdentifier",
"instrument",
"processingLevel",
"resolution",
"organisationName",
"parentIdentifier",
"orbitNumber",
"orbitDirection",
"swathIdentifier",
"snowCover",
"sensorMode",
"polarizationMode",
"id",
"tileIdentifier",
"geometry",
}
queryables = self.dag.get_queryables(provider="peps", product_type="S1_SAR_GRD")
self.assertSetEqual(queryables, expected_result)

expected_result = {"productType", "start", "end", "geom", "locations", "id"}
queryables = self.dag.get_queryables(product_type="S2_MSI_L1C")
self.assertSetEqual(queryables, expected_result)

expected_result = {
"productType",
"start",
"end",
"geom",
"locations",
"geometry",
"platformSerialIdentifier",
"title",
"cloudCover",
"illuminationAzimuthAngle",
"illuminationZenithAngle",
"awsPath",
"productPath",
"id",
}
queryables = self.dag.get_queryables(
provider="aws_eos", product_type="S2_MSI_L1C"
)
self.assertSetEqual(queryables, expected_result)


class TestCoreConfWithEnvVar(TestCoreBase):
@classmethod
Expand Down

0 comments on commit 79a17b6

Please sign in to comment.