eodag 3.1.0b1__py3-none-any.whl → 3.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. eodag/api/core.py +69 -63
  2. eodag/api/product/_assets.py +49 -13
  3. eodag/api/product/_product.py +41 -30
  4. eodag/api/product/drivers/__init__.py +81 -4
  5. eodag/api/product/drivers/base.py +65 -4
  6. eodag/api/product/drivers/generic.py +65 -0
  7. eodag/api/product/drivers/sentinel1.py +97 -0
  8. eodag/api/product/drivers/sentinel2.py +95 -0
  9. eodag/api/product/metadata_mapping.py +85 -79
  10. eodag/api/search_result.py +13 -23
  11. eodag/cli.py +4 -4
  12. eodag/config.py +77 -80
  13. eodag/plugins/apis/base.py +1 -1
  14. eodag/plugins/apis/ecmwf.py +12 -15
  15. eodag/plugins/apis/usgs.py +12 -11
  16. eodag/plugins/authentication/aws_auth.py +16 -13
  17. eodag/plugins/authentication/base.py +5 -3
  18. eodag/plugins/authentication/header.py +3 -3
  19. eodag/plugins/authentication/keycloak.py +4 -4
  20. eodag/plugins/authentication/oauth.py +7 -3
  21. eodag/plugins/authentication/openid_connect.py +20 -14
  22. eodag/plugins/authentication/sas_auth.py +4 -4
  23. eodag/plugins/authentication/token.py +7 -7
  24. eodag/plugins/authentication/token_exchange.py +1 -1
  25. eodag/plugins/base.py +4 -4
  26. eodag/plugins/crunch/base.py +4 -4
  27. eodag/plugins/crunch/filter_date.py +4 -4
  28. eodag/plugins/crunch/filter_latest_intersect.py +6 -6
  29. eodag/plugins/crunch/filter_latest_tpl_name.py +7 -7
  30. eodag/plugins/crunch/filter_overlap.py +4 -4
  31. eodag/plugins/crunch/filter_property.py +4 -4
  32. eodag/plugins/download/aws.py +137 -77
  33. eodag/plugins/download/base.py +8 -17
  34. eodag/plugins/download/creodias_s3.py +2 -2
  35. eodag/plugins/download/http.py +30 -32
  36. eodag/plugins/download/s3rest.py +5 -4
  37. eodag/plugins/manager.py +10 -20
  38. eodag/plugins/search/__init__.py +6 -5
  39. eodag/plugins/search/base.py +38 -42
  40. eodag/plugins/search/build_search_result.py +286 -336
  41. eodag/plugins/search/cop_marine.py +22 -12
  42. eodag/plugins/search/creodias_s3.py +8 -78
  43. eodag/plugins/search/csw.py +11 -11
  44. eodag/plugins/search/data_request_search.py +19 -18
  45. eodag/plugins/search/qssearch.py +84 -151
  46. eodag/plugins/search/stac_list_assets.py +85 -0
  47. eodag/plugins/search/static_stac_search.py +4 -4
  48. eodag/resources/ext_product_types.json +1 -1
  49. eodag/resources/product_types.yml +848 -398
  50. eodag/resources/providers.yml +1038 -1115
  51. eodag/resources/stac_api.yml +2 -2
  52. eodag/resources/user_conf_template.yml +10 -9
  53. eodag/rest/cache.py +2 -2
  54. eodag/rest/config.py +3 -3
  55. eodag/rest/core.py +24 -24
  56. eodag/rest/errors.py +5 -5
  57. eodag/rest/server.py +3 -11
  58. eodag/rest/stac.py +41 -38
  59. eodag/rest/types/collections_search.py +3 -3
  60. eodag/rest/types/eodag_search.py +23 -23
  61. eodag/rest/types/queryables.py +40 -28
  62. eodag/rest/types/stac_search.py +15 -25
  63. eodag/rest/utils/__init__.py +11 -21
  64. eodag/rest/utils/cql_evaluate.py +6 -6
  65. eodag/rest/utils/rfc3339.py +2 -2
  66. eodag/types/__init__.py +97 -29
  67. eodag/types/bbox.py +2 -2
  68. eodag/types/download_args.py +2 -2
  69. eodag/types/queryables.py +5 -2
  70. eodag/types/search_args.py +4 -4
  71. eodag/types/whoosh.py +1 -3
  72. eodag/utils/__init__.py +82 -41
  73. eodag/utils/exceptions.py +2 -2
  74. eodag/utils/import_system.py +2 -2
  75. eodag/utils/requests.py +2 -2
  76. eodag/utils/rest.py +2 -2
  77. eodag/utils/s3.py +231 -0
  78. eodag/utils/stac_reader.py +10 -10
  79. {eodag-3.1.0b1.dist-info → eodag-3.2.0.dist-info}/METADATA +12 -10
  80. eodag-3.2.0.dist-info/RECORD +113 -0
  81. {eodag-3.1.0b1.dist-info → eodag-3.2.0.dist-info}/WHEEL +1 -1
  82. {eodag-3.1.0b1.dist-info → eodag-3.2.0.dist-info}/entry_points.txt +1 -0
  83. eodag-3.1.0b1.dist-info/RECORD +0 -108
  84. {eodag-3.1.0b1.dist-info → eodag-3.2.0.dist-info/licenses}/LICENSE +0 -0
  85. {eodag-3.1.0b1.dist-info → eodag-3.2.0.dist-info}/top_level.txt +0 -0
