eodag 3.0.0b3__py3-none-any.whl → 3.1.0b1__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/api/core.py +292 -198
- eodag/api/product/_assets.py +6 -6
- eodag/api/product/_product.py +18 -18
- eodag/api/product/metadata_mapping.py +51 -14
- eodag/api/search_result.py +29 -3
- eodag/cli.py +57 -20
- eodag/config.py +413 -117
- eodag/plugins/apis/base.py +10 -4
- eodag/plugins/apis/ecmwf.py +49 -16
- eodag/plugins/apis/usgs.py +30 -7
- eodag/plugins/authentication/aws_auth.py +14 -5
- 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 +178 -163
- eodag/plugins/authentication/qsauth.py +12 -4
- eodag/plugins/authentication/sas_auth.py +19 -2
- eodag/plugins/authentication/token.py +93 -15
- eodag/plugins/authentication/token_exchange.py +19 -19
- eodag/plugins/crunch/base.py +4 -1
- eodag/plugins/crunch/filter_date.py +5 -2
- eodag/plugins/crunch/filter_latest_intersect.py +5 -4
- eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
- eodag/plugins/crunch/filter_overlap.py +5 -7
- eodag/plugins/crunch/filter_property.py +6 -6
- eodag/plugins/download/aws.py +50 -34
- eodag/plugins/download/base.py +41 -50
- eodag/plugins/download/creodias_s3.py +40 -2
- eodag/plugins/download/http.py +221 -195
- eodag/plugins/download/s3rest.py +25 -25
- eodag/plugins/manager.py +168 -23
- eodag/plugins/search/base.py +106 -39
- eodag/plugins/search/build_search_result.py +1065 -324
- eodag/plugins/search/cop_marine.py +112 -29
- eodag/plugins/search/creodias_s3.py +45 -24
- eodag/plugins/search/csw.py +41 -1
- eodag/plugins/search/data_request_search.py +109 -9
- eodag/plugins/search/qssearch.py +549 -257
- eodag/plugins/search/static_stac_search.py +20 -21
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +577 -87
- eodag/resources/providers.yml +1619 -2776
- eodag/resources/stac.yml +3 -163
- eodag/resources/user_conf_template.yml +112 -97
- eodag/rest/config.py +1 -2
- eodag/rest/constants.py +0 -1
- eodag/rest/core.py +138 -98
- eodag/rest/errors.py +181 -0
- eodag/rest/server.py +55 -329
- eodag/rest/stac.py +93 -544
- eodag/rest/types/eodag_search.py +19 -8
- eodag/rest/types/queryables.py +6 -8
- eodag/rest/types/stac_search.py +11 -2
- eodag/rest/utils/__init__.py +3 -0
- eodag/types/__init__.py +71 -18
- eodag/types/download_args.py +3 -3
- eodag/types/queryables.py +180 -73
- eodag/types/search_args.py +3 -3
- eodag/types/whoosh.py +126 -0
- eodag/utils/__init__.py +147 -66
- eodag/utils/exceptions.py +47 -26
- eodag/utils/logging.py +37 -77
- eodag/utils/repr.py +65 -6
- eodag/utils/requests.py +11 -13
- eodag/utils/stac_reader.py +1 -1
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/METADATA +80 -81
- eodag-3.1.0b1.dist-info/RECORD +108 -0
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/WHEEL +1 -1
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/entry_points.txt +4 -2
- eodag/resources/constraints/climate-dt.json +0 -13
- eodag/resources/constraints/extremes-dt.json +0 -8
- eodag/utils/constraints.py +0 -244
- eodag-3.0.0b3.dist-info/RECORD +0 -110
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/LICENSE +0 -0
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/top_level.txt +0 -0
eodag/rest/core.py
CHANGED
|
@@ -50,13 +50,10 @@ from eodag.rest.constants import (
|
|
|
50
50
|
CACHE_KEY_COLLECTIONS,
|
|
51
51
|
CACHE_KEY_QUERYABLES,
|
|
52
52
|
)
|
|
53
|
+
from eodag.rest.errors import ResponseSearchError
|
|
53
54
|
from eodag.rest.stac import StacCatalog, StacCollection, StacCommon, StacItem
|
|
54
55
|
from eodag.rest.types.eodag_search import EODAGSearch
|
|
55
|
-
from eodag.rest.types.queryables import
|
|
56
|
-
QueryablesGetParams,
|
|
57
|
-
StacQueryableProperty,
|
|
58
|
-
StacQueryables,
|
|
59
|
-
)
|
|
56
|
+
from eodag.rest.types.queryables import QueryablesGetParams, StacQueryables
|
|
60
57
|
from eodag.rest.types.stac_search import SearchPostRequest
|
|
61
58
|
from eodag.rest.utils import (
|
|
62
59
|
Cruncher,
|
|
@@ -80,7 +77,7 @@ from eodag.utils.exceptions import (
|
|
|
80
77
|
)
|
|
81
78
|
|
|
82
79
|
if TYPE_CHECKING:
|
|
83
|
-
from typing import Any, Dict, List, Optional,
|
|
80
|
+
from typing import Any, Dict, List, Optional, Union
|
|
84
81
|
|
|
85
82
|
from fastapi import Request
|
|
86
83
|
from requests.auth import AuthBase
|
|
@@ -133,17 +130,15 @@ def format_product_types(product_types: List[Dict[str, Any]]) -> str:
|
|
|
133
130
|
def search_stac_items(
|
|
134
131
|
request: Request,
|
|
135
132
|
search_request: SearchPostRequest,
|
|
136
|
-
catalogs: Optional[List[str]] = None,
|
|
137
133
|
) -> Dict[str, Any]:
|
|
138
134
|
"""
|
|
139
|
-
Search and retrieve STAC items
|
|
135
|
+
Search and retrieve STAC items based on the given search request.
|
|
140
136
|
|
|
141
|
-
This function takes a search request
|
|
137
|
+
This function takes a search request, performs a search using EODAG API, and returns a
|
|
142
138
|
dictionary of STAC items.
|
|
143
139
|
|
|
144
140
|
:param request: The incoming HTTP request with state information.
|
|
145
|
-
:param search_request: The search criteria for STAC items
|
|
146
|
-
:param catalogs: (optional) A list of catalogs to search within. Defaults to None.
|
|
141
|
+
:param search_request: The search criteria for STAC items
|
|
147
142
|
:returns: A dictionary containing the STAC items and related metadata.
|
|
148
143
|
|
|
149
144
|
The function handles the conversion of search criteria into STAC and EODAG compatible formats, validates the input
|
|
@@ -165,60 +160,54 @@ def search_stac_items(
|
|
|
165
160
|
if search_request.spatial_filter:
|
|
166
161
|
stac_args["geometry"] = search_request.spatial_filter
|
|
167
162
|
try:
|
|
168
|
-
eodag_args = EODAGSearch.model_validate(
|
|
169
|
-
stac_args, context={"isCatalog": bool(catalogs)}
|
|
170
|
-
)
|
|
163
|
+
eodag_args = EODAGSearch.model_validate(stac_args)
|
|
171
164
|
except pydanticValidationError as e:
|
|
172
165
|
raise ValidationError(format_pydantic_error(e)) from e
|
|
173
166
|
|
|
174
167
|
catalog_url = re.sub("/items.*", "", request.state.url)
|
|
175
|
-
|
|
176
168
|
catalog = StacCatalog(
|
|
177
|
-
url=(
|
|
178
|
-
catalog_url
|
|
179
|
-
if catalogs
|
|
180
|
-
else catalog_url.replace(
|
|
181
|
-
"/search", f"/collections/{eodag_args.productType}"
|
|
182
|
-
)
|
|
183
|
-
),
|
|
169
|
+
url=catalog_url.replace("/search", f"/collections/{eodag_args.productType}"),
|
|
184
170
|
stac_config=stac_config,
|
|
185
171
|
root=request.state.url_root,
|
|
186
172
|
provider=eodag_args.provider,
|
|
187
173
|
eodag_api=eodag_api,
|
|
188
|
-
|
|
174
|
+
collection=eodag_args.productType, # type: ignore
|
|
189
175
|
)
|
|
190
176
|
|
|
191
177
|
# get products by ids
|
|
192
178
|
if eodag_args.ids:
|
|
193
|
-
|
|
179
|
+
results = SearchResult([])
|
|
194
180
|
for item_id in eodag_args.ids:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
181
|
+
results.extend(
|
|
182
|
+
eodag_api.search(
|
|
183
|
+
id=item_id,
|
|
184
|
+
productType=eodag_args.productType,
|
|
185
|
+
provider=eodag_args.provider,
|
|
186
|
+
)
|
|
199
187
|
)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
total = len(search_results)
|
|
203
|
-
|
|
204
|
-
elif time_interval_overlap(eodag_args, catalog):
|
|
205
|
-
criteria = {
|
|
206
|
-
**catalog.search_args,
|
|
207
|
-
**eodag_args.model_dump(exclude_none=True),
|
|
208
|
-
}
|
|
188
|
+
results.number_matched = len(results)
|
|
189
|
+
total = len(results)
|
|
209
190
|
|
|
210
|
-
search_results = eodag_api.search(count=True, **criteria)
|
|
211
|
-
total = search_results.number_matched or 0
|
|
212
|
-
if search_request.crunch:
|
|
213
|
-
search_results = crunch_products(
|
|
214
|
-
search_results, search_request.crunch, **criteria
|
|
215
|
-
)
|
|
216
191
|
else:
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
192
|
+
criteria = eodag_args.model_dump(exclude_none=True)
|
|
193
|
+
# remove provider prefixes
|
|
194
|
+
# quickfix for ecmwf fake extension to not impact items creation
|
|
195
|
+
stac_extensions = list(stac_config["extensions"].keys()) + ["ecmwf"]
|
|
196
|
+
for key in list(criteria):
|
|
197
|
+
if ":" in key and key.split(":")[0] not in stac_extensions:
|
|
198
|
+
new_key = key.split(":")[1]
|
|
199
|
+
criteria[new_key] = criteria.pop(key)
|
|
200
|
+
|
|
201
|
+
results = eodag_api.search(count=True, **criteria)
|
|
202
|
+
total = results.number_matched or 0
|
|
203
|
+
|
|
204
|
+
if len(results) == 0 and results.errors:
|
|
205
|
+
raise ResponseSearchError(results.errors)
|
|
206
|
+
|
|
207
|
+
if search_request.crunch:
|
|
208
|
+
results = crunch_products(results, search_request.crunch, **criteria)
|
|
220
209
|
|
|
221
|
-
for record in
|
|
210
|
+
for record in results:
|
|
222
211
|
record.product_type = eodag_api.get_alias_from_product_type(record.product_type)
|
|
223
212
|
|
|
224
213
|
items = StacItem(
|
|
@@ -228,7 +217,7 @@ def search_stac_items(
|
|
|
228
217
|
eodag_api=eodag_api,
|
|
229
218
|
root=request.state.url_root,
|
|
230
219
|
).get_stac_items(
|
|
231
|
-
search_results=
|
|
220
|
+
search_results=results,
|
|
232
221
|
total=total,
|
|
233
222
|
next_link=get_next_link(
|
|
234
223
|
request, search_request, total, eodag_args.items_per_page
|
|
@@ -243,7 +232,7 @@ def search_stac_items(
|
|
|
243
232
|
|
|
244
233
|
def download_stac_item(
|
|
245
234
|
request: Request,
|
|
246
|
-
|
|
235
|
+
collection_id: str,
|
|
247
236
|
item_id: str,
|
|
248
237
|
provider: Optional[str] = None,
|
|
249
238
|
asset: Optional[str] = None,
|
|
@@ -251,13 +240,13 @@ def download_stac_item(
|
|
|
251
240
|
) -> Response:
|
|
252
241
|
"""Download item
|
|
253
242
|
|
|
254
|
-
:param
|
|
243
|
+
:param collection_id: id of the product type
|
|
255
244
|
:param item_id: Product ID
|
|
256
245
|
:param provider: (optional) Chosen provider
|
|
257
246
|
:param kwargs: additional download parameters
|
|
258
247
|
:returns: a stream of the downloaded data (zip file)
|
|
259
248
|
"""
|
|
260
|
-
product_type =
|
|
249
|
+
product_type = collection_id
|
|
261
250
|
|
|
262
251
|
search_results = eodag_api.search(
|
|
263
252
|
id=item_id, productType=product_type, provider=provider, **kwargs
|
|
@@ -338,13 +327,11 @@ def _order_and_update(
|
|
|
338
327
|
if (
|
|
339
328
|
product.properties.get("storageStatus") != ONLINE_STATUS
|
|
340
329
|
and NOT_AVAILABLE in product.properties.get("orderStatusLink", "")
|
|
341
|
-
and hasattr(product.downloader, "
|
|
330
|
+
and hasattr(product.downloader, "_order")
|
|
342
331
|
):
|
|
343
332
|
# first order
|
|
344
333
|
logger.debug("Order product")
|
|
345
|
-
order_status_dict = product.downloader.
|
|
346
|
-
product=product, auth=auth
|
|
347
|
-
)
|
|
334
|
+
order_status_dict = product.downloader._order(product=product, auth=auth)
|
|
348
335
|
query_args.update(order_status_dict or {})
|
|
349
336
|
|
|
350
337
|
if (
|
|
@@ -355,11 +342,11 @@ def _order_and_update(
|
|
|
355
342
|
product.properties["storageStatus"] = STAGING_STATUS
|
|
356
343
|
|
|
357
344
|
if product.properties.get("storageStatus") == STAGING_STATUS and hasattr(
|
|
358
|
-
product.downloader, "
|
|
345
|
+
product.downloader, "_order_status"
|
|
359
346
|
):
|
|
360
347
|
# check order status if needed
|
|
361
348
|
logger.debug("Checking product order status")
|
|
362
|
-
product.downloader.
|
|
349
|
+
product.downloader._order_status(product=product, auth=auth)
|
|
363
350
|
|
|
364
351
|
if product.properties.get("storageStatus") != ONLINE_STATUS:
|
|
365
352
|
raise NotAvailableError("Product is not available yet")
|
|
@@ -390,7 +377,7 @@ async def all_collections(
|
|
|
390
377
|
:param root: The API root
|
|
391
378
|
:param filters: Search collections filters
|
|
392
379
|
:param provider: (optional) Chosen provider
|
|
393
|
-
:returns: Collections
|
|
380
|
+
:returns: Collections dictionary
|
|
394
381
|
"""
|
|
395
382
|
|
|
396
383
|
async def _fetch() -> Dict[str, Any]:
|
|
@@ -413,7 +400,10 @@ async def all_collections(
|
|
|
413
400
|
# # parse f-strings
|
|
414
401
|
format_args = deepcopy(stac_config)
|
|
415
402
|
format_args["collections"].update(
|
|
416
|
-
{
|
|
403
|
+
{
|
|
404
|
+
"url": stac_collection.url,
|
|
405
|
+
"root": stac_collection.root,
|
|
406
|
+
}
|
|
417
407
|
)
|
|
418
408
|
|
|
419
409
|
collections["links"] = [
|
|
@@ -423,7 +413,9 @@ async def all_collections(
|
|
|
423
413
|
collections = format_dict_items(collections, **format_args)
|
|
424
414
|
return collections
|
|
425
415
|
|
|
426
|
-
hashed_collections = hash(
|
|
416
|
+
hashed_collections = hash(
|
|
417
|
+
f"{provider}:{q}:{platform}:{instrument}:{constellation}:{datetime}"
|
|
418
|
+
)
|
|
427
419
|
cache_key = f"{CACHE_KEY_COLLECTIONS}:{hashed_collections}"
|
|
428
420
|
return await cached(_fetch, cache_key, request)
|
|
429
421
|
|
|
@@ -462,14 +454,12 @@ async def get_collection(
|
|
|
462
454
|
async def get_stac_catalogs(
|
|
463
455
|
request: Request,
|
|
464
456
|
url: str,
|
|
465
|
-
catalogs: Optional[Tuple[str, ...]] = None,
|
|
466
457
|
provider: Optional[str] = None,
|
|
467
458
|
) -> Dict[str, Any]:
|
|
468
459
|
"""Build STAC catalog
|
|
469
460
|
|
|
470
461
|
:param url: Requested URL
|
|
471
462
|
:param root: (optional) API root
|
|
472
|
-
:param catalogs: (optional) Catalogs list
|
|
473
463
|
:param provider: (optional) Chosen provider
|
|
474
464
|
:returns: Catalog dictionary
|
|
475
465
|
"""
|
|
@@ -481,13 +471,9 @@ async def get_stac_catalogs(
|
|
|
481
471
|
root=request.state.url_root,
|
|
482
472
|
provider=provider,
|
|
483
473
|
eodag_api=eodag_api,
|
|
484
|
-
catalogs=list(catalogs) if catalogs else None,
|
|
485
474
|
).data
|
|
486
475
|
|
|
487
|
-
|
|
488
|
-
return await cached(
|
|
489
|
-
_fetch, f"{CACHE_KEY_COLLECTION}:{provider}:{hashed_catalogs}", request
|
|
490
|
-
)
|
|
476
|
+
return await cached(_fetch, f"{CACHE_KEY_COLLECTION}:{provider}", request)
|
|
491
477
|
|
|
492
478
|
|
|
493
479
|
def time_interval_overlap(eodag_args: EODAGSearch, catalog: StacCatalog) -> bool:
|
|
@@ -495,7 +481,10 @@ def time_interval_overlap(eodag_args: EODAGSearch, catalog: StacCatalog) -> bool
|
|
|
495
481
|
# check if time filtering appears both in search arguments and catalog
|
|
496
482
|
# (for catalogs built by date: i.e. `year/2020/month/05`)
|
|
497
483
|
if not set(["start", "end"]) <= set(eodag_args.model_dump().keys()) or not set(
|
|
498
|
-
[
|
|
484
|
+
[
|
|
485
|
+
"start",
|
|
486
|
+
"end",
|
|
487
|
+
]
|
|
499
488
|
) <= set(catalog.search_args.keys()):
|
|
500
489
|
return True
|
|
501
490
|
|
|
@@ -537,7 +526,7 @@ def time_interval_overlap(eodag_args: EODAGSearch, catalog: StacCatalog) -> bool
|
|
|
537
526
|
def get_stac_conformance() -> Dict[str, str]:
|
|
538
527
|
"""Build STAC conformance
|
|
539
528
|
|
|
540
|
-
:returns: conformance
|
|
529
|
+
:returns: conformance dictionary
|
|
541
530
|
"""
|
|
542
531
|
return stac_config["conformance"]
|
|
543
532
|
|
|
@@ -555,7 +544,7 @@ def get_stac_extension_oseo(url: str) -> Dict[str, str]:
|
|
|
555
544
|
"""Build STAC OGC / OpenSearch Extension for EO
|
|
556
545
|
|
|
557
546
|
:param url: Requested URL
|
|
558
|
-
:returns: Catalog
|
|
547
|
+
:returns: Catalog dictionary
|
|
559
548
|
"""
|
|
560
549
|
|
|
561
550
|
def apply_method(_: str, x: str) -> str:
|
|
@@ -591,41 +580,87 @@ async def get_queryables(
|
|
|
591
580
|
|
|
592
581
|
async def _fetch() -> Dict[str, Any]:
|
|
593
582
|
python_queryables = eodag_api.list_queryables(
|
|
594
|
-
provider=provider,
|
|
583
|
+
provider=provider,
|
|
584
|
+
fetch_providers=False,
|
|
585
|
+
**params.model_dump(exclude_none=True, by_alias=True),
|
|
595
586
|
)
|
|
596
|
-
python_queryables.pop("start")
|
|
597
|
-
python_queryables.pop("end")
|
|
598
|
-
|
|
599
|
-
# productType and id are already default in stac collection and id
|
|
600
|
-
python_queryables.pop("productType", None)
|
|
601
|
-
python_queryables.pop("id", None)
|
|
602
587
|
|
|
603
|
-
|
|
604
|
-
|
|
588
|
+
python_queryables_json = python_queryables.get_model().model_json_schema(
|
|
589
|
+
by_alias=True
|
|
605
590
|
)
|
|
606
|
-
for param, queryable in python_queryables.items():
|
|
607
|
-
stac_param = EODAGSearch.to_stac(param)
|
|
608
|
-
# only keep "datetime" queryable for dates
|
|
609
|
-
if stac_param in stac_queryables or stac_param in (
|
|
610
|
-
"start_datetime",
|
|
611
|
-
"end_datetime",
|
|
612
|
-
):
|
|
613
|
-
continue
|
|
614
591
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
] = StacQueryableProperty.from_python_field_definition(
|
|
618
|
-
stac_param, queryable
|
|
619
|
-
)
|
|
592
|
+
properties: Dict[str, Any] = python_queryables_json["properties"]
|
|
593
|
+
required: List[str] = python_queryables_json.get("required") or []
|
|
620
594
|
|
|
621
|
-
|
|
622
|
-
|
|
595
|
+
# productType is either simply removed or replaced by collection later.
|
|
596
|
+
if "productType" in properties:
|
|
597
|
+
properties.pop("productType")
|
|
598
|
+
if "productType" in required:
|
|
599
|
+
required.remove("productType")
|
|
600
|
+
|
|
601
|
+
stac_properties: Dict[str, Any] = {}
|
|
602
|
+
|
|
603
|
+
# get stac default properties to set prefixes
|
|
604
|
+
stac_item_properties = list(stac_config["item"]["properties"].values())
|
|
605
|
+
stac_item_properties.extend(stac_config["metadata_ignore"])
|
|
606
|
+
for param, queryable in properties.items():
|
|
607
|
+
# convert key to STAC format
|
|
608
|
+
if param in OSEO_METADATA_MAPPING.keys() and not any(
|
|
609
|
+
param in str(prop) for prop in stac_item_properties
|
|
610
|
+
):
|
|
611
|
+
param = f"oseo:{param}"
|
|
612
|
+
stac_param = EODAGSearch.to_stac(param, stac_item_properties, provider)
|
|
613
|
+
|
|
614
|
+
queryable["title"] = stac_param.split(":")[-1]
|
|
615
|
+
|
|
616
|
+
# remove null default values
|
|
617
|
+
if not queryable.get("default"):
|
|
618
|
+
queryable.pop("default", None)
|
|
619
|
+
|
|
620
|
+
stac_properties[stac_param] = queryable
|
|
621
|
+
required = list(map(lambda x: x.replace(param, stac_param), required))
|
|
622
|
+
|
|
623
|
+
# due to certain metadata mappings we might only get end_datetime but we can
|
|
624
|
+
# assume that start_datetime is also available
|
|
625
|
+
if (
|
|
626
|
+
"end_datetime" in stac_properties
|
|
627
|
+
and "start_datetime" not in stac_properties
|
|
628
|
+
):
|
|
629
|
+
stac_properties["start_datetime"] = deepcopy(
|
|
630
|
+
stac_properties["end_datetime"]
|
|
631
|
+
)
|
|
632
|
+
stac_properties["start_datetime"]["title"] = "start_datetime"
|
|
633
|
+
# if we can search by start_datetime we can search by datetime
|
|
634
|
+
if "start_datetime" in stac_properties:
|
|
635
|
+
stac_properties["datetime"] = StacQueryables.possible_properties[
|
|
636
|
+
"datetime"
|
|
637
|
+
].model_dump()
|
|
638
|
+
|
|
639
|
+
# format spatial extend properties to STAC format.
|
|
640
|
+
if "geometry" in stac_properties:
|
|
641
|
+
stac_properties["bbox"] = StacQueryables.possible_properties[
|
|
642
|
+
"bbox"
|
|
643
|
+
].model_dump()
|
|
644
|
+
stac_properties["geometry"] = StacQueryables.possible_properties[
|
|
645
|
+
"geometry"
|
|
646
|
+
].model_dump()
|
|
647
|
+
|
|
648
|
+
if not params.collection:
|
|
649
|
+
stac_properties["collection"] = StacQueryables.default_properties[
|
|
650
|
+
"collection"
|
|
651
|
+
].model_dump()
|
|
652
|
+
|
|
653
|
+
additional_properties = python_queryables.additional_properties
|
|
654
|
+
description = "Queryable names for the EODAG STAC API Item Search filter. "
|
|
655
|
+
description += python_queryables.additional_information
|
|
623
656
|
|
|
624
657
|
return StacQueryables(
|
|
625
658
|
q_id=request.state.url,
|
|
626
|
-
additional_properties=
|
|
627
|
-
properties=
|
|
628
|
-
|
|
659
|
+
additional_properties=additional_properties,
|
|
660
|
+
properties=stac_properties,
|
|
661
|
+
required=required or None,
|
|
662
|
+
description=description,
|
|
663
|
+
).model_dump(mode="json", by_alias=True, exclude_none=True)
|
|
629
664
|
|
|
630
665
|
hashed_queryables = hash(params.model_dump_json())
|
|
631
666
|
return await cached(
|
|
@@ -691,11 +726,16 @@ def eodag_api_init() -> None:
|
|
|
691
726
|
constellation: Union[str, List[str]] = ext_col.get("summaries", {}).get(
|
|
692
727
|
"constellation"
|
|
693
728
|
)
|
|
729
|
+
processing_level: Union[str, List[str]] = ext_col.get("summaries", {}).get(
|
|
730
|
+
"processing:level"
|
|
731
|
+
)
|
|
694
732
|
# Check if platform or constellation are lists and join them into a string if they are
|
|
695
733
|
if isinstance(platform, list):
|
|
696
734
|
platform = ",".join(platform)
|
|
697
735
|
if isinstance(constellation, list):
|
|
698
736
|
constellation = ",".join(constellation)
|
|
737
|
+
if isinstance(processing_level, list):
|
|
738
|
+
processing_level = ",".join(processing_level)
|
|
699
739
|
|
|
700
740
|
update_fields = {
|
|
701
741
|
"title": ext_col.get("title"),
|
|
@@ -706,7 +746,7 @@ def eodag_api_init() -> None:
|
|
|
706
746
|
),
|
|
707
747
|
"platform": constellation,
|
|
708
748
|
"platformSerialIdentifier": platform,
|
|
709
|
-
"processingLevel":
|
|
749
|
+
"processingLevel": processing_level,
|
|
710
750
|
"license": ext_col["license"],
|
|
711
751
|
"missionStartDate": ext_col["extent"]["temporal"]["interval"][0][0],
|
|
712
752
|
"missionEndDate": ext_col["extent"]["temporal"]["interval"][-1][1],
|
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)
|