eodag 3.0.1__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 (87) hide show
  1. eodag/api/core.py +174 -138
  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 +117 -90
  10. eodag/api/search_result.py +13 -23
  11. eodag/cli.py +26 -5
  12. eodag/config.py +86 -92
  13. eodag/plugins/apis/base.py +1 -1
  14. eodag/plugins/apis/ecmwf.py +42 -22
  15. eodag/plugins/apis/usgs.py +17 -16
  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 +22 -16
  22. eodag/plugins/authentication/sas_auth.py +4 -4
  23. eodag/plugins/authentication/token.py +41 -10
  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 +6 -7
  32. eodag/plugins/download/aws.py +146 -87
  33. eodag/plugins/download/base.py +38 -56
  34. eodag/plugins/download/creodias_s3.py +29 -0
  35. eodag/plugins/download/http.py +173 -183
  36. eodag/plugins/download/s3rest.py +10 -11
  37. eodag/plugins/manager.py +10 -20
  38. eodag/plugins/search/__init__.py +6 -5
  39. eodag/plugins/search/base.py +90 -46
  40. eodag/plugins/search/build_search_result.py +1048 -361
  41. eodag/plugins/search/cop_marine.py +22 -12
  42. eodag/plugins/search/creodias_s3.py +9 -73
  43. eodag/plugins/search/csw.py +11 -11
  44. eodag/plugins/search/data_request_search.py +19 -18
  45. eodag/plugins/search/qssearch.py +99 -258
  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 +1134 -325
  50. eodag/resources/providers.yml +906 -2006
  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 +112 -82
  56. eodag/rest/errors.py +5 -5
  57. eodag/rest/server.py +33 -14
  58. eodag/rest/stac.py +41 -38
  59. eodag/rest/types/collections_search.py +3 -3
  60. eodag/rest/types/eodag_search.py +29 -23
  61. eodag/rest/types/queryables.py +42 -31
  62. eodag/rest/types/stac_search.py +15 -25
  63. eodag/rest/utils/__init__.py +14 -21
  64. eodag/rest/utils/cql_evaluate.py +6 -6
  65. eodag/rest/utils/rfc3339.py +2 -2
  66. eodag/types/__init__.py +141 -32
  67. eodag/types/bbox.py +2 -2
  68. eodag/types/download_args.py +3 -3
  69. eodag/types/queryables.py +183 -72
  70. eodag/types/search_args.py +4 -4
  71. eodag/types/whoosh.py +127 -3
  72. eodag/utils/__init__.py +153 -51
  73. eodag/utils/exceptions.py +28 -21
  74. eodag/utils/import_system.py +2 -2
  75. eodag/utils/repr.py +65 -6
  76. eodag/utils/requests.py +13 -13
  77. eodag/utils/rest.py +2 -2
  78. eodag/utils/s3.py +231 -0
  79. eodag/utils/stac_reader.py +10 -10
  80. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/METADATA +77 -76
  81. eodag-3.1.0.dist-info/RECORD +113 -0
  82. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
  83. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +4 -2
  84. eodag/utils/constraints.py +0 -244
  85. eodag-3.0.1.dist-info/RECORD +0 -109
  86. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/LICENSE +0 -0
  87. {eodag-3.0.1.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
 
@@ -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"""
@@ -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"""
@@ -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
 
@@ -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
-
554
- except Exception:
555
- import traceback as tb
556
541
 
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,10 +15,12 @@
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
 
@@ -65,3 +67,30 @@ class CreodiasS3Download(AwsDownload):
65
67
  list(objects.filter(Prefix=prefix).limit(1))
66
68
  self.s3_session = s3_session
67
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