eodag 3.0.0b2__py3-none-any.whl → 3.0.1__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 (84) hide show
  1. eodag/__init__.py +6 -8
  2. eodag/api/core.py +295 -287
  3. eodag/api/product/__init__.py +10 -4
  4. eodag/api/product/_assets.py +2 -14
  5. eodag/api/product/_product.py +16 -30
  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 +12 -31
  9. eodag/api/search_result.py +33 -12
  10. eodag/cli.py +35 -19
  11. eodag/config.py +455 -155
  12. eodag/plugins/apis/base.py +13 -7
  13. eodag/plugins/apis/ecmwf.py +16 -7
  14. eodag/plugins/apis/usgs.py +68 -16
  15. eodag/plugins/authentication/aws_auth.py +25 -7
  16. eodag/plugins/authentication/base.py +10 -1
  17. eodag/plugins/authentication/generic.py +14 -3
  18. eodag/plugins/authentication/header.py +12 -4
  19. eodag/plugins/authentication/keycloak.py +41 -22
  20. eodag/plugins/authentication/oauth.py +11 -1
  21. eodag/plugins/authentication/openid_connect.py +183 -167
  22. eodag/plugins/authentication/qsauth.py +12 -4
  23. eodag/plugins/authentication/sas_auth.py +19 -2
  24. eodag/plugins/authentication/token.py +59 -11
  25. eodag/plugins/authentication/token_exchange.py +19 -19
  26. eodag/plugins/crunch/base.py +7 -2
  27. eodag/plugins/crunch/filter_date.py +8 -11
  28. eodag/plugins/crunch/filter_latest_intersect.py +5 -7
  29. eodag/plugins/crunch/filter_latest_tpl_name.py +2 -5
  30. eodag/plugins/crunch/filter_overlap.py +9 -15
  31. eodag/plugins/crunch/filter_property.py +9 -14
  32. eodag/plugins/download/aws.py +84 -99
  33. eodag/plugins/download/base.py +36 -77
  34. eodag/plugins/download/creodias_s3.py +11 -2
  35. eodag/plugins/download/http.py +134 -109
  36. eodag/plugins/download/s3rest.py +37 -43
  37. eodag/plugins/manager.py +173 -41
  38. eodag/plugins/search/__init__.py +9 -9
  39. eodag/plugins/search/base.py +35 -35
  40. eodag/plugins/search/build_search_result.py +55 -64
  41. eodag/plugins/search/cop_marine.py +113 -32
  42. eodag/plugins/search/creodias_s3.py +20 -8
  43. eodag/plugins/search/csw.py +41 -1
  44. eodag/plugins/search/data_request_search.py +119 -14
  45. eodag/plugins/search/qssearch.py +619 -197
  46. eodag/plugins/search/static_stac_search.py +25 -23
  47. eodag/resources/ext_product_types.json +1 -1
  48. eodag/resources/product_types.yml +211 -56
  49. eodag/resources/providers.yml +1762 -1809
  50. eodag/resources/stac.yml +3 -163
  51. eodag/resources/user_conf_template.yml +134 -119
  52. eodag/rest/config.py +1 -2
  53. eodag/rest/constants.py +0 -1
  54. eodag/rest/core.py +70 -92
  55. eodag/rest/errors.py +181 -0
  56. eodag/rest/server.py +24 -330
  57. eodag/rest/stac.py +105 -630
  58. eodag/rest/types/eodag_search.py +17 -15
  59. eodag/rest/types/queryables.py +5 -14
  60. eodag/rest/types/stac_search.py +18 -13
  61. eodag/rest/utils/rfc3339.py +0 -1
  62. eodag/types/__init__.py +24 -6
  63. eodag/types/download_args.py +14 -5
  64. eodag/types/queryables.py +1 -2
  65. eodag/types/search_args.py +10 -11
  66. eodag/types/whoosh.py +0 -2
  67. eodag/utils/__init__.py +97 -136
  68. eodag/utils/constraints.py +0 -8
  69. eodag/utils/exceptions.py +23 -9
  70. eodag/utils/import_system.py +0 -4
  71. eodag/utils/logging.py +37 -80
  72. eodag/utils/notebook.py +4 -4
  73. eodag/utils/requests.py +13 -23
  74. eodag/utils/rest.py +0 -4
  75. eodag/utils/stac_reader.py +3 -15
  76. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/METADATA +41 -24
  77. eodag-3.0.1.dist-info/RECORD +109 -0
  78. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/WHEEL +1 -1
  79. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/entry_points.txt +1 -0
  80. eodag/resources/constraints/climate-dt.json +0 -13
  81. eodag/resources/constraints/extremes-dt.json +0 -8
  82. eodag-3.0.0b2.dist-info/RECORD +0 -110
  83. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/LICENSE +0 -0
  84. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/top_level.txt +0 -0
