eodag 3.6.0__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.
@@ -15,7 +15,7 @@
15
15
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
- from typing import Optional
18
+ from typing import TYPE_CHECKING, Optional
19
19
 
20
20
  import boto3
21
21
  from botocore.exceptions import ClientError
@@ -24,6 +24,9 @@ from eodag import EOProduct
24
24
  from eodag.plugins.download.aws import AwsDownload
25
25
  from eodag.utils.exceptions import MisconfiguredError
26
26
 
27
+ if TYPE_CHECKING:
28
+ from mypy_boto3_s3.service_resource import S3ServiceResource
29
+
27
30
 
28
31
  class CreodiasS3Download(AwsDownload):
29
32
  """
@@ -60,19 +63,21 @@ class CreodiasS3Download(AwsDownload):
60
63
  )
61
64
 
62
65
  s3_session = boto3.session.Session(**auth_dict)
63
- s3_resource = s3_session.resource(
66
+ s3_resource: S3ServiceResource = s3_session.resource(
64
67
  "s3", endpoint_url=getattr(self.config, "s3_endpoint", None)
65
68
  )
66
69
  objects = s3_resource.Bucket(bucket_name).objects.filter()
67
70
  list(objects.filter(Prefix=prefix).limit(1))
68
71
  self.s3_session = s3_session
72
+ self.s3_resource = s3_resource
69
73
  return objects
70
74
 
71
75
  def _get_bucket_names_and_prefixes(
72
76
  self,
73
77
  product: EOProduct,
74
- asset_filter: Optional[str] = None,
75
- ignore_assets: Optional[bool] = False,
78
+ asset_filter: Optional[str],
79
+ ignore_assets: bool,
80
+ complementary_url_keys: list[str],
76
81
  ) -> list[tuple[str, Optional[str]]]:
77
82
  """
78
83
  Retrieves the bucket names and path prefixes for the assets
@@ -85,7 +90,7 @@ class CreodiasS3Download(AwsDownload):
85
90
  # if assets are defined, use them instead of scanning product.location
86
91
  if len(product.assets) > 0 and not ignore_assets:
87
92
  bucket_names_and_prefixes = super()._get_bucket_names_and_prefixes(
88
- product, asset_filter, ignore_assets
93
+ product, asset_filter, ignore_assets, complementary_url_keys
89
94
  )
90
95
  else:
91
96
  # if no assets are given, use productIdentifier to get S3 path for download
@@ -28,7 +28,16 @@ from email.message import Message
28
28
  from itertools import chain
29
29
  from json import JSONDecodeError
30
30
  from pathlib import Path
31
- from typing import TYPE_CHECKING, Any, Iterator, Optional, TypedDict, Union, cast
31
+ from typing import (
32
+ TYPE_CHECKING,
33
+ Any,
34
+ Iterator,
35
+ Literal,
36
+ Optional,
37
+ TypedDict,
38
+ Union,
39
+ cast,
40
+ )
32
41
  from urllib.parse import parse_qs, urlparse
33
42
 
34
43
  import geojson
@@ -745,7 +754,8 @@ class HTTPDownload(Download):
745
754
  self,
746
755
  product: EOProduct,
747
756
  auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
748
- progress_callback: Optional[ProgressCallback] = None,
757
+ byte_range: tuple[Optional[int], Optional[int]] = (None, None),
758
+ compress: Literal["zip", "raw", "auto"] = "auto",
749
759
  wait: float = DEFAULT_DOWNLOAD_WAIT,
750
760
  timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
751
761
  **kwargs: Unpack[DownloadConf],
@@ -779,7 +789,7 @@ class HTTPDownload(Download):
779
789
  chunks_tuples = self._stream_download_assets(
780
790
  product,
781
791
  auth,
782
- progress_callback,
792
+ None,
783
793
  assets_values=assets_values,
784
794
  **kwargs,
785
795
  )
@@ -825,9 +835,7 @@ class HTTPDownload(Download):
825
835
  else:
826
836
  pass
827
837
 
828
- chunk_iterator = self._stream_download(
829
- product, auth, progress_callback, **kwargs
830
- )
838
+ chunk_iterator = self._stream_download(product, auth, None, **kwargs)
831
839
 
832
840
  # start reading chunks to set product.headers
833
841
  try:
@@ -21,6 +21,7 @@ import logging
21
21
  import os
22
22
  import os.path
23
23
  from typing import TYPE_CHECKING, Optional, Union
24
+ from urllib.parse import unquote, urljoin
24
25
  from xml.dom import minidom
25
26
  from xml.parsers.expat import ExpatError
26
27
 
@@ -40,8 +41,6 @@ from eodag.utils import (
40
41
  ProgressCallback,
41
42
  get_bucket_name_and_prefix,
42
43
  path_to_uri,
43
- unquote,
44
- urljoin,
45
44
  )
46
45
  from eodag.utils.exceptions import (
47
46
  AuthenticationError,
eodag/plugins/manager.py CHANGED
@@ -109,7 +109,7 @@ class PluginManager:
109
109
  logger.warning("Reason:\n%s", tb.format_exc())
110
110
  logger.warning(
111
111
  "Check that the plugin module (%s) is importable",
112
- entry_point.module_name,
112
+ entry_point.name,
113
113
  )
114
114
  if entry_point.dist and entry_point.dist.name != "eodag":
115
115
  # use plugin providers if any
@@ -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",
@@ -548,7 +549,9 @@ class ECMWFSearch(PostJsonSearch):
548
549
  :param params: Search parameters to be preprocessed.
549
550
  :param product_type: (optional) product type id
550
551
  """
552
+
551
553
  _dc_qs = params.get("_dc_qs")
554
+
552
555
  if _dc_qs is not None:
553
556
  # if available, update search params using datacube query-string
554
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(
@@ -1481,13 +1496,17 @@ class PostJsonSearch(QueryStringSearch):
1481
1496
  )
1482
1497
 
1483
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
+ }
1484
1503
  keywords.update(
1485
1504
  {
1486
1505
  k: v
1487
1506
  for k, v in prep.product_type_def_params.items()
1488
1507
  if k not in keywords.keys()
1489
- and k in self.config.metadata_mapping.keys()
1490
- 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)
1491
1510
  }
1492
1511
  )
1493
1512