eodag/api/core.py CHANGED
@@ -23,13 +23,13 @@ import os
23
23
  import re
24
24
  import shutil
25
25
  import tempfile
26
+ from importlib.metadata import version
27
+ from importlib.resources import files as res_files
26
28
  from operator import itemgetter
27
- from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Tuple, Union
29
+ from typing import TYPE_CHECKING, Any, Iterator, Optional, Union
28
30
 
29
31
  import geojson
30
- import pkg_resources
31
32
  import yaml.parser
32
- from pkg_resources import resource_filename
33
33
  from whoosh import analysis, fields
34
34
  from whoosh.fields import Schema
35
35
  from whoosh.index import exists_in, open_dir
@@ -119,8 +119,8 @@ class EODataAccessGateway:
119
119
  user_conf_file_path: Optional[str] = None,
120
120
  locations_conf_path: Optional[str] = None,
121
121
  ) -> None:
122
- product_types_config_path = resource_filename(
123
- "eodag", os.path.join("resources/", "product_types.yml")
122
+ product_types_config_path = os.getenv("EODAG_PRODUCT_TYPES_CFG_FILE") or str(
123
+ res_files("eodag") / "resources" / "product_types.yml"
124
124
  )
125
125
  self.product_types_config = SimpleYamlProxyConfig(product_types_config_path)
126
126
  self.product_types_config_md5 = obj_md5sum(self.product_types_config.source)
@@ -161,8 +161,8 @@ class EODataAccessGateway:
161
161
  user_conf_file_path = standard_configuration_path
162
162
  if not os.path.isfile(standard_configuration_path):
163
163
  shutil.copy(
164
- resource_filename(
165
- "eodag", os.path.join("resources", "user_conf_template.yml")
164
+ str(
165
+ res_files("eodag") / "resources" / "user_conf_template.yml"
166
166
  ),
167
167
  standard_configuration_path,
168
168
  )
@@ -185,7 +185,7 @@ class EODataAccessGateway:
185
185
  self._plugins_manager.rebuild(self.providers_config)
186
186
 
187
187
  # store pruned providers configs
188
- self._pruned_providers_config: Dict[str, Any] = {}
188
+ self._pruned_providers_config: dict[str, Any] = {}
189
189
  # filter out providers needing auth that have no credentials set
190
190
  self._prune_providers_list()
191
191
 
@@ -203,9 +203,8 @@ class EODataAccessGateway:
203
203
  locations_conf_path = os.path.join(self.conf_dir, "locations.yml")
204
204
  if not os.path.isfile(locations_conf_path):
205
205
  # copy locations conf file and replace path example
206
- locations_conf_template = resource_filename(
207
- "eodag",
208
- os.path.join("resources", "locations_conf_template.yml"),
206
+ locations_conf_template = str(
207
+ res_files("eodag") / "resources" / "locations_conf_template.yml"
209
208
  )
210
209
  with (
211
210
  open(locations_conf_template) as infile,
@@ -222,14 +221,14 @@ class EODataAccessGateway:
222
221
  outfile.write(line)
223
222
  # copy sample shapefile dir
224
223
  shutil.copytree(
225
- resource_filename("eodag", os.path.join("resources", "shp")),
224
+ str(res_files("eodag") / "resources" / "shp"),
226
225
  os.path.join(self.conf_dir, "shp"),
227
226
  )
228
227
  self.set_locations_conf(locations_conf_path)
229
228
 
230
229
  def get_version(self) -> str:
231
230
  """Get eodag package version"""
232
- return pkg_resources.get_distribution("eodag").version
231
+ return version("eodag")
233
232
 
234
233
  def build_index(self) -> None:
235
234
  """Build a `Whoosh <https://whoosh.readthedocs.io/en/latest/index.html>`_
@@ -335,7 +334,7 @@ class EODataAccessGateway:
335
334
  new_priority = max_priority + 1
336
335
  self._plugins_manager.set_priority(provider, new_priority)
337
336
 
338
- def get_preferred_provider(self) -> Tuple[str, int]:
337
+ def get_preferred_provider(self) -> tuple[str, int]:
339
338
  """Get the provider currently set as the preferred one for searching
340
339
  products, along with its priority.
341
340
 
@@ -351,7 +350,7 @@ class EODataAccessGateway:
351
350
  def update_providers_config(
352
351
  self,
353
352
  yaml_conf: Optional[str] = None,
354
- dict_conf: Optional[Dict[str, Any]] = None,
353
+ dict_conf: Optional[dict[str, Any]] = None,
355
354
  ) -> None:
356
355
  """Update providers configuration with given input.
357
356
  Can be used to add a provider to existing configuration or update
@@ -397,12 +396,12 @@ class EODataAccessGateway:
397
396
  name: str,
398
397
  url: Optional[str] = None,
399
398
  priority: Optional[int] = None,
400
- search: Dict[str, Any] = {"type": "StacSearch"},
401
- products: Dict[str, Any] = {
399
+ search: dict[str, Any] = {"type": "StacSearch"},
400
+ products: dict[str, Any] = {
402
401
  GENERIC_PRODUCT_TYPE: {"productType": "{productType}"}
403
402
  },
404
- download: Dict[str, Any] = {"type": "HTTPDownload", "auth_error_code": 401},
405
- **kwargs: Dict[str, Any],
403
+ download: dict[str, Any] = {"type": "HTTPDownload", "auth_error_code": 401},
404
+ **kwargs: dict[str, Any],
406
405
  ):
407
406
  """Adds a new provider.
408
407
 
@@ -421,7 +420,7 @@ class EODataAccessGateway:
421
420
  :param download: Download :class:`~eodag.config.PluginConfig` mapping
422
421
  :param kwargs: Additional :class:`~eodag.config.ProviderConfig` mapping
423
422
  """
424
- conf_dict: Dict[str, Any] = {
423
+ conf_dict: dict[str, Any] = {
425
424
  name: {
426
425
  "url": url,
427
426
  "search": {"type": "StacSearch", **search},
@@ -565,7 +564,7 @@ class EODataAccessGateway:
565
564
  main_locations_config = locations_config[main_key]
566
565
 
567
566
  logger.info("Locations configuration loaded from %s" % locations_conf_path)
568
- self.locations_config: List[Dict[str, Any]] = main_locations_config
567
+ self.locations_config: list[dict[str, Any]] = main_locations_config
569
568
  else:
570
569
  logger.info(
571
570
  "Could not load locations configuration from %s" % locations_conf_path
@@ -574,7 +573,7 @@ class EODataAccessGateway:
574
573
 
575
574
  def list_product_types(
576
575
  self, provider: Optional[str] = None, fetch_providers: bool = True
577
- ) -> List[Dict[str, Any]]:
576
+ ) -> list[dict[str, Any]]:
578
577
  """Lists supported product types.
579
578
 
580
579
  :param provider: (optional) The name of a provider that must support the product
@@ -588,7 +587,7 @@ class EODataAccessGateway:
588
587
  # First, update product types list if possible
589
588
  self.fetch_product_types_list(provider=provider)
590
589
 
591
- product_types: List[Dict[str, Any]] = []
590
+ product_types: list[dict[str, Any]] = []
592
591
 
593
592
  providers_configs = (
594
593
  list(self.providers_config.values())
@@ -644,7 +643,7 @@ class EODataAccessGateway:
644
643
  providers_to_fetch = [provider]
645
644
 
646
645
  # providers discovery confs that are fetchable
647
- providers_discovery_configs_fetchable: Dict[str, Any] = {}
646
+ providers_discovery_configs_fetchable: dict[str, Any] = {}
648
647
  # check if any provider has not already been fetched for product types
649
648
  already_fetched = True
650
649
  for provider_to_fetch in providers_to_fetch:
@@ -767,7 +766,7 @@ class EODataAccessGateway:
767
766
 
768
767
  def discover_product_types(
769
768
  self, provider: Optional[str] = None
770
- ) -> Optional[Dict[str, Any]]:
769
+ ) -> Optional[dict[str, Any]]:
771
770
  """Fetch providers for product types
772
771
 
773
772
  :param provider: The name of a provider or provider-group to fetch. Defaults to
@@ -787,7 +786,7 @@ class EODataAccessGateway:
787
786
  raise UnsupportedProvider(
788
787
  f"The requested provider is not (yet) supported: {provider}"
789
788
  )
790
- ext_product_types_conf: Dict[str, Any] = {}
789
+ ext_product_types_conf: dict[str, Any] = {}
791
790
  providers_to_fetch = [
792
791
  p
793
792
  for p in (
@@ -800,7 +799,7 @@ class EODataAccessGateway:
800
799
  else self.available_providers()
801
800
  )
802
801
  ]
803
- kwargs: Dict[str, Any] = {}
802
+ kwargs: dict[str, Any] = {}
804
803
  for provider in providers_to_fetch:
805
804
  if hasattr(self.providers_config[provider], "search"):
806
805
  search_plugin_config = self.providers_config[provider].search
@@ -841,7 +840,7 @@ class EODataAccessGateway:
841
840
  return sort_dict(ext_product_types_conf)
842
841
 
843
842
  def update_product_types_list(
844
- self, ext_product_types_conf: Dict[str, Optional[Dict[str, Dict[str, Any]]]]
843
+ self, ext_product_types_conf: dict[str, Optional[dict[str, dict[str, Any]]]]
845
844
  ) -> None:
846
845
  """Update eodag product types list
847
846
 
@@ -869,7 +868,7 @@ class EODataAccessGateway:
869
868
  provider,
870
869
  )
871
870
  continue
872
- new_product_types: List[str] = []
871
+ new_product_types: list[str] = []
873
872
  for (
874
873
  new_product_type,
875
874
  new_product_type_conf,
@@ -932,7 +931,7 @@ class EODataAccessGateway:
932
931
 
933
932
  def available_providers(
934
933
  self, product_type: Optional[str] = None, by_group: bool = False
935
- ) -> List[str]:
934
+ ) -> list[str]:
936
935
  """Gives the sorted list of the available providers or groups
937
936
 
938
937
  The providers or groups are sorted first by their priority level in descending order,
@@ -959,7 +958,7 @@ class EODataAccessGateway:
959
958
 
960
959
  # If by_group is True, keep only the highest priority for each group
961
960
  if by_group:
962
- group_priority: Dict[str, int] = {}
961
+ group_priority: dict[str, int] = {}
963
962
  for name, priority in providers:
964
963
  if name not in group_priority or priority > group_priority[name]:
965
964
  group_priority[name] = priority
@@ -1026,7 +1025,7 @@ class EODataAccessGateway:
1026
1025
  missionStartDate: Optional[str] = None,
1027
1026
  missionEndDate: Optional[str] = None,
1028
1027
  **kwargs: Any,
1029
- ) -> List[str]:
1028
+ ) -> list[str]:
1030
1029
  """
1031
1030
  Find EODAG product type IDs that best match a set of search parameters.
1032
1031
 
@@ -1084,7 +1083,7 @@ class EODataAccessGateway:
1084
1083
  query = p.parse(text)
1085
1084
  results = searcher.search(query, limit=None)
1086
1085
 
1087
- guesses: List[Dict[str, str]] = [dict(r) for r in results or []]
1086
+ guesses: list[dict[str, str]] = [dict(r) for r in results or []]
1088
1087
 
1089
1088
  # datetime filtering
1090
1089
  if missionStartDate or missionEndDate:
@@ -1125,8 +1124,8 @@ class EODataAccessGateway:
1125
1124
  raise_errors: bool = False,
1126
1125
  start: Optional[str] = None,
1127
1126
  end: Optional[str] = None,
1128
- geom: Optional[Union[str, Dict[str, float], BaseGeometry]] = None,
1129
- locations: Optional[Dict[str, str]] = None,
1127
+ geom: Optional[Union[str, dict[str, float], BaseGeometry]] = None,
1128
+ locations: Optional[dict[str, str]] = None,
1130
1129
  provider: Optional[str] = None,
1131
1130
  count: bool = False,
1132
1131
  **kwargs: Any,
@@ -1205,7 +1204,7 @@ class EODataAccessGateway:
1205
1204
  items_per_page=items_per_page,
1206
1205
  )
1207
1206
 
1208
- errors: List[Tuple[str, Exception]] = []
1207
+ errors: list[tuple[str, Exception]] = []
1209
1208
  # Loop over available providers and return the first non-empty results
1210
1209
  for i, search_plugin in enumerate(search_plugins):
1211
1210
  search_plugin.clear()
@@ -1234,8 +1233,8 @@ class EODataAccessGateway:
1234
1233
  items_per_page: int = DEFAULT_ITEMS_PER_PAGE,
1235
1234
  start: Optional[str] = None,
1236
1235
  end: Optional[str] = None,
1237
- geom: Optional[Union[str, Dict[str, float], BaseGeometry]] = None,
1238
- locations: Optional[Dict[str, str]] = None,
1236
+ geom: Optional[Union[str, dict[str, float], BaseGeometry]] = None,
1237
+ locations: Optional[dict[str, str]] = None,
1239
1238
  **kwargs: Any,
1240
1239
  ) -> Iterator[SearchResult]:
1241
1240
  """Iterate over the pages of a products search.
@@ -1411,8 +1410,8 @@ class EODataAccessGateway:
1411
1410
  items_per_page: Optional[int] = None,
1412
1411
  start: Optional[str] = None,
1413
1412
  end: Optional[str] = None,
1414
- geom: Optional[Union[str, Dict[str, float], BaseGeometry]] = None,
1415
- locations: Optional[Dict[str, str]] = None,
1413
+ geom: Optional[Union[str, dict[str, float], BaseGeometry]] = None,
1414
+ locations: Optional[dict[str, str]] = None,
1416
1415
  **kwargs: Any,
1417
1416
  ) -> SearchResult:
1418
1417
  """Search and return all the products matching the search criteria.
@@ -1633,7 +1632,7 @@ class EODataAccessGateway:
1633
1632
  if not getattr(plugin.config, "discover_product_types", {}).get("fetch_url"):
1634
1633
  return None
1635
1634
 
1636
- kwargs: Dict[str, Any] = {"productType": product_type}
1635
+ kwargs: dict[str, Any] = {"productType": product_type}
1637
1636
 
1638
1637
  # append auth if needed
1639
1638
  if getattr(plugin.config, "need_auth", False):
@@ -1651,11 +1650,11 @@ class EODataAccessGateway:
1651
1650
  self,
1652
1651
  start: Optional[str] = None,
1653
1652
  end: Optional[str] = None,
1654
- geom: Optional[Union[str, Dict[str, float], BaseGeometry]] = None,
1655
- locations: Optional[Dict[str, str]] = None,
1653
+ geom: Optional[Union[str, dict[str, float], BaseGeometry]] = None,
1654
+ locations: Optional[dict[str, str]] = None,
1656
1655
  provider: Optional[str] = None,
1657
1656
  **kwargs: Any,
1658
- ) -> Tuple[List[Union[Search, Api]], Dict[str, Any]]:
1657
+ ) -> tuple[list[Union[Search, Api]], dict[str, Any]]:
1659
1658
  """Internal method to prepare the search kwargs and get the search plugins.
1660
1659
 
1661
1660
  Product query:
@@ -1763,7 +1762,7 @@ class EODataAccessGateway:
1763
1762
 
1764
1763
  preferred_provider = self.get_preferred_provider()[0]
1765
1764
 
1766
- search_plugins: List[Union[Search, Api]] = []
1765
+ search_plugins: list[Union[Search, Api]] = []
1767
1766
  for plugin in self._plugins_manager.get_search_plugins(
1768
1767
  product_type=product_type, provider=provider
1769
1768
  ):
@@ -1833,10 +1832,10 @@ class EODataAccessGateway:
1833
1832
  max_items_per_page,
1834
1833
  )
1835
1834
 
1836
- results: List[EOProduct] = []
1835
+ results: list[EOProduct] = []
1837
1836
  total_results: Optional[int] = 0 if count else None
1838
1837
 
1839
- errors: List[Tuple[str, Exception]] = []
1838
+ errors: list[tuple[str, Exception]] = []
1840
1839
 
1841
1840
  try:
1842
1841
  prep = PreparedSearch(count=count)
@@ -1984,7 +1983,7 @@ class EODataAccessGateway:
1984
1983
  return results
1985
1984
 
1986
1985
  @staticmethod
1987
- def group_by_extent(searches: List[SearchResult]) -> List[SearchResult]:
1986
+ def group_by_extent(searches: list[SearchResult]) -> list[SearchResult]:
1988
1987
  """Combines multiple SearchResults and return a list of SearchResults grouped
