eodag 3.0.0b3__py3-none-any.whl → 3.1.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 (94) hide show
  1. eodag/api/core.py +347 -247
  2. eodag/api/product/_assets.py +44 -15
  3. eodag/api/product/_product.py +58 -47
  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 +129 -93
  10. eodag/api/search_result.py +28 -12
  11. eodag/cli.py +61 -24
  12. eodag/config.py +457 -167
  13. eodag/plugins/apis/base.py +10 -4
  14. eodag/plugins/apis/ecmwf.py +53 -23
  15. eodag/plugins/apis/usgs.py +41 -17
  16. eodag/plugins/authentication/aws_auth.py +30 -18
  17. eodag/plugins/authentication/base.py +14 -3
  18. eodag/plugins/authentication/generic.py +14 -3
  19. eodag/plugins/authentication/header.py +14 -6
  20. eodag/plugins/authentication/keycloak.py +44 -25
  21. eodag/plugins/authentication/oauth.py +18 -4
  22. eodag/plugins/authentication/openid_connect.py +192 -171
  23. eodag/plugins/authentication/qsauth.py +12 -4
  24. eodag/plugins/authentication/sas_auth.py +22 -5
  25. eodag/plugins/authentication/token.py +95 -17
  26. eodag/plugins/authentication/token_exchange.py +19 -19
  27. eodag/plugins/base.py +4 -4
  28. eodag/plugins/crunch/base.py +8 -5
  29. eodag/plugins/crunch/filter_date.py +9 -6
  30. eodag/plugins/crunch/filter_latest_intersect.py +9 -8
  31. eodag/plugins/crunch/filter_latest_tpl_name.py +8 -8
  32. eodag/plugins/crunch/filter_overlap.py +9 -11
  33. eodag/plugins/crunch/filter_property.py +10 -10
  34. eodag/plugins/download/aws.py +181 -105
  35. eodag/plugins/download/base.py +49 -67
  36. eodag/plugins/download/creodias_s3.py +40 -2
  37. eodag/plugins/download/http.py +247 -223
  38. eodag/plugins/download/s3rest.py +29 -28
  39. eodag/plugins/manager.py +176 -41
  40. eodag/plugins/search/__init__.py +6 -5
  41. eodag/plugins/search/base.py +123 -60
  42. eodag/plugins/search/build_search_result.py +1046 -355
  43. eodag/plugins/search/cop_marine.py +132 -39
  44. eodag/plugins/search/creodias_s3.py +19 -68
  45. eodag/plugins/search/csw.py +48 -8
  46. eodag/plugins/search/data_request_search.py +124 -23
  47. eodag/plugins/search/qssearch.py +531 -310
  48. eodag/plugins/search/stac_list_assets.py +85 -0
  49. eodag/plugins/search/static_stac_search.py +23 -24
  50. eodag/resources/ext_product_types.json +1 -1
  51. eodag/resources/product_types.yml +1295 -355
  52. eodag/resources/providers.yml +1819 -3010
  53. eodag/resources/stac.yml +3 -163
  54. eodag/resources/stac_api.yml +2 -2
  55. eodag/resources/user_conf_template.yml +115 -99
  56. eodag/rest/cache.py +2 -2
  57. eodag/rest/config.py +3 -4
  58. eodag/rest/constants.py +0 -1
  59. eodag/rest/core.py +157 -117
  60. eodag/rest/errors.py +181 -0
  61. eodag/rest/server.py +57 -339
  62. eodag/rest/stac.py +133 -581
  63. eodag/rest/types/collections_search.py +3 -3
  64. eodag/rest/types/eodag_search.py +41 -30
  65. eodag/rest/types/queryables.py +42 -32
  66. eodag/rest/types/stac_search.py +15 -16
  67. eodag/rest/utils/__init__.py +14 -21
  68. eodag/rest/utils/cql_evaluate.py +6 -6
  69. eodag/rest/utils/rfc3339.py +2 -2
  70. eodag/types/__init__.py +153 -32
  71. eodag/types/bbox.py +2 -2
  72. eodag/types/download_args.py +4 -4
  73. eodag/types/queryables.py +183 -73
  74. eodag/types/search_args.py +6 -6
  75. eodag/types/whoosh.py +127 -3
  76. eodag/utils/__init__.py +228 -106
  77. eodag/utils/exceptions.py +47 -26
  78. eodag/utils/import_system.py +2 -2
  79. eodag/utils/logging.py +37 -77
  80. eodag/utils/repr.py +65 -6
  81. eodag/utils/requests.py +13 -15
  82. eodag/utils/rest.py +2 -2
  83. eodag/utils/s3.py +231 -0
  84. eodag/utils/stac_reader.py +11 -11
  85. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/METADATA +81 -81
  86. eodag-3.1.0.dist-info/RECORD +113 -0
  87. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
  88. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +5 -2
  89. eodag/resources/constraints/climate-dt.json +0 -13
  90. eodag/resources/constraints/extremes-dt.json +0 -8
  91. eodag/utils/constraints.py +0 -244
  92. eodag-3.0.0b3.dist-info/RECORD +0 -110
  93. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/LICENSE +0 -0
  94. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/top_level.txt +0 -0
