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.
- eodag/__init__.py +6 -8
- eodag/api/core.py +295 -287
- eodag/api/product/__init__.py +10 -4
- eodag/api/product/_assets.py +2 -14
- eodag/api/product/_product.py +16 -30
- eodag/api/product/drivers/__init__.py +7 -2
- eodag/api/product/drivers/base.py +0 -3
- eodag/api/product/metadata_mapping.py +12 -31
- eodag/api/search_result.py +33 -12
- eodag/cli.py +35 -19
- eodag/config.py +455 -155
- eodag/plugins/apis/base.py +13 -7
- eodag/plugins/apis/ecmwf.py +16 -7
- eodag/plugins/apis/usgs.py +68 -16
- eodag/plugins/authentication/aws_auth.py +25 -7
- eodag/plugins/authentication/base.py +10 -1
- eodag/plugins/authentication/generic.py +14 -3
- eodag/plugins/authentication/header.py +12 -4
- eodag/plugins/authentication/keycloak.py +41 -22
- eodag/plugins/authentication/oauth.py +11 -1
- eodag/plugins/authentication/openid_connect.py +183 -167
- eodag/plugins/authentication/qsauth.py +12 -4
- eodag/plugins/authentication/sas_auth.py +19 -2
- eodag/plugins/authentication/token.py +59 -11
- eodag/plugins/authentication/token_exchange.py +19 -19
- eodag/plugins/crunch/base.py +7 -2
- eodag/plugins/crunch/filter_date.py +8 -11
- eodag/plugins/crunch/filter_latest_intersect.py +5 -7
- eodag/plugins/crunch/filter_latest_tpl_name.py +2 -5
- eodag/plugins/crunch/filter_overlap.py +9 -15
- eodag/plugins/crunch/filter_property.py +9 -14
- eodag/plugins/download/aws.py +84 -99
- eodag/plugins/download/base.py +36 -77
- eodag/plugins/download/creodias_s3.py +11 -2
- eodag/plugins/download/http.py +134 -109
- eodag/plugins/download/s3rest.py +37 -43
- eodag/plugins/manager.py +173 -41
- eodag/plugins/search/__init__.py +9 -9
- eodag/plugins/search/base.py +35 -35
- eodag/plugins/search/build_search_result.py +55 -64
- eodag/plugins/search/cop_marine.py +113 -32
- eodag/plugins/search/creodias_s3.py +20 -8
- eodag/plugins/search/csw.py +41 -1
- eodag/plugins/search/data_request_search.py +119 -14
- eodag/plugins/search/qssearch.py +619 -197
- eodag/plugins/search/static_stac_search.py +25 -23
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +211 -56
- eodag/resources/providers.yml +1762 -1809
- eodag/resources/stac.yml +3 -163
- eodag/resources/user_conf_template.yml +134 -119
- eodag/rest/config.py +1 -2
- eodag/rest/constants.py +0 -1
- eodag/rest/core.py +70 -92
- eodag/rest/errors.py +181 -0
- eodag/rest/server.py +24 -330
- eodag/rest/stac.py +105 -630
- eodag/rest/types/eodag_search.py +17 -15
- eodag/rest/types/queryables.py +5 -14
- eodag/rest/types/stac_search.py +18 -13
- eodag/rest/utils/rfc3339.py +0 -1
- eodag/types/__init__.py +24 -6
- eodag/types/download_args.py +14 -5
- eodag/types/queryables.py +1 -2
- eodag/types/search_args.py +10 -11
- eodag/types/whoosh.py +0 -2
- eodag/utils/__init__.py +97 -136
- eodag/utils/constraints.py +0 -8
- eodag/utils/exceptions.py +23 -9
- eodag/utils/import_system.py +0 -4
- eodag/utils/logging.py +37 -80
- eodag/utils/notebook.py +4 -4
- eodag/utils/requests.py +13 -23
- eodag/utils/rest.py +0 -4
- eodag/utils/stac_reader.py +3 -15
- {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/METADATA +41 -24
- eodag-3.0.1.dist-info/RECORD +109 -0
- {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/WHEEL +1 -1
- {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/entry_points.txt +1 -0
- eodag/resources/constraints/climate-dt.json +0 -13
- eodag/resources/constraints/extremes-dt.json +0 -8
- eodag-3.0.0b2.dist-info/RECORD +0 -110
- {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/LICENSE +0 -0
- {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,
|
|
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
|
|
140
|
+
Search and retrieve STAC items based on the given search request.
|
|
143
141
|
|
|
144
|
-
This function takes a search request
|
|
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
|
-
:
|
|
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
|
-
|
|
179
|
+
collection=eodag_args.productType, # type: ignore
|
|
196
180
|
)
|
|
197
181
|
|
|
198
182
|
# get products by ids
|
|
199
183
|
if eodag_args.ids:
|
|
200
|
-
|
|
184
|
+
results = SearchResult([])
|
|
201
185
|
for item_id in eodag_args.ids:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
208
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
216
|
+
results = SearchResult([], 0)
|
|
226
217
|
total = 0
|
|
227
218
|
|
|
228
|
-
|
|
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=
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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, "
|
|
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.
|
|
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, "
|
|
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.
|
|
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
|
-
:
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
:
|
|
587
|
-
:returns: Catalog dictionnary
|
|
588
|
-
:rtype: dict
|
|
558
|
+
:returns: Catalog dictionary
|
|
589
559
|
"""
|
|
590
560
|
|
|
591
|
-
apply_method:
|
|
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
|
-
|
|
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)
|