1989
1988
  by extent (i.e. bounding box).
1990
1989
 
@@ -1993,7 +1992,7 @@ class EODataAccessGateway:
1993
1992
  """
1994
1993
  # Dict with extents as keys, each extent being defined by a str
1995
1994
  # "{minx}{miny}{maxx}{maxy}" (each float rounded to 2 dec).
1996
- products_grouped_by_extent: Dict[str, Any] = {}
1995
+ products_grouped_by_extent: dict[str, Any] = {}
1997
1996
 
1998
1997
  for search in searches:
1999
1998
  for product in search:
@@ -2015,7 +2014,7 @@ class EODataAccessGateway:
2015
2014
  wait: float = DEFAULT_DOWNLOAD_WAIT,
2016
2015
  timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
2017
2016
  **kwargs: Unpack[DownloadConf],
2018
- ) -> List[str]:
2017
+ ) -> list[str]:
2019
2018
  """Download all products resulting from a search.
2020
2019
 
2021
2020
  :param search_result: A collection of EO products resulting from a search
@@ -2273,7 +2272,7 @@ class EODataAccessGateway:
2273
2272
  properties, associating parameters to their annotated type, and a additional_properties attribute
2274
2273
  """
2275
2274
  # only fetch providers if product type is not found
2276
- available_product_types = [
2275
+ available_product_types: list[str] = [
2277
2276
  pt["ID"]
2278
2277
  for pt in self.list_product_types(provider=provider, fetch_providers=False)
2279
2278
  ]
@@ -2304,13 +2303,13 @@ class EODataAccessGateway:
2304
2303
  **model_fields_to_annotated(CommonQueryables.model_fields),
2305
2304
  )
2306
2305
 
2307
- queryables: QueryablesDict = QueryablesDict(
2308
- additional_properties=True, additional_information="", **{}
2309
- )
2306
+ additional_properties = False
2307
+ additional_information = []
2308
+ queryable_properties: dict[str, Any] = {}
2310
2309
 
2311
2310
  for plugin in self._plugins_manager.get_search_plugins(product_type, provider):
2312
2311
  # attach product type config
2313
- product_type_configs = {}
2312
+ product_type_configs: dict[str, Any] = {}
2314
2313
  if product_type:
2315
2314
  self._attach_product_type_config(plugin, product_type)
2316
2315
  product_type_configs[product_type] = plugin.config.product_type_config
@@ -2330,6 +2329,7 @@ class EODataAccessGateway:
2330
2329
  "queryables from provider %s could not be fetched due to an authentication error",
2331
2330
  plugin.provider,
2332
2331
  )
2332
+
2333
2333
  plugin_queryables = plugin.list_queryables(
2334
2334
  kwargs,
2335
2335
  available_product_types,
@@ -2337,17 +2337,23 @@ class EODataAccessGateway:
2337
2337
  product_type,
2338
2338
  pt_alias,
2339
2339
  )
2340
- queryables.update(plugin_queryables)
2341
- if not plugin_queryables.additional_properties:
2342
- queryables.additional_properties = False
2340
+
2343
2341
  if plugin_queryables.additional_information:
2344
- queryables.additional_information += (
2345
- f"{provider}: {plugin_queryables.additional_information}"
2342
+ additional_information.append(
2343
+ f"{plugin.provider}: {plugin_queryables.additional_information}"
2346
2344
  )
2345
+ queryable_properties = {**plugin_queryables, **queryable_properties}
2346
+ additional_properties = (
2347
+ additional_properties or plugin_queryables.additional_properties
2348
+ )
2347
2349
 
2348
- return queryables
2350
+ return QueryablesDict(
2351
+ additional_properties=additional_properties,
2352
+ additional_information=" | ".join(additional_information),
2353
+ **queryable_properties,
2354
+ )
2349
2355
 
2350
- def available_sortables(self) -> Dict[str, Optional[ProviderSortables]]:
2356
+ def available_sortables(self) -> dict[str, Optional[ProviderSortables]]:
2351
2357
  """For each provider, gives its available sortable parameter(s) and its maximum
