eodag 2.12.1__py3-none-any.whl → 3.0.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 (93) hide show
  1. eodag/__init__.py +6 -8
  2. eodag/api/core.py +654 -538
  3. eodag/api/product/__init__.py +12 -2
  4. eodag/api/product/_assets.py +59 -16
  5. eodag/api/product/_product.py +100 -93
  6. eodag/api/product/drivers/__init__.py +7 -2
  7. eodag/api/product/drivers/base.py +0 -3
  8. eodag/api/product/metadata_mapping.py +192 -96
  9. eodag/api/search_result.py +69 -10
  10. eodag/cli.py +55 -25
  11. eodag/config.py +391 -116
  12. eodag/plugins/apis/base.py +11 -168
  13. eodag/plugins/apis/ecmwf.py +36 -25
  14. eodag/plugins/apis/usgs.py +80 -35
  15. eodag/plugins/authentication/aws_auth.py +13 -4
  16. eodag/plugins/authentication/base.py +10 -1
  17. eodag/plugins/authentication/generic.py +2 -2
  18. eodag/plugins/authentication/header.py +31 -6
  19. eodag/plugins/authentication/keycloak.py +17 -84
  20. eodag/plugins/authentication/oauth.py +3 -3
  21. eodag/plugins/authentication/openid_connect.py +268 -49
  22. eodag/plugins/authentication/qsauth.py +4 -1
  23. eodag/plugins/authentication/sas_auth.py +9 -2
  24. eodag/plugins/authentication/token.py +98 -47
  25. eodag/plugins/authentication/token_exchange.py +122 -0
  26. eodag/plugins/crunch/base.py +3 -1
  27. eodag/plugins/crunch/filter_date.py +3 -9
  28. eodag/plugins/crunch/filter_latest_intersect.py +0 -3
  29. eodag/plugins/crunch/filter_latest_tpl_name.py +1 -4
  30. eodag/plugins/crunch/filter_overlap.py +4 -8
  31. eodag/plugins/crunch/filter_property.py +5 -11
  32. eodag/plugins/download/aws.py +149 -185
  33. eodag/plugins/download/base.py +88 -97
  34. eodag/plugins/download/creodias_s3.py +1 -1
  35. eodag/plugins/download/http.py +638 -310
  36. eodag/plugins/download/s3rest.py +47 -45
  37. eodag/plugins/manager.py +228 -88
  38. eodag/plugins/search/__init__.py +36 -0
  39. eodag/plugins/search/base.py +239 -30
  40. eodag/plugins/search/build_search_result.py +382 -37
  41. eodag/plugins/search/cop_marine.py +441 -0
  42. eodag/plugins/search/creodias_s3.py +25 -20
  43. eodag/plugins/search/csw.py +5 -7
  44. eodag/plugins/search/data_request_search.py +61 -30
  45. eodag/plugins/search/qssearch.py +713 -255
  46. eodag/plugins/search/static_stac_search.py +106 -40
  47. eodag/resources/ext_product_types.json +1 -1
  48. eodag/resources/product_types.yml +1921 -34
  49. eodag/resources/providers.yml +4091 -3655
  50. eodag/resources/stac.yml +50 -216
  51. eodag/resources/stac_api.yml +71 -25
  52. eodag/resources/stac_provider.yml +5 -0
  53. eodag/resources/user_conf_template.yml +89 -32
  54. eodag/rest/__init__.py +6 -0
  55. eodag/rest/cache.py +70 -0
  56. eodag/rest/config.py +68 -0
  57. eodag/rest/constants.py +26 -0
  58. eodag/rest/core.py +735 -0
  59. eodag/rest/errors.py +178 -0
  60. eodag/rest/server.py +264 -431
  61. eodag/rest/stac.py +442 -836
  62. eodag/rest/types/collections_search.py +44 -0
  63. eodag/rest/types/eodag_search.py +238 -47
  64. eodag/rest/types/queryables.py +164 -0
  65. eodag/rest/types/stac_search.py +273 -0
  66. eodag/rest/utils/__init__.py +216 -0
  67. eodag/rest/utils/cql_evaluate.py +119 -0
  68. eodag/rest/utils/rfc3339.py +64 -0
  69. eodag/types/__init__.py +106 -10
  70. eodag/types/bbox.py +15 -14
  71. eodag/types/download_args.py +40 -0
  72. eodag/types/search_args.py +57 -7
  73. eodag/types/whoosh.py +79 -0
  74. eodag/utils/__init__.py +110 -91
  75. eodag/utils/constraints.py +37 -45
  76. eodag/utils/exceptions.py +39 -22
  77. eodag/utils/import_system.py +0 -4
  78. eodag/utils/logging.py +37 -80
  79. eodag/utils/notebook.py +4 -4
  80. eodag/utils/repr.py +113 -0
  81. eodag/utils/requests.py +128 -0
  82. eodag/utils/rest.py +100 -0
  83. eodag/utils/stac_reader.py +93 -21
  84. {eodag-2.12.1.dist-info → eodag-3.0.0.dist-info}/METADATA +88 -53
  85. eodag-3.0.0.dist-info/RECORD +109 -0
  86. {eodag-2.12.1.dist-info → eodag-3.0.0.dist-info}/WHEEL +1 -1
  87. {eodag-2.12.1.dist-info → eodag-3.0.0.dist-info}/entry_points.txt +7 -5
  88. eodag/plugins/apis/cds.py +0 -540
  89. eodag/rest/types/stac_queryables.py +0 -134
  90. eodag/rest/utils.py +0 -1133
  91. eodag-2.12.1.dist-info/RECORD +0 -94
  92. {eodag-2.12.1.dist-info → eodag-3.0.0.dist-info}/LICENSE +0 -0
  93. {eodag-2.12.1.dist-info → eodag-3.0.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, Any, Dict, Iterable, List, Optional, Tuple, 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
@@ -28,6 +29,7 @@ from eodag.plugins.crunch.filter_latest_intersect import FilterLatestIntersect
28
29
  from eodag.plugins.crunch.filter_latest_tpl_name import FilterLatestByName
29
30
  from eodag.plugins.crunch.filter_overlap import FilterOverlap
30
31
  from eodag.plugins.crunch.filter_property import FilterProperty
32
+ from eodag.utils import Annotated
31
33
 
32
34
  if TYPE_CHECKING:
33
35
  from shapely.geometry.base import BaseGeometry
@@ -39,23 +41,34 @@ class SearchResult(UserList):
39
41
  """An object representing a collection of :class:`~eodag.api.product._product.EOProduct` resulting from a search.
40
42
 
41
43
  :param products: A list of products resulting from a search
42
- :type products: list(:class:`~eodag.api.product._product.EOProduct`)
44
+ :param number_matched: (optional) the estimated total number of matching results
45
+
46
+ :cvar data: List of products
47
+ :ivar number_matched: Estimated total number of matching results
43
48
  """
44
49
 
45
50
  data: List[EOProduct]
46
51
 
47
- def __init__(self, products: List[EOProduct]) -> None:
48
- super(SearchResult, self).__init__(products)
52
+ errors: Annotated[
53
+ List[Tuple[str, Exception]], Doc("Tuple of provider name, exception")
54
+ ]
55
+
56
+ def __init__(
57
+ self,
58
+ products: List[EOProduct],
59
+ number_matched: Optional[int] = None,
60
+ errors: List[Tuple[str, Exception]] = [],
61
+ ) -> None:
62
+ super().__init__(products)
63
+ self.number_matched = number_matched
64
+ self.errors = errors
49
65
 
50
66
  def crunch(self, cruncher: Crunch, **search_params: Any) -> SearchResult:
51
67
  """Do some crunching with the underlying EO products.
52
68
 
53
69
  :param cruncher: The plugin instance to use to work on the products
54
- :type cruncher: subclass of :class:`~eodag.plugins.crunch.base.Crunch`
55
70
  :param search_params: The criteria that have been used to produce this result
56
- :type search_params: dict
57
71
  :returns: The result of the application of the crunching method to the EO products
58
- :rtype: :class:`~eodag.api.search_result.SearchResult`
59
72
  """
60
73
  crunched_results = cruncher.proceed(self.data, **search_params)
61
74
  return SearchResult(crunched_results)
@@ -130,9 +143,7 @@ class SearchResult(UserList):
130
143
  """Builds an :class:`~eodag.api.search_result.SearchResult` object from its representation as geojson
131
144
 
132
145
  :param feature_collection: A collection representing a search result.
133
- :type feature_collection: dict
134
146
  :returns: An eodag representation of a search result
135
- :rtype: :class:`~eodag.api.search_result.SearchResult`
136
147
  """
137
148
  return SearchResult(
138
149
  [
@@ -149,7 +160,7 @@ class SearchResult(UserList):
149
160
  }
150
161
 
151
162
  def as_shapely_geometry_object(self) -> GeometryCollection:
152
- """:class:`shapely.geometry.GeometryCollection` representation of SearchResult"""
163
+ """:class:`shapely.GeometryCollection` representation of SearchResult"""
153
164
  return GeometryCollection(
154
165
  [
155
166
  shape(feature["geometry"]).buffer(0)
@@ -168,3 +179,51 @@ class SearchResult(UserList):
168
179
  See https://gist.github.com/sgillies/2217756
169
180
  """
170
181
  return self.as_geojson_object()
182
+
183
+ def _repr_html_(self):
184
+ total_count = f"/{self.number_matched}" if self.number_matched else ""
185
+ return (
186
+ f"""<table>
187
+ <thead><tr><td style='text-align: left; color: grey;'>
188
+ {type(self).__name__}&ensp;({len(self)}{total_count})
189
+ </td></tr></thead>
190
+ """
191
+ + "".join(
192
+ [
193
+ f"""<tr><td style='text-align: left;'>
194
+ <details><summary style='color: grey; font-family: monospace;'>
195
+ {i}&ensp;
196
+ {type(p).__name__}(id=<span style='color: black;'>{
197
+ p.properties['id']
198
+ }</span>, provider={p.provider})
199
+ </summary>
200
+ {p._repr_html_()}
201
+ </details>
202
+ </td></tr>
203
+ """
204
+ for i, p in enumerate(self)
205
+ ]
206
+ )
207
+ + "</table>"
208
+ )
209
+
210
+ def extend(self, other: Iterable) -> None:
211
+ """override extend method to include errors"""
212
+ if isinstance(other, SearchResult):
213
+ self.errors.extend(other.errors)
214
+
215
+ return super().extend(other)
216
+
217
+
218
+ class RawSearchResult(UserList):
219
+ """An object representing a collection of raw/unparsed search results obtained from a provider.
220
+
221
+ :param results: A list of raw/unparsed search results
222
+ """
223
+
224
+ data: List[Any]
225
+ query_params: Dict[str, Any]
226
+ product_type_def_params: Dict[str, Any]
227
+
228
+ def __init__(self, results: List[Any]) -> None:
229
+ 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
@@ -50,7 +51,6 @@ from importlib.metadata import metadata
50
51
  from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Set
51
52
 
52
53
  import click
53
- import uvicorn
54
54
 
55
55
  from eodag.api.core import EODataAccessGateway
56
56
  from eodag.utils import DEFAULT_ITEMS_PER_PAGE, DEFAULT_PAGE, parse_qs
@@ -242,6 +242,11 @@ def version() -> None:
242
242
  "or a maximum value defined internally for the requested provider, or a default "
243
243
  "maximum value equals to 50.",
244
244
  )
245
+ @click.option(
246
+ "--count",
247
+ is_flag=True,
248
+ help="Whether to run a query with a count request or not.",
249
+ )
245
250
  @click.option(
246
251
  "--locations",
247
252
  type=str,
@@ -334,6 +339,8 @@ def search_crunch(ctx: Context, **kwargs: Any) -> None:
334
339
  if locs_file:
335
340
  locs_file = click.format_filename(locs_file)
336
341
 
342
+ count = kwargs.pop("count")
343
+
337
344
  # Process inputs for crunch
338
345
  cruncher_names: Set[Any] = set(kwargs.pop("cruncher") or [])
339
346
  cruncher_args = kwargs.pop("cruncher_args")
@@ -361,10 +368,13 @@ def search_crunch(ctx: Context, **kwargs: Any) -> None:
361
368
  items_per_page = (
362
369
  DEFAULT_ITEMS_PER_PAGE if items_per_page is None else items_per_page
363
370
  )
364
- results, total = gateway.search(
365
- page=page, items_per_page=items_per_page, **criteria
371
+ results = gateway.search(
372
+ count=count, page=page, items_per_page=items_per_page, **criteria
366
373
  )
367
- click.echo("Found a total number of {} products".format(total))
374
+ if results.number_matched is not None:
375
+ click.echo(
376
+ "Found a total number of {} products".format(results.number_matched)
377
+ )
368
378
  click.echo("Returned {} products".format(len(results)))
369
379
 
370
380
  # Crunch !
@@ -446,8 +456,6 @@ def list_pt(ctx: Context, **kwargs: Any) -> None:
446
456
  provider=provider, fetch_providers=fetch_providers
447
457
  )
448
458
  if pt["ID"] in guessed_product_types
449
- or "alias" in pt
450
- and pt["alias"] in guessed_product_types
451
459
  ]
452
460
  else:
453
461
  product_types = dag.list_product_types(
@@ -546,12 +554,13 @@ def download(ctx: Context, **kwargs: Any) -> None:
546
554
 
547
555
  for idx, product in enumerate(search_results):
548
556
  if product.downloader is None:
557
+ downloader = satim_api._plugins_manager.get_download_plugin(product)
549
558
  auth = product.downloader_auth
550
559
  if auth is None:
551
- auth = satim_api._plugins_manager.get_auth_plugin(product.provider)
552
- search_results[idx].register_downloader(
553
- satim_api._plugins_manager.get_download_plugin(product), auth
554
- )
560
+ auth = satim_api._plugins_manager.get_auth_plugin(
561
+ downloader, product
562
+ )
563
+ search_results[idx].register_downloader(downloader, auth)
555
564
 
556
565
  downloaded_file = product.get_quicklook()
557
566
  if not downloaded_file:
@@ -566,12 +575,13 @@ def download(ctx: Context, **kwargs: Any) -> None:
566
575
  # register downloader
567
576
  for idx, product in enumerate(search_results):
568
577
  if product.downloader is None:
578
+ downloader = satim_api._plugins_manager.get_download_plugin(product)
569
579
  auth = product.downloader_auth
570
580
  if auth is None:
571
- auth = satim_api._plugins_manager.get_auth_plugin(product.provider)
572
- search_results[idx].register_downloader(
573
- satim_api._plugins_manager.get_download_plugin(product), auth
574
- )
581
+ auth = satim_api._plugins_manager.get_auth_plugin(
582
+ downloader, product
583
+ )
584
+ search_results[idx].register_downloader(downloader, auth)
575
585
 
576
586
  downloaded_files = satim_api.download_all(search_results)
577
587
  if downloaded_files and len(downloaded_files) > 0:
@@ -645,6 +655,13 @@ def serve_rest(
645
655
  ) -> None:
646
656
  """Serve EODAG functionalities through a WEB interface"""
647
657
  setup_logging(verbose=ctx.obj["verbosity"])
658
+ try:
659
+ import uvicorn
660
+ except ImportError:
661
+ raise ImportError(
662
+ "Feature not available, please install eodag[server] or eodag[all]"
663
+ )
664
+
648
665
  # Set the settings of the app
649
666
  # IMPORTANT: the order of imports counts here (first we override the settings,
650
667
  # then we import the app so that the updated settings is taken into account in
@@ -670,19 +687,32 @@ def serve_rest(
670
687
  else:
671
688
  sys.exit(0)
672
689
  else:
690
+ import logging
691
+
673
692
  logging_config = uvicorn.config.LOGGING_CONFIG
674
- if debug:
675
- logging_config["loggers"]["uvicorn"]["level"] = "DEBUG"
676
- logging_config["loggers"]["uvicorn.error"]["level"] = "DEBUG"
677
- logging_config["loggers"]["uvicorn.access"]["level"] = "DEBUG"
678
- logging_config["formatters"]["default"][
679
- "fmt"
680
- ] = "%(asctime)-15s %(name)-32s [%(levelname)-8s] (%(module)-17s) %(message)s"
681
- logging_config["loggers"]["eodag"] = {
682
- "handlers": ["default"],
683
- "level": "DEBUG" if debug else "INFO",
684
- "propagate": False,
693
+ uvicorn_fmt = "%(asctime)-15s %(name)-32s [%(levelname)-8s] %(message)s"
694
+ logging_config["formatters"]["access"]["fmt"] = uvicorn_fmt
695
+ logging_config["formatters"]["default"]["fmt"] = uvicorn_fmt
696
+
697
+ eodag_formatter = logging.Formatter(
698
+ "%(asctime)-15s %(name)-32s [%(levelname)-8s] (tid=%(thread)d) %(message)s"
699
+ )
700
+ logging.getLogger("eodag").handlers[0].setFormatter(eodag_formatter)
701
+
702
+ if ctx.obj["verbosity"] <= 1:
703
+ logging_config["handlers"]["null"] = {
704
+ "level": "DEBUG",
705
+ "class": "logging.NullHandler",
685
706
  }
707
+ logging_config["loggers"]["uvicorn"]["handlers"] = ["null"]
708
+ logging_config["loggers"]["uvicorn.error"]["handlers"] = ["null"]
709
+ logging_config["loggers"]["uvicorn.access"]["handlers"] = ["null"]
710
+ else:
711
+ log_level = "INFO" if ctx.obj["verbosity"] == 2 else "DEBUG"
712
+ logging_config["loggers"]["uvicorn"]["level"] = log_level
713
+ logging_config["loggers"]["uvicorn.error"]["level"] = log_level
714
+ logging_config["loggers"]["uvicorn.access"]["level"] = log_level
715
+
686
716
  uvicorn.run(
687
717
  "eodag.rest.server:app",
688
718
  host=bind_host,