@@ -26,17 +26,7 @@ import tempfile
26
26
  import zipfile
27
27
  from datetime import datetime, timedelta
28
28
  from time import sleep
29
- from typing import (
30
- TYPE_CHECKING,
31
- Any,
32
- Callable,
33
- Dict,
34
- List,
35
- Optional,
36
- Tuple,
37
- TypeVar,
38
- Union,
39
- )
29
+ from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar, Union
40
30
 
41
31
  from eodag.plugins.base import PluginTopic
42
32
  from eodag.utils import (
@@ -60,6 +50,7 @@ if TYPE_CHECKING:
60
50
  from eodag.api.product import EOProduct
61
51
  from eodag.api.search_result import SearchResult
62
52
  from eodag.config import PluginConfig
53
+ from eodag.types import S3SessionKwargs
63
54
  from eodag.types.download_args import DownloadConf
64
55
  from eodag.utils import DownloadedCallback, Unpack
65
56
 
@@ -81,15 +72,15 @@ class Download(PluginTopic):
81
72
 
82
73
  - download data in the ``output_dir`` folder defined in the plugin's
83
74
  configuration or passed through kwargs
84
- - extract products from their archive (if relevant) if ``extract`` is set to True
85
- (True by default)
75
+ - extract products from their archive (if relevant) if ``extract`` is set to ``True``
76
+ (``True`` by default)
86
77
  - save a product in an archive/directory (in ``output_dir``) whose name must be
87
78
  the product's ``title`` property
88
79
  - update the product's ``location`` attribute once its data is downloaded (and
89
80
  eventually after it's extracted) to the product's location given as a file URI
90
- (e.g. 'file:///tmp/product_folder' on Linux or
91
- 'file:///C:/Users/username/AppData/LOcal/Temp' on Windows)
92
- - save a *record* file in the directory ``output_dir/.downloaded`` whose name
81
+ (e.g. ``file:///tmp/product_folder`` on Linux or
82
+ ``file:///C:/Users/username/AppData/Local/Temp`` on Windows)
83
+ - save a *record* file in the directory ``{output_dir}/.downloaded`` whose name
93
84
  is built on the MD5 hash of the product's ``product_type`` and ``properties['id']``
94
85
  attributes (``hashlib.md5((product.product_type+"-"+product.properties['id']).encode("utf-8")).hexdigest()``)
95
86
  and whose content is the product's ``remote_location`` attribute itself.
@@ -110,10 +101,10 @@ class Download(PluginTopic):
110
101
  def download(
111
102
  self,
112
103
  product: EOProduct,
113
- auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
104
+ auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
114
105
  progress_callback: Optional[ProgressCallback] = None,
115
- wait: int = DEFAULT_DOWNLOAD_WAIT,
116
- timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
106
+ wait: float = DEFAULT_DOWNLOAD_WAIT,
107
+ timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
117
108
  **kwargs: Unpack[DownloadConf],
118
109
  ) -> Optional[str]:
119
110
  r"""
@@ -130,8 +121,8 @@ class Download(PluginTopic):
130
121
  and will override any other values defined in a configuration
131
122
  file or with environment variables.
132
123
  :returns: The absolute path to the downloaded product in the local filesystem
133
- (e.g. '/tmp/product.zip' on Linux or
134
- 'C:\\Users\\username\\AppData\\Local\\Temp\\product.zip' on Windows)
124
+ (e.g. ``/tmp/product.zip`` on Linux or
125
+ ``C:\\Users\\username\\AppData\\Local\\Temp\\product.zip`` on Windows)
135
126
  """
136
127
  raise NotImplementedError(
137
128
  "A Download plugin must implement a method named download"
@@ -140,10 +131,10 @@ class Download(PluginTopic):
140
131
  def _stream_download_dict(
141
132
  self,
142
133
  product: EOProduct,
143
- auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
134
+ auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
144
135
  progress_callback: Optional[ProgressCallback] = None,
145
- wait: int = DEFAULT_DOWNLOAD_WAIT,
146
- timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
136
+ wait: float = DEFAULT_DOWNLOAD_WAIT,
137
+ timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
147
138
  **kwargs: Unpack[DownloadConf],
148
139
  ) -> StreamResponse:
149
140
  r"""
@@ -155,11 +146,11 @@ class Download(PluginTopic):
155
146
  :param wait: (optional) If download fails, wait time in minutes between two download tries
156
147
  :param timeout: (optional) If download fails, maximum time in minutes before stop retrying
157
148
  to download
158
- :param kwargs: `output_dir` (str), `extract` (bool), `delete_archive` (bool)
159
- and `dl_url_params` (dict) can be provided as additional kwargs
149
+ :param kwargs: ``output_dir`` (str), ``extract`` (bool), ``delete_archive`` (bool)
150
+ and ``dl_url_params`` (dict) can be provided as additional kwargs
160
151
  and will override any other values defined in a configuration
161
152
  file or with environment variables.
162
- :returns: Dictionnary of :class:`~fastapi.responses.StreamingResponse` keyword-arguments
153
+ :returns: Dictionary of :class:`~fastapi.responses.StreamingResponse` keyword-arguments
163
154
  """
164
155
  raise NotImplementedError(
165
156
  "Download streaming must be implemented using a method named _stream_download_dict"
@@ -170,7 +161,7 @@ class Download(PluginTopic):
170
161
  product: EOProduct,
171
162
  progress_callback: Optional[ProgressCallback] = None,
172
163
  **kwargs: Unpack[DownloadConf],
173
- ) -> Tuple[Optional[str], Optional[str]]:
164
+ ) -> tuple[Optional[str], Optional[str]]:
174
165
  """Check if file has already been downloaded, and prepare product download
175
166
 
176
167
  :param product: The EO product to download
@@ -202,8 +193,8 @@ class Download(PluginTopic):
202
193
  or getattr(self.config, "output_dir", tempfile.gettempdir())
203
194
  or tempfile.gettempdir()
204
195
  )
205
- output_extension = kwargs.get("output_extension", None) or getattr(
206
- self.config, "output_extension", ".zip"
196
+ output_extension = kwargs.get("output_extension") or getattr(
197
+ self.config, "output_extension", ""
207
198
  )
208
199
 
209
200
  # Strong asumption made here: all products downloaded will be zip files
@@ -233,9 +224,13 @@ class Download(PluginTopic):
233
224
  logger.warning(
234
225
  f"Unable to create records directory. Got:\n{tb.format_exc()}",
235
226
  )
227
+ url_hash = hashlib.md5(url.encode("utf-8")).hexdigest()
228
+ old_record_filename = os.path.join(download_records_dir, url_hash)
236
229
  record_filename = os.path.join(
237
230
  download_records_dir, self.generate_record_hash(product)
238
231
  )
232
+ if os.path.isfile(old_record_filename):
233
+ os.rename(old_record_filename, record_filename)
239
234
  if os.path.isfile(record_filename) and os.path.isfile(fs_path):
240
235
  logger.info(
241
236
  f"Product already downloaded: {fs_path}",
@@ -339,13 +334,7 @@ class Download(PluginTopic):
339
334
  if delete_archive is not None
340
335
  else getattr(self.config, "delete_archive", True)
341
336
  )
342
- output_extension = kwargs.pop("output_extension", ".zip")
343
-
344
- product_path = (
345
- fs_path[: fs_path.index(output_extension)]
346
- if output_extension in fs_path
347
- else fs_path
348
- )
337
+ product_path, _ = os.path.splitext(fs_path)
349
338
  product_path_exists = os.path.exists(product_path)
350
339
  if product_path_exists and os.path.isfile(product_path):
351
340
  logger.info(
@@ -422,10 +411,10 @@ class Download(PluginTopic):
422
411
 
423
412
  tmp_dir.cleanup()
424
413
 
425
- if delete_archive:
414
+ if delete_archive and os.path.isfile(fs_path):
426
415
  logger.info(f"Deleting archive {os.path.basename(fs_path)}")
427
416
  os.unlink(fs_path)
428
- else:
417
+ elif os.path.isfile(fs_path):
429
418
  logger.info(
430
419
  f"Archive deletion is deactivated, keeping {os.path.basename(fs_path)}"
431
420
  )
@@ -441,13 +430,13 @@ class Download(PluginTopic):
441
430
  def download_all(
442
431
  self,
443
432
  products: SearchResult,
444
- auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
433
+ auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
445
434
  downloaded_callback: Optional[DownloadedCallback] = None,
446
435
  progress_callback: Optional[ProgressCallback] = None,
447
- wait: int = DEFAULT_DOWNLOAD_WAIT,
448
- timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
436
+ wait: float = DEFAULT_DOWNLOAD_WAIT,
437
+ timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
449
438
  **kwargs: Unpack[DownloadConf],
450
- ) -> List[str]:
439
+ ) -> list[str]:
451
440
  """
452
441
  Base download_all method.
453
442
 
@@ -458,7 +447,7 @@ class Download(PluginTopic):
458
447
  :param auth: (optional) authenticated object
459
448
  :param downloaded_callback: (optional) A method or a callable object which takes
460
449
  as parameter the ``product``. You can use the base class
461
- :class:`~eodag.api.product.DownloadedCallback` and override
450
+ :class:`~eodag.utils.DownloadedCallback` and override
462
451
  its ``__call__`` method. Will be called each time a product
463
452
  finishes downloading
464
453
  :param progress_callback: (optional) A progress callback
@@ -476,7 +465,7 @@ class Download(PluginTopic):
476
465
  # Products are going to be removed one by one from this sequence once
477
466
  # downloaded.
478
467
  products = products[:]
479
- paths: List[str] = []
468
+ paths: list[str] = []
480
469
  # initiate retry loop
481
470
  start_time = datetime.now()
482
471
  stop_time = start_time + timedelta(minutes=timeout)
@@ -541,7 +530,7 @@ class Download(PluginTopic):
541
530
  )
542
531
  raise
543
532
 
544
- except RuntimeError:
533
+ except (RuntimeError, Exception):
545
534
  import traceback as tb
546
535
 
547
536
  logger.error(
@@ -549,16 +538,9 @@ class Download(PluginTopic):
549
538
  "Skipping it"
550
539
  )
551
540
  logger.debug(f"\n{tb.format_exc()}")
552
- stop_time = datetime.now()
553
541
 
554
- except Exception:
555
- import traceback as tb
556
-
557
- logger.warning(
558
- f"A problem occurred during download of product: {product}. "
559
- "Skipping it",
560
- )
561
- logger.debug(f"\n{tb.format_exc()}")
542
+ # product skipped, to not retry it
543
+ products.remove(product)
562
544
 
563
545
  if (
564
546
  len(products) > 0
@@ -585,14 +567,14 @@ class Download(PluginTopic):
585
567
 
586
568
  return paths
587
569
 
588
- def _download_retry(
589
- self, product: EOProduct, wait: int, timeout: int
570
+ def _order_download_retry(
571
+ self, product: EOProduct, wait: float, timeout: float
590
572
  ) -> Callable[[Callable[..., T]], Callable[..., T]]:
591
573
  """
592
- Download retry decorator.
574
+ Order download retry decorator.
593
575
 
594
- Retries the wrapped download method after `wait` minutes if a NotAvailableError
595
- exception is thrown until `timeout` minutes.
576
+ Retries the wrapped order_download method after ``wait`` minutes if a
577
+ ``NotAvailableError`` exception is thrown until ``timeout`` minutes.
596
578
 
597
579
  :param product: The EO product to download
598
580
  :param wait: If download fails, wait time in minutes between two download tries
@@ -601,7 +583,7 @@ class Download(PluginTopic):
601
583
  :returns: decorator
602
584
  """
603
585
 
604
- def decorator(download: Callable[..., T]) -> Callable[..., T]:
586
+ def decorator(order_download: Callable[..., T]) -> Callable[..., T]:
605
587
  def download_and_retry(*args: Any, **kwargs: Unpack[DownloadConf]) -> T:
606
588
  # initiate retry loop
607
589
  start_time = datetime.now()
@@ -618,7 +600,7 @@ class Download(PluginTopic):
618
600
  if datetime_now >= product.next_try:
619
601
  product.next_try += timedelta(minutes=wait)
620
602
  try:
621
- return download(*args, **kwargs)
603
+ return order_download(*args, **kwargs)
622
604
 
623
605
  except NotAvailableError as e:
624
606
  if not getattr(self.config, "order_enabled", False):
@@ -634,7 +616,7 @@ class Download(PluginTopic):
634
616
  ).seconds
635
617
  retry_count += 1
636
618
  retry_info = (
637
- f"[Retry #{retry_count}] Waited {wait_seconds}s, trying again to download ordered product"
619
+ f"[Retry #{retry_count}] Waited {wait_seconds}s, checking order status again"
638
620
  f" (retry every {wait}' for {timeout}')"
639
621
  )
640
622
  logger.info(not_available_info)
@@ -656,8 +638,8 @@ class Download(PluginTopic):
656
638
  ).microseconds / 1e6
657
639
  retry_count += 1
658
640
  retry_info = (
659
- f"[Retry #{retry_count}] Waiting {wait_seconds}s until next download try"
660
- f" for ordered product (retry every {wait}' for {timeout}')"
641
+ f"[Retry #{retry_count}] Waiting {wait_seconds}s until next order status check"
642
+ f" (retry every {wait}' for {timeout}')"
661
643
  )
662
644
  logger.info(not_available_info)
663
645
  # Retry-After info from Response header
@@ -678,12 +660,12 @@ class Download(PluginTopic):
678
660
  logger.info(not_available_info)
679
661
  raise NotAvailableError(
680
662
  f"{product.properties['title']} is not available ({product.properties['storageStatus']})"
681
- f" and could not be downloaded, timeout reached"
663
+ f" and order was not successfull, timeout reached"
682
664
  )
683
665
  elif datetime_now >= stop_time:
684
666
  raise NotAvailableError(not_available_info)
685
667
 
686
- return download(*args, **kwargs)
668
+ return order_download(*args, **kwargs)
687
669
 
688
670
  return download_and_retry
689
671
 
@@ -15,17 +15,28 @@
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
19
 
19
20
  import boto3
20
21
  from botocore.exceptions import ClientError
21
22
 
23
+ from eodag import EOProduct
22
24
  from eodag.plugins.download.aws import AwsDownload
23
25
  from eodag.utils.exceptions import MisconfiguredError
24
26
 
25
27
 
26
28
  class CreodiasS3Download(AwsDownload):
27
29
  """
28
- Download on creodias s3 from their VMs
30
+ Download on creodias s3 from their VMs (extension of :class:`~eodag.plugins.download.aws.AwsDownload`)
31
+
32
+ :param provider: provider name
33
+ :param config: Download plugin configuration:
34
+
35
+ * :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): CreodiasS3Download
36
+ * :attr:`~eodag.config.PluginConfig.base_uri` (``str``) (**mandatory**): s3 endpoint url
37
+ * :attr:`~eodag.config.PluginConfig.s3_bucket` (``str``) (**mandatory**): bucket where the products can be found
38
+ * :attr:`~eodag.config.PluginConfig.ssl_verify` (``bool``): if the ssl certificates should be
39
+ verified in requests; default: ``True``
29
40
  """
30
41
 
31
42
  def _get_authenticated_objects_unsigned(self, bucket_name, prefix, auth_dict):
@@ -50,9 +61,36 @@ class CreodiasS3Download(AwsDownload):
50
61
 
51
62
  s3_session = boto3.session.Session(**auth_dict)
52
63
  s3_resource = s3_session.resource(
53
- "s3", endpoint_url=getattr(self.config, "base_uri", None)
64
+ "s3", endpoint_url=getattr(self.config, "s3_endpoint", None)
54
65
  )
55
66
  objects = s3_resource.Bucket(bucket_name).objects.filter()
56
67
  list(objects.filter(Prefix=prefix).limit(1))
57
68
  self.s3_session = s3_session
58
69
  return objects
70
+
71
+ def _get_bucket_names_and_prefixes(
72
+ self,
73
+ product: EOProduct,
74
+ asset_filter: Optional[str] = None,
75
+ ignore_assets: Optional[bool] = False,
76
+ ) -> list[tuple[str, Optional[str]]]:
77
+ """
78
+ Retrieves the bucket names and path prefixes for the assets
79
+
80
+ :param product: product for which the assets shall be downloaded
81
+ :param asset_filter: text for which the assets should be filtered
82
+ :param ignore_assets: if product instead of individual assets should be used
83
+ :return: tuples of bucket names and prefixes
84
+ """
85
+ # if assets are defined, use them instead of scanning product.location
86
+ if len(product.assets) > 0 and not ignore_assets:
87
+ bucket_names_and_prefixes = super()._get_bucket_names_and_prefixes(
88
+ product, asset_filter, ignore_assets
89
+ )
90
+ else:
91
+ # if no assets are given, use productIdentifier to get S3 path for download
92
+ s3_url = "s3:/" + product.properties["productIdentifier"]
93
+ bucket_names_and_prefixes = [
94
+ self.get_product_bucket_name_and_prefix(product, s3_url)
95
+ ]
96
+ return bucket_names_and_prefixes