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
@@ -18,9 +18,10 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  from collections import UserList
21
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
21
+ from typing import TYPE_CHECKING, Annotated, Any, Iterable, Optional, Union
22
22
 
23
23
  from shapely.geometry import GeometryCollection, shape
24
+ from typing_extensions import Doc
24
25
 
25
26
  from eodag.api.product import EOProduct
26
27
  from eodag.plugins.crunch.filter_date import FilterDate
@@ -45,13 +46,21 @@ class SearchResult(UserList):
45
46
  :ivar number_matched: Estimated total number of matching results
46
47
  """
47
48
 
48
- data: List[EOProduct]
49
+ data: list[EOProduct]
50
+
51
+ errors: Annotated[
52
+ list[tuple[str, Exception]], Doc("Tuple of provider name, exception")
53
+ ]
49
54
 
50
55
  def __init__(
51
- self, products: List[EOProduct], number_matched: Optional[int] = None
56
+ self,
57
+ products: list[EOProduct],
58
+ number_matched: Optional[int] = None,
59
+ errors: list[tuple[str, Exception]] = [],
52
60
  ) -> None:
53
- super(SearchResult, self).__init__(products)
61
+ super().__init__(products)
54
62
  self.number_matched = number_matched
63
+ self.errors = errors
55
64
 
56
65
  def crunch(self, cruncher: Crunch, **search_params: Any) -> SearchResult:
57
66
  """Do some crunching with the underlying EO products.
@@ -73,7 +82,7 @@ class SearchResult(UserList):
73
82
  return self.crunch(FilterDate(dict(start=start, end=end)))
74
83
 
75
84
  def filter_latest_intersect(
76
- self, geometry: Union[Dict[str, Any], BaseGeometry, Any]
85
+ self, geometry: Union[dict[str, Any], BaseGeometry, Any]
77
86
  ) -> SearchResult:
78
87
  """
79
88
  Apply :class:`~eodag.plugins.crunch.filter_latest_intersect.FilterLatestIntersect` crunch,
@@ -129,7 +138,7 @@ class SearchResult(UserList):
129
138
  return self.filter_property(storageStatus="ONLINE")
130
139
 
131
140
  @staticmethod
132
- def from_geojson(feature_collection: Dict[str, Any]) -> SearchResult:
141
+ def from_geojson(feature_collection: dict[str, Any]) -> SearchResult:
133
142
  """Builds an :class:`~eodag.api.search_result.SearchResult` object from its representation as geojson
134
143
 
135
144
  :param feature_collection: A collection representing a search result.
@@ -142,7 +151,7 @@ class SearchResult(UserList):
142
151
  ]
143
152
  )
144
153
 
145
- def as_geojson_object(self) -> Dict[str, Any]:
154
+ def as_geojson_object(self) -> dict[str, Any]:
146
155
  """GeoJSON representation of SearchResult"""
