eodag 3.5.1__py3-none-any.whl → 3.7.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.
@@ -25,6 +25,7 @@ from pydantic.fields import Field, FieldInfo
25
25
 
26
26
  from eodag.api.product.metadata_mapping import (
27
27
  DEFAULT_METADATA_MAPPING,
28
+ NOT_AVAILABLE,
28
29
  NOT_MAPPED,
29
30
  mtd_cfg_as_conversion_and_querypath,
30
31
  )
@@ -38,6 +39,7 @@ from eodag.utils import (
38
39
  copy_deepcopy,
39
40
  deepcopy,
40
41
  format_dict_items,
42
+ string_to_jsonpath,
41
43
  update_nested_dict,
42
44
  )
43
45
  from eodag.utils.exceptions import ValidationError
@@ -158,10 +160,6 @@ class Search(PluginTopic):
158
160
  :returns: The product type definition parameters
159
161
  """
160
162
  if product_type in self.config.products.keys():
161
- logger.debug(
162
- "Getting provider product type definition parameters for %s",
163
- product_type,
164
- )
165
163
  return self.config.products[product_type]
166
164
  elif GENERIC_PRODUCT_TYPE in self.config.products.keys():
167
165
  logger.debug(
@@ -439,3 +437,35 @@ class Search(PluginTopic):
439
437
  ):
440
438
  queryables[k] = v
441
439
  return queryables
440
+
441
+ def get_assets_from_mapping(self, provider_item: dict[str, Any]) -> dict[str, Any]:
442
+ """
443
+ Create assets based on the assets_mapping in the provider's config
444
+ and an item returned by the provider
445
+
446
+ :param provider_item: dict of item properties returned by the provider
447
+ :returns: dict containing the asset metadata
448
+ """
449
+ assets_mapping = getattr(self.config, "assets_mapping", None)
450
+ if not assets_mapping:
451
+ return {}
452
+ assets = {}
453
+ for key, values in assets_mapping.items():
454
+ asset_href = values.get("href")
455
+ if not asset_href:
456
+ logger.warning(
457
+ "asset mapping %s skipped because no href is available", key
458
+ )
459
+ continue
460
+ json_url_path = string_to_jsonpath(asset_href)
461
+ if isinstance(json_url_path, str):
462
+ url_path = json_url_path
463
+ else:
464
+ url_match = json_url_path.find(provider_item)
465
+ if len(url_match) == 1:
466
+ url_path = url_match[0].value
467
+ else:
468
+ url_path = NOT_AVAILABLE
469
+ assets[key] = deepcopy(values)
470
+ assets[key]["href"] = url_path
471
+ return assets
@@ -94,6 +94,7 @@ ECMWF_KEYWORDS = {
94
94
  "fcperiod",
95
95
  "fieldset",
96
96
  "filter",
97
+ "feature",
97
98
  "format",
98
99
  "frame",
99
100
  "frequency",
@@ -317,17 +318,17 @@ def append_time(input_date: date, time: Optional[str]) -> datetime:
317
318
 
318
319
 
319
320
  def parse_date(
320
- date_str: str, time: Optional[Union[str, list[str]]]
321
+ date: str, time: Optional[Union[str, list[str]]]
321
322
  ) -> tuple[datetime, datetime]:
322
323
  """Parses a date string in formats YYYY-MM-DD, YYYMMDD, solo or in start/end or start/to/end intervals."""
323
- if "to" in date_str:
324
- start_date_str, end_date_str = date_str.split("/to/")
325
- elif "/" in date_str:
326
- dates = date_str.split("/")
324
+ if "to" in date:
325
+ start_date_str, end_date_str = date.split("/to/")
326
+ elif "/" in date:
327
+ dates = date.split("/")
327
328
  start_date_str = dates[0]
328
329
  end_date_str = dates[-1]
329
330
  else:
330
- start_date_str = end_date_str = date_str
331
+ start_date_str = end_date_str = date
331
332
 
332
333
  # Update YYYYMMDD formatted dates
333
334
  if re.match(r"^\d{8}$", start_date_str):
@@ -401,6 +402,8 @@ def ecmwf_temporal_to_eodag(
401
402
  start = end = None
402
403
 
403
404
  if date := params.get("date"):
405
+ if isinstance(date, list):
406
+ date = "/".join(date)
404
407
  start, end = parse_date(date, params.get("time"))
405
408
 
406
409
  elif year := (params.get("year") or params.get("hyear")):
@@ -546,7 +549,9 @@ class ECMWFSearch(PostJsonSearch):
546
549
  :param params: Search parameters to be preprocessed.
547
550
  :param product_type: (optional) product type id
548
551
  """