2352
2358
  number of them if it supports the sorting feature, otherwise gives None.
2353
2359
 
@@ -2355,7 +2361,7 @@ class EODataAccessGateway:
2355
2361
  its (their) maximum number as value(s).
2356
2362
  :raises: :class:`~eodag.utils.exceptions.UnsupportedProvider`
2357
2363
  """
2358
- sortables: Dict[str, Optional[ProviderSortables]] = {}
2364
+ sortables: dict[str, Optional[ProviderSortables]] = {}
2359
2365
  provider_search_plugins = self._plugins_manager.get_search_plugins()
2360
2366
  for provider_search_plugin in provider_search_plugins:
2361
2367
  provider = provider_search_plugin.provider
@@ -19,7 +19,7 @@ from __future__ import annotations
19
19
 
20
20
  import re
21
21
  from collections import UserDict
22
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
22
+ from typing import TYPE_CHECKING, Any, Optional
23
23
 
24
24
  from eodag.utils.exceptions import NotAvailableError
25
25
  from eodag.utils.repr import dict_to_html_table
@@ -31,12 +31,27 @@ if TYPE_CHECKING:
31
31
 
32
32
 
33
33
  class AssetsDict(UserDict):
34
- """A UserDict object listing assets contained in a
35
- :class:`~eodag.api.product._product.EOProduct` resulting from a search.
34
+ """A UserDict object which values are :class:`~eodag.api.product._assets.Asset`
35
+ contained in a :class:`~eodag.api.product._product.EOProduct` resulting from a
36
+ search.
36
37
 
37
38
  :param product: Product resulting from a search
38
39
  :param args: (optional) Arguments used to init the dictionary
39
40
  :param kwargs: (optional) Additional named-arguments used to init the dictionary
41
+
42
+ Example
43
+ -------
44
+
45
+ >>> from eodag.api.product import EOProduct
46
+ >>> product = EOProduct(
47
+ ... provider="foo",
48
+ ... properties={"id": "bar", "geometry": "POINT (0 0)"}
49
+ ... )
50
+ >>> type(product.assets)
51
+ <class 'eodag.api.product._assets.AssetsDict'>
52
+ >>> product.assets.update({"foo": {"href": "http://somewhere/something"}})
53
+ >>> product.assets
54
+ {'foo': {'href': 'http://somewhere/something'}}
40
55
  """