eodag/rest/core.py CHANGED
@@ -33,6 +33,7 @@ from requests.models import Response as RequestsResponse
33
33
  import eodag
34
34
  from eodag import EOProduct
35
35
  from eodag.api.product.metadata_mapping import (
36
+ DEFAULT_METADATA_MAPPING,
36
37
  NOT_AVAILABLE,
37
38
  OFFLINE_STATUS,
38
39
  ONLINE_STATUS,
@@ -50,6 +51,7 @@ from eodag.rest.constants import (
50
51
  CACHE_KEY_COLLECTIONS,
51
52
  CACHE_KEY_QUERYABLES,
52
53
  )
54
+ from eodag.rest.errors import ResponseSearchError
53
55
  from eodag.rest.stac import StacCatalog, StacCollection, StacCommon, StacItem
54
56
  from eodag.rest.types.eodag_search import EODAGSearch
55
57
  from eodag.rest.types.queryables import (
@@ -80,7 +82,7 @@ from eodag.utils.exceptions import (
80
82
  )
81
83
 
82
84
  if TYPE_CHECKING:
83
- from typing import Any, Callable, Dict, List, Optional, Tuple, Union
85
+ from typing import Any, Dict, List, Optional, Union
84
86
 
85
87
  from fastapi import Request
86
88
  from requests.auth import AuthBase
@@ -105,9 +107,7 @@ def get_home_page_content(base_url: str, ipp: Optional[int] = None) -> str:
105
107
  """Compute eodag service home page content
106
108
 
107
109
  :param base_url: The service root URL
108
- :type base_url: str
109
110
  :param ipp: (optional) Items per page number
110
- :type ipp: int
111
111
  """
112
112
  base_url = base_url.rstrip("/") + "/"
113
113
  content = f"""<h1>EODAG Server</h1><br />
@@ -125,7 +125,6 @@ def format_product_types(product_types: List[Dict[str, Any]]) -> str:
125
125
  """Format product_types
126
126
 
127
127
  :param product_types: A list of EODAG product types as returned by the core api
128
- :type product_types: list
129
128
  """
130
129
  result: List[str] = []
131
130
  for pt in product_types:
@@ -136,22 +135,16 @@ def format_product_types(product_types: List[Dict[str, Any]]) -> str:
136
135
  def search_stac_items(
137
136
  request: Request,
138
137
  search_request: SearchPostRequest,
139
- catalogs: Optional[List[str]] = None,
140
138
  ) -> Dict[str, Any]:
141
139
  """
142
- Search and retrieve STAC items from the given catalogs.
140
+ Search and retrieve STAC items based on the given search request.
143
141
 
144
- This function takes a search request and optional catalogs list, performs a search using EODAG API, and returns a
142
+ This function takes a search request, performs a search using EODAG API, and returns a
145
143
  dictionary of STAC items.
146
144
 
147
145
  :param request: The incoming HTTP request with state information.
148
- :type request: Request
149
- :param search_request: The search criteria for STAC items.
150
- :type search_request: SearchPostRequest
151
- :param catalogs: (optional) A list of catalogs to search within. Defaults to None.
152
- :type catalogs: Optional[List[str]]
146
+ :param search_request: The search criteria for STAC items
153
147
  :returns: A dictionary containing the STAC items and related metadata.
154
- :rtype: Dict[str, Any]
155
148
 
156
149
  The function handles the conversion of search criteria into STAC and EODAG compatible formats, validates the input
157
150
  using pydantic, and constructs the appropriate URLs for querying the STAC API. It also manages pagination and the
@@ -172,60 +165,64 @@ def search_stac_items(
172
165
  if search_request.spatial_filter:
173
166
  stac_args["geometry"] = search_request.spatial_filter
174
167
  try:
175
- eodag_args = EODAGSearch.model_validate(
176
- stac_args, context={"isCatalog": bool(catalogs)}
177
- )
168
+ eodag_args = EODAGSearch.model_validate(stac_args)
178
169
  except pydanticValidationError as e:
179
170
  raise ValidationError(format_pydantic_error(e)) from e
180
171
 
181
172
  catalog_url = re.sub("/items.*", "", request.state.url)
182
-
183
173
  catalog = StacCatalog(
184
- url=(
185
- catalog_url
186
- if catalogs
187
- else catalog_url.replace(
188
- "/search", f"/collections/{eodag_args.productType}"
189
- )
190
- ),
174
+ url=catalog_url.replace("/search", f"/collections/{eodag_args.productType}"),
191
175
  stac_config=stac_config,
192
176
  root=request.state.url_root,
193
177
  provider=eodag_args.provider,
194
178
  eodag_api=eodag_api,
195
- catalogs=catalogs or [eodag_args.productType], # type: ignore
179
+ collection=eodag_args.productType, # type: ignore
196
180
  )
197
181
 
198
182
  # get products by ids
199
183
  if eodag_args.ids:
200
- search_results = SearchResult([])
184
+ results = SearchResult([])
201
185
  for item_id in eodag_args.ids:
202
- sr = eodag_api.search(
203
- id=item_id,
204
- productType=catalogs[0] if catalogs else eodag_args.productType,
205
- provider=eodag_args.provider,
186
+ results.extend(
187
+ eodag_api.search(
188
+ id=item_id,
189
+ productType=eodag_args.productType,
190
+ provider=eodag_args.provider,
191
+ )
206
192
  )
207
- search_results.extend(sr)
208
- search_results.number_matched = len(search_results)
209
- total = len(search_results)
193
+ results.number_matched = len(results)
194
+ total = len(results)
210
195
 
211
196
  elif time_interval_overlap(eodag_args, catalog):
212
197
  criteria = {
213
198
  **catalog.search_args,
214
199
  **eodag_args.model_dump(exclude_none=True),
215
200
  }
216
-
217
- search_results = eodag_api.search(count=True, **criteria)
218
- total = search_results.number_matched
219
- if search_request.crunch:
220
- search_results = crunch_products(
221
- search_results, search_request.crunch, **criteria
222
- )
201
+ # remove provider prefixes
202
+ stac_extensions = stac_config["extensions"]
203
+ keys_to_update = {}
204
+ for key in criteria:
205
+ if ":" in key and key.split(":")[0] not in stac_extensions:
206
+ new_key = key.split(":")[1]
207
+ keys_to_update[key] = new_key
208
+ for key, new_key in keys_to_update.items():
209
+ criteria[new_key] = criteria[key]
210
+ criteria.pop(key)
211
+
212
+ results = eodag_api.search(count=True, **criteria)
213
+ total = results.number_matched or 0
223
214
  else:
224
215
  # return empty results
225
- search_results = SearchResult([], 0)
216
+ results = SearchResult([], 0)
226
217
  total = 0
227
218
 
228
- for record in search_results:
219
+ if len(results) == 0 and results.errors:
220
+ raise ResponseSearchError(results.errors)
221
+
222
+ if search_request.crunch:
223
+ results = crunch_products(results, search_request.crunch, **criteria)
224
+
225
+ for record in results:
229
226
  record.product_type = eodag_api.get_alias_from_product_type(record.product_type)
230
227
 
231
228
  items = StacItem(
@@ -235,7 +232,7 @@ def search_stac_items(
235
232
  eodag_api=eodag_api,
236
233
  root=request.state.url_root,
237
234
  ).get_stac_items(
238
- search_results=search_results,
235
+ search_results=results,
239
236
  total=total,
240
237
  next_link=get_next_link(
241
238
  request, search_request, total, eodag_args.items_per_page
@@ -250,7 +247,7 @@ def search_stac_items(
250
247
 
251
248
  def download_stac_item(
252
249
  request: Request,
253
- catalogs: List[str],
250
+ collection_id: str,
254
251
  item_id: str,
255
252
  provider: Optional[str] = None,
256
253
  asset: Optional[str] = None,
@@ -258,18 +255,13 @@ def download_stac_item(
258
255
  ) -> Response:
259
256
  """Download item
260
257
 
261
- :param catalogs: Catalogs list (only first is used as product_type)
262
- :type catalogs: list
258
+ :param collection_id: id of the product type
263
259
  :param item_id: Product ID
264
- :type item_id: str
265
260
  :param provider: (optional) Chosen provider
266
- :type provider: str
267
261
  :param kwargs: additional download parameters
268
- :type kwargs: Any
269
262
  :returns: a stream of the downloaded data (zip file)
270
- :rtype: Response
271
263
  """
272
- product_type = catalogs[0]
264
+ product_type = collection_id
273
265
 
274
266
  search_results = eodag_api.search(
275
267
  id=item_id, productType=product_type, provider=provider, **kwargs
@@ -350,11 +342,13 @@ def _order_and_update(
350
342
  if (
351
343
  product.properties.get("storageStatus") != ONLINE_STATUS
352
344
  and NOT_AVAILABLE in product.properties.get("orderStatusLink", "")
353
- and hasattr(product.downloader, "orderDownload")
345
+ and hasattr(product.downloader, "order_download")
354
346
  ):
355
347
  # first order
356
348
  logger.debug("Order product")
357
- order_status_dict = product.downloader.orderDownload(product=product, auth=auth)
349
+ order_status_dict = product.downloader.order_download(
350
+ product=product, auth=auth
351
+ )
358
352
  query_args.update(order_status_dict or {})
359
353
 
360
354
  if (
@@ -365,11 +359,11 @@ def _order_and_update(
365
359
  product.properties["storageStatus"] = STAGING_STATUS
366
360
 
367
361
  if product.properties.get("storageStatus") == STAGING_STATUS and hasattr(
368
- product.downloader, "orderDownloadStatus"
362
+ product.downloader, "order_download_status"
369
363
  ):
370
364
  # check order status if needed
371
365
  logger.debug("Checking product order status")
372
- product.downloader.orderDownloadStatus(product=product, auth=auth)
366
+ product.downloader.order_download_status(product=product, auth=auth)
373
367
 
374
368
  if product.properties.get("storageStatus") != ONLINE_STATUS:
375
369
  raise NotAvailableError("Product is not available yet")
@@ -381,7 +375,6 @@ def get_detailled_collections_list() -> List[Dict[str, Any]]:
381
375
  config dicts
382
376
 
383
377
  :returns: List of config dicts
384
- :rtype: list
385
378
  """
386
379
  return eodag_api.list_product_types(fetch_providers=False)
387
380
 
@@ -398,15 +391,10 @@ async def all_collections(
398
391
  """Build STAC collections
399
392
 
400
393
  :param url: Requested URL
401
- :type url: str
402
394
  :param root: The API root
403
- :type root: str
404
395
  :param filters: Search collections filters
405
- :type filters: CollectionsSearchRequest
406
396
  :param provider: (optional) Chosen provider
407
- :type provider: str
408
- :returns: Collections dictionnary
409
- :rtype: dict
397
+ :returns: Collections dictionary
410
398
  """
411
399
 
412
400
  async def _fetch() -> Dict[str, Any]:
@@ -439,7 +427,9 @@ async def all_collections(
439
427
  collections = format_dict_items(collections, **format_args)
440
428
  return collections
441
429
 
442
- hashed_collections = hash(f"{provider}:{q}:{platform}:{instrument}:{constellation}")
430
+ hashed_collections = hash(
431
+ f"{provider}:{q}:{platform}:{instrument}:{constellation}:{datetime}"
432
+ )
443
433
  cache_key = f"{CACHE_KEY_COLLECTIONS}:{hashed_collections}"
444
434
  return await cached(_fetch, cache_key, request)
445
435
 
@@ -450,15 +440,10 @@ async def get_collection(
450
440
  """Build STAC collection by id
451
441
 
452
442
  :param url: Requested URL
453
- :type url: str
454
443
  :param root: API root
455
- :type root: str
456
444
  :param collection_id: Product_type as ID of the collection
457
- :type collection_id: str
458
445
  :param provider: (optional) Chosen provider
459
- :type provider: str
460
446
  :returns: Collection dictionary
461
- :rtype: dict
462
447
  """
463
448
 
464
449
  async def _fetch() -> Dict[str, Any]:
@@ -483,21 +468,14 @@ async def get_collection(
483
468
  async def get_stac_catalogs(
484
469
  request: Request,
485
470
  url: str,
486
- catalogs: Optional[Tuple[str, ...]] = None,
487
471
  provider: Optional[str] = None,
488
472
  ) -> Dict[str, Any]:
489
473
  """Build STAC catalog
490
474
 
491
475
  :param url: Requested URL
492
- :type url: str
493
476
  :param root: (optional) API root
494
- :type root: str
495
- :param catalogs: (optional) Catalogs list
496
- :type catalogs: list
497
477
  :param provider: (optional) Chosen provider
498
- :type provider: str
499
478
  :returns: Catalog dictionary
500
- :rtype: dict
501
479
  """
502
480
 
503
481
  async def _fetch() -> Dict[str, Any]:
@@ -507,13 +485,9 @@ async def get_stac_catalogs(
507
485
  root=request.state.url_root,
508
486
  provider=provider,
509
487
  eodag_api=eodag_api,
510
- catalogs=list(catalogs) if catalogs else None,
511
488
  ).data
512
489
 
513
- hashed_catalogs = hash(":".join(catalogs) if catalogs else None)
514
- return await cached(
515
- _fetch, f"{CACHE_KEY_COLLECTION}:{provider}:{hashed_catalogs}", request
516
- )
490
+ return await cached(_fetch, f"{CACHE_KEY_COLLECTION}:{provider}", request)
517
491
 
518
492
 
519
493
  def time_interval_overlap(eodag_args: EODAGSearch, catalog: StacCatalog) -> bool:
@@ -563,8 +537,7 @@ def time_interval_overlap(eodag_args: EODAGSearch, catalog: StacCatalog) -> bool
563
537
  def get_stac_conformance() -> Dict[str, str]:
564
538
  """Build STAC conformance
565
539
 
566
- :returns: conformance dictionnary
567
- :rtype: dict
540
+ :returns: conformance dictionary
568
541
  """
569
542
  return stac_config["conformance"]
570
543
 
@@ -573,7 +546,6 @@ def get_stac_api_version() -> str:
573
546
  """Get STAC API version
574
547
 
575
548
  :returns: STAC API version
576
- :rtype: str
577
549
  """
578
550
  return stac_config["stac_api_version"]
579
551
 
@@ -583,14 +555,12 @@ def get_stac_extension_oseo(url: str) -> Dict[str, str]:
583
555
  """Build STAC OGC / OpenSearch Extension for EO
584
556
 
585
557
  :param url: Requested URL
586
- :type url: str
587
- :returns: Catalog dictionnary
588
- :rtype: dict
558
+ :returns: Catalog dictionary
589
559
  """
590
560
 
591
- apply_method: Callable[[str, str], str] = lambda _, x: str(x).replace(
592
- "$.product.", "$."
593
- )
561
+ def apply_method(_: str, x: str) -> str:
562
+ return str(x).replace("$.product.", "$.")
563
+
594
564
  item_mapping = dict_items_recursive_apply(stac_config["item"], apply_method)
595
565
 
596
566
  # all properties as string type by default
@@ -616,9 +586,7 @@ async def get_queryables(
616
586
  """Fetch the queryable properties for a collection.
617
587
 
618
588
  :param collection_id: The ID of the collection.
619
- :type collection_id: str
620
589
  :returns: A set containing the STAC standardized queryable properties for a collection.
621
- :rtype Dict[str, StacQueryableProperty]: set
622
590
  """
623
591
 
624
592
  async def _fetch() -> Dict[str, Any]:
@@ -635,8 +603,18 @@ async def get_queryables(
635
603
  stac_queryables: Dict[str, StacQueryableProperty] = deepcopy(
636
604
  StacQueryables.default_properties
637
605
  )
606
+ # get stac default properties to set prefixes
607
+ stac_item_properties = list(stac_config["item"]["properties"].values())
608
+ stac_item_properties.extend(list(stac_queryables.keys()))
609
+ ignore = stac_config["metadata_ignore"]
610
+ stac_item_properties.extend(ignore)
611
+ default_mapping = DEFAULT_METADATA_MAPPING.keys()
638
612
  for param, queryable in python_queryables.items():
639
- stac_param = EODAGSearch.to_stac(param)
613
+ if param in default_mapping and not any(
614
+ param in str(prop) for prop in stac_item_properties
615
+ ):
616
+ param = f"oseo:{param}"
617
+ stac_param = EODAGSearch.to_stac(param, stac_item_properties, provider)
640
618
  # only keep "datetime" queryable for dates
641
619
  if stac_param in stac_queryables or stac_param in (
642
620
  "start_datetime",
eodag/rest/errors.py ADDED
@@ -0,0 +1,181 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright 2024, CS GROUP - France, https://www.csgroup.eu/
3
+ #
4
+ # This file is part of EODAG project
5
+ # https://www.github.com/CS-SI/EODAG
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ import logging
19
+ from typing import Dict, List, Tuple, Union
20
+
21
+ from fastapi import FastAPI, Request
22
+ from fastapi.responses import ORJSONResponse
23
+ from starlette import status
24
+ from starlette.exceptions import HTTPException as StarletteHTTPException
25
+
26
+ from eodag.rest.types.eodag_search import EODAGSearch
27
+ from eodag.utils.exceptions import (
28
+ AuthenticationError,
29
+ DownloadError,
30
+ EodagError,
31
+ MisconfiguredError,
32
+ NoMatchingProductType,
33
+ NotAvailableError,
34
+ RequestError,
35
+ TimeOutError,
36
+ UnsupportedProductType,
37
+ UnsupportedProvider,
38
+ ValidationError,
39
+ )
40
+
41
+ EODAG_DEFAULT_STATUS_CODES = {
42
+ AuthenticationError: status.HTTP_500_INTERNAL_SERVER_ERROR,
43
+ DownloadError: status.HTTP_500_INTERNAL_SERVER_ERROR,
44
+ MisconfiguredError: status.HTTP_500_INTERNAL_SERVER_ERROR,
45
+ NotAvailableError: status.HTTP_404_NOT_FOUND,
46
+ NoMatchingProductType: status.HTTP_404_NOT_FOUND,
47
+ TimeOutError: status.HTTP_504_GATEWAY_TIMEOUT,
48
+ UnsupportedProductType: status.HTTP_404_NOT_FOUND,
49
+ UnsupportedProvider: status.HTTP_404_NOT_FOUND,
50
+ ValidationError: status.HTTP_400_BAD_REQUEST,
51
+ }
52
+
53
+ logger = logging.getLogger("eodag.rest.server")
54
+
55
+
56
+ class ResponseSearchError(Exception):
57
+ """Represent a EODAG search error response"""
58
+
59
+ def __init__(self, errors: List[Tuple[str, Exception]]) -> None:
60
+ self._errors = errors
61
+
62
+ @property
63
+ def errors(self) -> List[Dict[str, Union[str, int]]]:
64
+ """return errors as a list of dict"""
65
+ error_list: List[Dict[str, Union[str, int]]] = []
66
+ for name, exception in self._errors:
67
+
68
+ error_dict: Dict[str, Union[str, int]] = {
69
+ "provider": name,
70
+ "error": exception.__class__.__name__,
71
+ }
72
+
73
+ if exception.args:
74
+ error_dict["message"] = exception.args[0]
75
+
76
+ if len(exception.args) > 1:
77
+ error_dict["detail"] = " ".join([str(i) for i in exception.args[1:]])
78
+
79
+ error_dict["status_code"] = EODAG_DEFAULT_STATUS_CODES.get(
80
+ type(exception), getattr(exception, "status_code", 500)
81
+ )
82
+
83
+ if type(exception) in (MisconfiguredError, AuthenticationError):
84
+ logger.error("%s: %s", type(exception).__name__, str(exception))
85
+ error_dict[
86
+ "message"
87
+ ] = "Internal server error: please contact the administrator"
88
+ error_dict.pop("detail", None)
89
+
90
+ if type(exception) is ValidationError:
91
+ for error_param in exception.parameters:
92
+ stac_param = EODAGSearch.to_stac(error_param)
93
+ exception.message = exception.message.replace(
94
+ error_param, stac_param
95
+ )
96
+ error_dict["message"] = exception.message
97
+
98
+ error_list.append(error_dict)
99
+
100
+ return error_list
101
+
102
+ @property
103
+ def status_code(self) -> int:
104
+ """get global errors status code"""
105
+ if len(self._errors) == 1 and type(self.errors[0]["status_code"]) is int:
106
+ return self.errors[0]["status_code"]
107
+
108
+ return 400
109
+
110
+
111
+ async def response_search_error_handler(
112
+ request: Request, exc: Exception
113
+ ) -> ORJSONResponse:
114
+ """Handle ResponseSearchError exceptions"""
115
+ if not isinstance(exc, ResponseSearchError):
116
+ return starlette_exception_handler(request, exc)
117
+
118
+ return ORJSONResponse(
119
+ status_code=exc.status_code,
120
+ content={"errors": exc.errors},
121
+ )
122
+
123
+
124
+ async def eodag_errors_handler(request: Request, exc: Exception) -> ORJSONResponse:
125
+ """Handler for EODAG errors"""
126
+ if not isinstance(exc, EodagError):
127
+ return starlette_exception_handler(request, exc)
128
+
129
+ exception_status_code = getattr(exc, "status_code", None)
130
+ default_status_code = exception_status_code or 500
131
+ code = EODAG_DEFAULT_STATUS_CODES.get(type(exc), default_status_code)
132
+
133
+ detail = f"{type(exc).__name__}: {str(exc)}"
134
+
135
+ if type(exc) in (MisconfiguredError, AuthenticationError, TimeOutError):
136
+ logger.error("%s: %s", type(exc).__name__, str(exc))
137
+
138
+ if type(exc) in (MisconfiguredError, AuthenticationError):
139
+ detail = "Internal server error: please contact the administrator"
140
+
141
+ if type(exc) is ValidationError:
142
+ for error_param in exc.parameters:
143
+ stac_param = EODAGSearch.to_stac(error_param)
144
+ exc.message = exc.message.replace(error_param, stac_param)
145
+ detail = exc.message
146
+
147
+ return ORJSONResponse(
148
+ status_code=code,
149
+ content={"description": detail},
150
+ )
151
+
152
+
153
+ def starlette_exception_handler(request: Request, error: Exception) -> ORJSONResponse:
154
+ """Default errors handle"""
155
+ description = (
156
+ getattr(error, "description", None)
157
+ or getattr(error, "detail", None)
158
+ or str(error)
159
+ )
160
+ return ORJSONResponse(
161
+ status_code=getattr(error, "status_code", 500),
162
+ content={"description": description},
163
+ )
164
+
165
+
166
+ def add_exception_handlers(app: FastAPI) -> None:
167
+ """Add exception handlers to the FastAPI application.
168
+
169
+ Args:
170
+ app: the FastAPI application.
171
+
172
+ Returns:
173
+ None
174
+ """
175
+ app.add_exception_handler(StarletteHTTPException, starlette_exception_handler)
176
+
177
+ app.add_exception_handler(RequestError, eodag_errors_handler)
178
+ for exc in EODAG_DEFAULT_STATUS_CODES:
179
+ app.add_exception_handler(exc, eodag_errors_handler)
180
+
181
+ app.add_exception_handler(ResponseSearchError, response_search_error_handler)