552
+
549
553
  _dc_qs = params.get("_dc_qs")
554
+
550
555
  if _dc_qs is not None:
551
556
  # if available, update search params using datacube query-string
552
557
  _dc_qp = geojson.loads(unquote_plus(unquote_plus(_dc_qs)))
@@ -284,6 +284,8 @@ class CopMarineSearch(StaticStacSearch):
284
284
  "type": "application/x-netcdf",
285
285
  }
286
286
  }
287
+ additional_assets = self.get_assets_from_mapping(dataset_item)
288
+ assets.update(additional_assets)
287
289
  product = EOProduct(self.provider, properties, productType=product_type)
288
290
  # use product_type_config as default properties
289
291
  product_type_config = getattr(self.config, "product_type_config", {})
@@ -35,9 +35,11 @@ from typing import (
35
35
  from urllib.error import URLError
36
36
  from urllib.parse import (
37
37
  parse_qsl,
38
+ quote,
38
39
  quote_plus,
39
40
  unquote,
40
41
  unquote_plus,
42
+ urlencode,
41
43
  urlparse,
42
44
  urlunparse,
43
45
  )
@@ -86,10 +88,8 @@ from eodag.utils import (
86
88
  dict_items_recursive_apply,
87
89
  format_dict_items,
88
90
  get_ssl_context,
89
- quote,
90
91
  string_to_jsonpath,
91
92
  update_nested_dict,
92
- urlencode,
93
93
  )
94
94
  from eodag.utils.exceptions import (
95
95
  AuthenticationError,
@@ -386,36 +386,48 @@ class QueryStringSearch(Search):
386
386
 
387
387
  # parse jsonpath on init: product type specific metadata-mapping
388
388
  for product_type in self.config.products.keys():
389
- if "metadata_mapping" in self.config.products[product_type].keys():
390
- self.config.products[product_type][
391
- "metadata_mapping"
392
- ] = mtd_cfg_as_conversion_and_querypath(
393
- self.config.products[product_type]["metadata_mapping"]
394
- )
389
+
390
+ product_type_metadata_mapping = {}
391
+ # product-type specific metadata-mapping
392
+ if any(
393
+ mm in self.config.products[product_type].keys()
394
+ for mm in ("metadata_mapping", "metadata_mapping_from_product")
395
+ ):
395
396
  # Complete and ready to use product type specific metadata-mapping
396
397
  product_type_metadata_mapping = deepcopy(self.config.metadata_mapping)
397
398
 
398
- # update config using provider product type definition metadata_mapping
399
- # from another product
400
- other_product_for_mapping = cast(
401
- str,
402
- self.config.products[product_type].get(
403
- "metadata_mapping_from_product", ""
404
- ),
399
+ # metadata_mapping from another product
400
+ if other_product_for_mapping := self.config.products[product_type].get(
401
+ "metadata_mapping_from_product"
402
+ ):
403
+ other_product_type_def_params = self.get_product_type_def_params(
404
+ other_product_for_mapping,
405
405
  )
406
- if other_product_for_mapping:
407
- other_product_type_def_params = self.get_product_type_def_params(
408
- other_product_for_mapping,
409
- )
406
+ # parse mapping to apply
407
+ if other_product_type_mtd_mapping := other_product_type_def_params.get(
408
+ "metadata_mapping", {}
409
+ ):
410
410
  other_product_type_mtd_mapping = (
411
411
  mtd_cfg_as_conversion_and_querypath(
412
412
  other_product_type_def_params.get("metadata_mapping", {})
413
413
  )
414
414
  )
415
- # updated mapping at the end
416
- for metadata, mapping in other_product_type_mtd_mapping.items():
417
- product_type_metadata_mapping.pop(metadata, None)
418
- product_type_metadata_mapping[metadata] = mapping
415
+ else:
416
+ msg = f"Cannot reuse empty metadata_mapping from {other_product_for_mapping} for {product_type}"
417
+ raise MisconfiguredError(msg)
418
+ # update mapping
419
+ for metadata, mapping in other_product_type_mtd_mapping.items():
420
+ product_type_metadata_mapping.pop(metadata, None)
421
+ product_type_metadata_mapping[metadata] = mapping
422
+
423
+ # metadata_mapping from current product
424
+ if "metadata_mapping" in self.config.products[product_type].keys():
425
+ # parse mapping to apply
426
+ self.config.products[product_type][
427
+ "metadata_mapping"
428
+ ] = mtd_cfg_as_conversion_and_querypath(
429
+ self.config.products[product_type]["metadata_mapping"]
430
+ )
419
431
 
420
432
  # from current product, updated mapping at the end
421
433
  for metadata, mapping in self.config.products[product_type][
@@ -424,6 +436,7 @@ class QueryStringSearch(Search):
424
436
  product_type_metadata_mapping.pop(metadata, None)
425
437
  product_type_metadata_mapping[metadata] = mapping
426
438
 
439
+ if product_type_metadata_mapping:
427
440
  self.config.products[product_type][
428
441
  "metadata_mapping"
429
442
  ] = product_type_metadata_mapping
@@ -1090,6 +1103,8 @@ class QueryStringSearch(Search):
1090
1103
  product.properties = dict(
1091
1104
  getattr(self.config, "product_type_config", {}), **product.properties
1092
1105
  )
1106
+ additional_assets = self.get_assets_from_mapping(result)
1107
+ product.assets.update(additional_assets)
1093
1108
  # move assets from properties to product's attr, normalize keys & roles
1094
1109
  for key, asset in product.properties.pop("assets", {}).items():
1095
1110
  norm_key, asset["roles"] = product.driver.guess_asset_key_and_roles(
@@ -1173,7 +1188,10 @@ class QueryStringSearch(Search):
1173
1188
 
1174
1189
  collection = getattr(self.config, "collection", None)
1175
1190
  if collection is None:
1176
- collection = prep.product_type_def_params.get("collection") or product_type
1191
+ collection = (
1192
+ getattr(prep, "product_type_def_params", {}).get("collection")
1193
+ or product_type
1194
+ )
1177
1195
 
1178
1196
  if collection is None:
1179
1197
  return ()
@@ -1478,13 +1496,17 @@ class PostJsonSearch(QueryStringSearch):
1478
1496
  )
1479
1497
 
1480
1498
  # Add to the query, the queryable parameters set in the provider product type definition
1499
+ product_type_metadata_mapping = {
1500
+ **getattr(self.config, "metadata_mapping", {}),
1501
+ **prep.product_type_def_params.get("metadata_mapping", {}),
1502
+ }
1481
1503
  keywords.update(
1482
1504
  {
1483
1505
  k: v
1484
1506
  for k, v in prep.product_type_def_params.items()
1485
1507
  if k not in keywords.keys()
1486
- and k in self.config.metadata_mapping.keys()
1487
- and isinstance(self.config.metadata_mapping[k], list)
1508
+ and k in product_type_metadata_mapping.keys()
1509
+ and isinstance(product_type_metadata_mapping[k], list)
1488
1510
  }
1489
1511
  )
1490
1512