41
56
 
42
57
  product: EOProduct
@@ -45,10 +60,10 @@ class AssetsDict(UserDict):
45
60
  self.product = product
46
61
  super(AssetsDict, self).__init__(*args, **kwargs)
47
62
 
48
- def __setitem__(self, key: str, value: Dict[str, Any]) -> None:
63
+ def __setitem__(self, key: str, value: dict[str, Any]) -> None:
49
64
  super().__setitem__(key, Asset(self.product, key, value))
50
65
 
51
- def as_dict(self) -> Dict[str, Any]:
66
+ def as_dict(self) -> dict[str, Any]:
52
67
  """Builds a representation of AssetsDict to enable its serialization
53
68
 
54
69
  :returns: The representation of a :class:`~eodag.api.product._assets.AssetsDict`
@@ -56,22 +71,29 @@ class AssetsDict(UserDict):
56
71
  """
57
72
  return {k: v.as_dict() for k, v in self.data.items()}
58
73
 
59
- def get_values(self, asset_filter: str = "") -> List[Asset]:
74
+ def get_values(self, asset_filter: str = "", regex=True) -> list[Asset]:
60
75
  """
61
76
  retrieves the assets matching the given filter
62
77
 
63
78
  :param asset_filter: regex filter with which the assets should be matched
79
+ :param regex: Uses regex to match the asset key or simply compare strings
64
80
  :return: list of assets
65
81
  """