147
156
  return {
148
157
  "type": "FeatureCollection",
@@ -163,7 +172,7 @@ class SearchResult(UserList):
163
172
  return self.as_shapely_geometry_object().wkt
164
173
 
165
174
  @property
166
- def __geo_interface__(self) -> Dict[str, Any]:
175
+ def __geo_interface__(self) -> dict[str, Any]:
167
176
  """Implements the geo-interface protocol.
168
177
 
169
178
  See https://gist.github.com/sgillies/2217756
@@ -197,6 +206,13 @@ class SearchResult(UserList):
197
206
  + "</table>"
198
207
  )
199
208
 
209
+ def extend(self, other: Iterable) -> None:
210
+ """override extend method to include errors"""
211
+ if isinstance(other, SearchResult):
212
+ self.errors.extend(other.errors)
213
+
214
+ return super().extend(other)
215
+
200
216
 
201
217
  class RawSearchResult(UserList):
202
218
  """An object representing a collection of raw/unparsed search results obtained from a provider.
@@ -204,9 +220,9 @@ class RawSearchResult(UserList):
204
220
  :param results: A list of raw/unparsed search results
205
221
  """
206
222
 
207
- data: List[Any]
208
- query_params: Dict[str, Any]
209
- product_type_def_params: Dict[str, Any]
223
+ data: list[Any]
224
+ query_params: dict[str, Any]
225
+ product_type_def_params: dict[str, Any]
210
226
 
211
- def __init__(self, results: List[Any]) -> None:
227
+ def __init__(self, results: list[Any]) -> None:
212
228
  super(RawSearchResult, self).__init__(results)
eodag/cli.py CHANGED
@@ -39,6 +39,7 @@ Commands:
39
39
 
40
40
  noqa: D103
41
41
  """
42
+
42
43
  from __future__ import annotations
43
44
 
44
45
  import json
@@ -47,7 +48,7 @@ import shutil
47
48
  import sys
48
49
  import textwrap
49
50
  from importlib.metadata import metadata
50
- from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Set
51
+ from typing import TYPE_CHECKING, Any, Mapping
51
52
 
52
53
  import click
53
54
 
@@ -56,6 +57,11 @@ from eodag.utils import DEFAULT_ITEMS_PER_PAGE, DEFAULT_PAGE, parse_qs
56
57
  from eodag.utils.exceptions import NoMatchingProductType, UnsupportedProvider
57
58
  from eodag.utils.logging import setup_logging
58
59
 
60
+ try:
61
+ from eodag.rest.utils import LIVENESS_PROBE_PATH
62
+ except ImportError:
63
+ pass
64
+
59
65
  if TYPE_CHECKING:
60
66
  from click import Context
61
67
 
@@ -69,6 +75,18 @@ CRUNCHERS = [
69
75
  ]
70
76
 
71
77
 
78
+ class LivenessFilter:
79
+ """
80
+ Filter out requests to the liveness probe endpoint
81
+ """
82
+
83
+ def filter(self, record):
84
+ """
85
+ Filter method required by the Python logging API.
86
+ """
87
+ return LIVENESS_PROBE_PATH not in record.getMessage()
88
+
89
+
72
90
  class MutuallyExclusiveOption(click.Option):
73
91
  """Mutually Exclusive Options for Click
74
92
  from https://gist.github.com/jacobtolar/fb80d5552a9a9dfc32b12a829fa21c0c
@@ -86,7 +104,7 @@ class MutuallyExclusiveOption(click.Option):
86
104
  super(MutuallyExclusiveOption, self).__init__(*args, **kwargs)
87
105
 
88
106
  def handle_parse_result(
89
- self, ctx: Context, opts: Mapping[str, Any], args: List[str]
107
+ self, ctx: Context, opts: Mapping[str, Any], args: list[str]
90
108
  ):
91
109
  """Raise error or use parent handle_parse_result()"""
92
110
  if self.mutually_exclusive.intersection(opts) and self.name in opts:
@@ -341,9 +359,9 @@ def search_crunch(ctx: Context, **kwargs: Any) -> None:
341
359
  count = kwargs.pop("count")
342
360
 
343
361
  # Process inputs for crunch
344
- cruncher_names: Set[Any] = set(kwargs.pop("cruncher") or [])
362
+ cruncher_names: set[Any] = set(kwargs.pop("cruncher") or [])
345
363
  cruncher_args = kwargs.pop("cruncher_args")
346
- cruncher_args_dict: Dict[str, Dict[str, Any]] = {}
364
+ cruncher_args_dict: dict[str, dict[str, Any]] = {}
347
365
  if cruncher_args:
348
366
  for cruncher, argname, argval in cruncher_args:
349
367
  cruncher_args_dict.setdefault(cruncher, {}).setdefault(argname, argval)
@@ -553,12 +571,13 @@ def download(ctx: Context, **kwargs: Any) -> None:
553
571
 
554
572
  for idx, product in enumerate(search_results):
555
573
  if product.downloader is None:
574
+ downloader = satim_api._plugins_manager.get_download_plugin(product)
556
575
  auth = product.downloader_auth
557
576
  if auth is None:
558
- auth = satim_api._plugins_manager.get_auth_plugin(product.provider)
559
- search_results[idx].register_downloader(
560
- satim_api._plugins_manager.get_download_plugin(product), auth
561
- )
577
+ auth = satim_api._plugins_manager.get_auth_plugin(
578
+ downloader, product
579
+ )
580
+ search_results[idx].register_downloader(downloader, auth)
562
581
 
563
582
  downloaded_file = product.get_quicklook()
564
583
  if not downloaded_file:
@@ -573,12 +592,13 @@ def download(ctx: Context, **kwargs: Any) -> None:
573
592
  # register downloader
574
593
  for idx, product in enumerate(search_results):
575
594
  if product.downloader is None:
595
+ downloader = satim_api._plugins_manager.get_download_plugin(product)
576
596
  auth = product.downloader_auth
577
597
  if auth is None:
578
- auth = satim_api._plugins_manager.get_auth_plugin(product.provider)
579
- search_results[idx].register_downloader(
580
- satim_api._plugins_manager.get_download_plugin(product), auth
581
- )
598
+ auth = satim_api._plugins_manager.get_auth_plugin(
599
+ downloader, product
600
+ )
601
+ search_results[idx].register_downloader(downloader, auth)
582
602
 
583
603
  downloaded_files = satim_api.download_all(search_results)
584
604
  if downloaded_files and len(downloaded_files) > 0:
@@ -676,7 +696,9 @@ def serve_rest(
676
696
  try:
677
697
  pid = os.fork()
678
698
  except OSError as e:
679
- raise Exception("%s [%d]" % (e.strerror, e.errno))
699
+ raise Exception(
700
+ "%s [%d]" % (e.strerror, e.errno) if e.errno is not None else e.strerror
701
+ )
680
702
 
681
703
  if pid == 0:
682
704
  os.setsid()
@@ -684,19 +706,34 @@ def serve_rest(
684
706
  else:
685
707
  sys.exit(0)
686
708
  else:
709
+ import logging
710
+
687
711
  logging_config = uvicorn.config.LOGGING_CONFIG
688
- if debug:
689
- logging_config["loggers"]["uvicorn"]["level"] = "DEBUG"
690
- logging_config["loggers"]["uvicorn.error"]["level"] = "DEBUG"
691
- logging_config["loggers"]["uvicorn.access"]["level"] = "DEBUG"
692
- logging_config["formatters"]["default"][
693
- "fmt"
694
- ] = "%(asctime)-15s %(name)-32s [%(levelname)-8s] (%(module)-17s) %(message)s"
695
- logging_config["loggers"]["eodag"] = {
696
- "handlers": ["default"],
697
- "level": "DEBUG" if debug else "INFO",
698
- "propagate": False,
712
+ uvicorn_fmt = "%(asctime)-15s %(name)-32s [%(levelname)-8s] %(message)s"
713
+ logging_config["filters"] = {"liveness": {"()": LivenessFilter}}
714
+ logging_config["formatters"]["access"]["fmt"] = uvicorn_fmt
715
+ logging_config["formatters"]["default"]["fmt"] = uvicorn_fmt
716
+ logging_config["loggers"]["uvicorn.access"]["filters"] = ["liveness"]
717
+
718
+ eodag_formatter = logging.Formatter(
719
+ "%(asctime)-15s %(name)-32s [%(levelname)-8s] (tid=%(thread)d) %(message)s"
720
+ )
721
+ logging.getLogger("eodag").handlers[0].setFormatter(eodag_formatter)
722
+
723
+ if ctx.obj["verbosity"] <= 1:
724
+ logging_config["handlers"]["null"] = {
725
+ "level": "DEBUG",
726
+ "class": "logging.NullHandler",
699
727
  }
728
+ logging_config["loggers"]["uvicorn"]["handlers"] = ["null"]
729
+ logging_config["loggers"]["uvicorn.error"]["handlers"] = ["null"]
730
+ logging_config["loggers"]["uvicorn.access"]["handlers"] = ["null"]
731
+ else:
732
+ log_level = "INFO" if ctx.obj["verbosity"] == 2 else "DEBUG"
733
+ logging_config["loggers"]["uvicorn"]["level"] = log_level
734
+ logging_config["loggers"]["uvicorn.error"]["level"] = log_level
735
+ logging_config["loggers"]["uvicorn.access"]["level"] = log_level
736
+
700
737
  uvicorn.run(
701
738
  "eodag.rest.server:app",
702
739
  host=bind_host,