66
82
  if asset_filter:
67
- filter_regex = re.compile(asset_filter)
68
- assets_keys = list(self.keys())
69
- assets_keys = list(filter(filter_regex.fullmatch, assets_keys))
83
+ if regex:
84
+ filter_regex = re.compile(asset_filter)
85
+ assets_keys = list(self.keys())
86
+ assets_keys = list(filter(filter_regex.fullmatch, assets_keys))
87
+ else:
88
+ assets_keys = [a for a in self.keys() if a == asset_filter]
70
89
  filtered_assets = {}
71
90
  if len(assets_keys) > 0:
72
91
  filtered_assets = {a_key: self.get(a_key) for a_key in assets_keys}
73
92
  assets_values = [a for a in filtered_assets.values() if a and "href" in a]
74
- if not assets_values:
93
+ if not assets_values and regex:
94
+ # retry without regex
95
+ return self.get_values(asset_filter, regex=False)
96
+ elif not assets_values:
75
97
  raise NotAvailableError(
76
98
  rf"No asset key matching re.fullmatch(r'{asset_filter}') was found in {self.product}"
77
99
  )
@@ -119,13 +141,27 @@ class AssetsDict(UserDict):
119
141
 
120
142
 
121
143
  class Asset(UserDict):
122
- """A UserDict object containg one of the assets of a
123
- :class:`~eodag.api.product._product.EOProduct` resulting from a search.
144
+ """A UserDict object containg one of the
145
+ :attr:`~eodag.api.product._product.EOProduct.assets` resulting from a search.
124
146
 
125
147
  :param product: Product resulting from a search
126
148
  :param key: asset key
127
149
  :param args: (optional) Arguments used to init the dictionary
128
150
  :param kwargs: (optional) Additional named-arguments used to init the dictionary
151
+
152
+ Example
153
+ -------
154
+
155
+ >>> from eodag.api.product import EOProduct
156
+ >>> product = EOProduct(
157
+ ... provider="foo",
158
+ ... properties={"id": "bar", "geometry": "POINT (0 0)"}
159
+ ... )
160
+ >>> product.assets.update({"foo": {"href": "http://somewhere/something"}})
161
+ >>> type(product.assets["foo"])
162
+ <class 'eodag.api.product._assets.Asset'>
163
+ >>> product.assets["foo"]
164
+ {'href': 'http://somewhere/something'}
129
165
  """
130
166
 
131
167
  product: EOProduct
@@ -138,7 +174,7 @@ class Asset(UserDict):
138
174
  self.key = key
139
175
  super(Asset, self).__init__(*args, **kwargs)
140
176
 
141
- def as_dict(self) -> Dict[str, Any]:
177
+ def as_dict(self) -> dict[str, Any]:
142
178
  """Builds a representation of Asset to enable its serialization
143
179
 
144
180
  :returns: The representation of a :class:`~eodag.api.product._assets.Asset` as a