eodag 3.0.0b3__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/api/core.py +189 -125
- eodag/api/product/metadata_mapping.py +12 -3
- eodag/api/search_result.py +29 -3
- eodag/cli.py +35 -19
- eodag/config.py +412 -116
- eodag/plugins/apis/base.py +10 -4
- eodag/plugins/apis/ecmwf.py +14 -4
- eodag/plugins/apis/usgs.py +25 -2
- 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 +57 -10
- 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 +4 -3
- eodag/plugins/download/aws.py +39 -22
- eodag/plugins/download/base.py +11 -11
- eodag/plugins/download/creodias_s3.py +11 -2
- eodag/plugins/download/http.py +86 -52
- eodag/plugins/download/s3rest.py +20 -18
- eodag/plugins/manager.py +168 -23
- eodag/plugins/search/base.py +33 -14
- eodag/plugins/search/build_search_result.py +55 -51
- eodag/plugins/search/cop_marine.py +112 -29
- eodag/plugins/search/creodias_s3.py +20 -5
- eodag/plugins/search/csw.py +41 -1
- eodag/plugins/search/data_request_search.py +109 -9
- eodag/plugins/search/qssearch.py +532 -152
- eodag/plugins/search/static_stac_search.py +20 -21
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +187 -56
- eodag/resources/providers.yml +1610 -1701
- 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 +61 -51
- eodag/rest/errors.py +181 -0
- eodag/rest/server.py +24 -325
- eodag/rest/stac.py +93 -544
- eodag/rest/types/eodag_search.py +13 -8
- eodag/rest/types/queryables.py +1 -2
- eodag/rest/types/stac_search.py +11 -2
- eodag/types/__init__.py +15 -3
- eodag/types/download_args.py +1 -1
- eodag/types/queryables.py +1 -2
- eodag/types/search_args.py +3 -3
- eodag/utils/__init__.py +77 -57
- eodag/utils/exceptions.py +23 -9
- eodag/utils/logging.py +37 -77
- eodag/utils/requests.py +1 -3
- eodag/utils/stac_reader.py +1 -1
- {eodag-3.0.0b3.dist-info → eodag-3.0.1.dist-info}/METADATA +11 -12
- eodag-3.0.1.dist-info/RECORD +109 -0
- {eodag-3.0.0b3.dist-info → eodag-3.0.1.dist-info}/WHEEL +1 -1
- {eodag-3.0.0b3.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.0b3.dist-info/RECORD +0 -110
- {eodag-3.0.0b3.dist-info → eodag-3.0.1.dist-info}/LICENSE +0 -0
- {eodag-3.0.0b3.dist-info → eodag-3.0.1.dist-info}/top_level.txt +0 -0
eodag/rest/server.py
CHANGED
|
@@ -20,7 +20,6 @@ from __future__ import annotations
|
|
|
20
20
|
import logging
|
|
21
21
|
import os
|
|
22
22
|
import re
|
|
23
|
-
import traceback
|
|
24
23
|
from contextlib import asynccontextmanager
|
|
25
24
|
from importlib.metadata import version
|
|
26
25
|
from json import JSONDecodeError
|
|
@@ -36,13 +35,13 @@ from typing import (
|
|
|
36
35
|
|
|
37
36
|
from fastapi import APIRouter as FastAPIRouter
|
|
38
37
|
from fastapi import FastAPI, HTTPException, Request
|
|
38
|
+
from fastapi.concurrency import run_in_threadpool
|
|
39
39
|
from fastapi.middleware.cors import CORSMiddleware
|
|
40
40
|
from fastapi.openapi.utils import get_openapi
|
|
41
41
|
from fastapi.responses import ORJSONResponse
|
|
42
42
|
from pydantic import ValidationError as pydanticValidationError
|
|
43
43
|
from pygeofilter.backends.cql2_json import to_cql2
|
|
44
44
|
from pygeofilter.parsers.cql2_text import parse as parse_cql2_text
|
|
45
|
-
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
46
45
|
|
|
47
46
|
from eodag.config import load_stac_api_config
|
|
48
47
|
from eodag.rest.cache import init_cache
|
|
@@ -59,23 +58,11 @@ from eodag.rest.core import (
|
|
|
59
58
|
get_stac_extension_oseo,
|
|
60
59
|
search_stac_items,
|
|
61
60
|
)
|
|
62
|
-
from eodag.rest.
|
|
61
|
+
from eodag.rest.errors import add_exception_handlers
|
|
63
62
|
from eodag.rest.types.queryables import QueryablesGetParams
|
|
64
63
|
from eodag.rest.types.stac_search import SearchPostRequest, sortby2list
|
|
65
64
|
from eodag.rest.utils import format_pydantic_error, str2json, str2list
|
|
66
65
|
from eodag.utils import parse_header, update_nested_dict
|
|
67
|
-
from eodag.utils.exceptions import (
|
|
68
|
-
AuthenticationError,
|
|
69
|
-
DownloadError,
|
|
70
|
-
MisconfiguredError,
|
|
71
|
-
NoMatchingProductType,
|
|
72
|
-
NotAvailableError,
|
|
73
|
-
RequestError,
|
|
74
|
-
TimeOutError,
|
|
75
|
-
UnsupportedProductType,
|
|
76
|
-
UnsupportedProvider,
|
|
77
|
-
ValidationError,
|
|
78
|
-
)
|
|
79
66
|
|
|
80
67
|
if TYPE_CHECKING:
|
|
81
68
|
from fastapi.types import DecoratedCallable
|
|
@@ -84,12 +71,6 @@ if TYPE_CHECKING:
|
|
|
84
71
|
from starlette.responses import Response as StarletteResponse
|
|
85
72
|
|
|
86
73
|
logger = logging.getLogger("eodag.rest.server")
|
|
87
|
-
ERRORS_WITH_500_STATUS_CODE = {
|
|
88
|
-
"MisconfiguredError",
|
|
89
|
-
"AuthenticationError",
|
|
90
|
-
"DownloadError",
|
|
91
|
-
"RequestError",
|
|
92
|
-
}
|
|
93
74
|
|
|
94
75
|
|
|
95
76
|
class APIRouter(FastAPIRouter):
|
|
@@ -197,6 +178,8 @@ app.add_middleware(
|
|
|
197
178
|
allow_headers=["*"],
|
|
198
179
|
)
|
|
199
180
|
|
|
181
|
+
add_exception_handlers(app)
|
|
182
|
+
|
|
200
183
|
|
|
201
184
|
@app.middleware("http")
|
|
202
185
|
async def forward_middleware(
|
|
@@ -221,141 +204,10 @@ async def forward_middleware(
|
|
|
221
204
|
return response
|
|
222
205
|
|
|
223
206
|
|
|
224
|
-
@app.exception_handler(StarletteHTTPException)
|
|
225
|
-
async def default_exception_handler(
|
|
226
|
-
request: Request, error: HTTPException
|
|
227
|
-
) -> ORJSONResponse:
|
|
228
|
-
"""Default errors handle"""
|
|
229
|
-
description = (
|
|
230
|
-
getattr(error, "description", None)
|
|
231
|
-
or getattr(error, "detail", None)
|
|
232
|
-
or str(error)
|
|
233
|
-
)
|
|
234
|
-
return ORJSONResponse(
|
|
235
|
-
status_code=error.status_code,
|
|
236
|
-
content={"description": description},
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
@app.exception_handler(ValidationError)
|
|
241
|
-
async def handle_invalid_usage_with_validation_error(
|
|
242
|
-
request: Request, error: ValidationError
|
|
243
|
-
) -> ORJSONResponse:
|
|
244
|
-
"""Invalid usage [400] ValidationError handle"""
|
|
245
|
-
if error.parameters:
|
|
246
|
-
for error_param in error.parameters:
|
|
247
|
-
stac_param = EODAGSearch.to_stac(error_param)
|
|
248
|
-
error.message = error.message.replace(error_param, stac_param)
|
|
249
|
-
logger.debug(traceback.format_exc())
|
|
250
|
-
return await default_exception_handler(
|
|
251
|
-
request,
|
|
252
|
-
HTTPException(
|
|
253
|
-
status_code=400,
|
|
254
|
-
detail=f"{type(error).__name__}: {str(error.message)}",
|
|
255
|
-
),
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
@app.exception_handler(NoMatchingProductType)
|
|
260
|
-
@app.exception_handler(UnsupportedProductType)
|
|
261
|
-
@app.exception_handler(UnsupportedProvider)
|
|
262
|
-
async def handle_invalid_usage(request: Request, error: Exception) -> ORJSONResponse:
|
|
263
|
-
"""Invalid usage [400] errors handle"""
|
|
264
|
-
return await default_exception_handler(
|
|
265
|
-
request,
|
|
266
|
-
HTTPException(
|
|
267
|
-
status_code=400,
|
|
268
|
-
detail=f"{type(error).__name__}: {str(error)}",
|
|
269
|
-
),
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
@app.exception_handler(NotAvailableError)
|
|
274
|
-
async def handle_resource_not_found(
|
|
275
|
-
request: Request, error: Exception
|
|
276
|
-
) -> ORJSONResponse:
|
|
277
|
-
"""Not found [404] errors handle"""
|
|
278
|
-
return await default_exception_handler(
|
|
279
|
-
request,
|
|
280
|
-
HTTPException(
|
|
281
|
-
status_code=404,
|
|
282
|
-
detail=f"{type(error).__name__}: {str(error)}",
|
|
283
|
-
),
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
@app.exception_handler(MisconfiguredError)
|
|
288
|
-
@app.exception_handler(AuthenticationError)
|
|
289
|
-
async def handle_auth_error(request: Request, error: Exception) -> ORJSONResponse:
|
|
290
|
-
"""These errors should be sent as internal server error to the client"""
|
|
291
|
-
logger.error("%s: %s", type(error).__name__, str(error))
|
|
292
|
-
return await default_exception_handler(
|
|
293
|
-
request,
|
|
294
|
-
HTTPException(
|
|
295
|
-
status_code=500,
|
|
296
|
-
detail="Internal server error: please contact the administrator",
|
|
297
|
-
),
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
@app.exception_handler(DownloadError)
|
|
302
|
-
async def handle_download_error(request: Request, error: Exception) -> ORJSONResponse:
|
|
303
|
-
"""DownloadError should be sent as internal server error with details to the client"""
|
|
304
|
-
logger.error(f"{type(error).__name__}: {str(error)}")
|
|
305
|
-
return await default_exception_handler(
|
|
306
|
-
request,
|
|
307
|
-
HTTPException(
|
|
308
|
-
status_code=500,
|
|
309
|
-
detail=f"{type(error).__name__}: {str(error)}",
|
|
310
|
-
),
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
@app.exception_handler(RequestError)
|
|
315
|
-
async def handle_request_error(request: Request, error: RequestError) -> ORJSONResponse:
|
|
316
|
-
"""RequestError should be sent as internal server error with details to the client"""
|
|
317
|
-
if getattr(error, "history", None):
|
|
318
|
-
error_history_tmp = list(error.history)
|
|
319
|
-
for i, search_error in enumerate(error_history_tmp):
|
|
320
|
-
if search_error[1].__class__.__name__ in ERRORS_WITH_500_STATUS_CODE:
|
|
321
|
-
search_error[1].args = ("an internal error occured",)
|
|
322
|
-
error_history_tmp[i] = search_error
|
|
323
|
-
continue
|
|
324
|
-
if getattr(error, "parameters", None):
|
|
325
|
-
for error_param in error.parameters:
|
|
326
|
-
stac_param = EODAGSearch.to_stac(error_param)
|
|
327
|
-
search_error[1].args = (
|
|
328
|
-
search_error[1].args[0].replace(error_param, stac_param),
|
|
329
|
-
)
|
|
330
|
-
error_history_tmp[i] = search_error
|
|
331
|
-
error.history = set(error_history_tmp)
|
|
332
|
-
logger.error(f"{type(error).__name__}: {str(error)}")
|
|
333
|
-
return await default_exception_handler(
|
|
334
|
-
request,
|
|
335
|
-
HTTPException(
|
|
336
|
-
status_code=500,
|
|
337
|
-
detail=f"{type(error).__name__}: {str(error)}",
|
|
338
|
-
),
|
|
339
|
-
)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
@app.exception_handler(TimeOutError)
|
|
343
|
-
async def handle_timeout(request: Request, error: Exception) -> ORJSONResponse:
|
|
344
|
-
"""Timeout [504] errors handle"""
|
|
345
|
-
logger.error(f"{type(error).__name__}: {str(error)}")
|
|
346
|
-
return await default_exception_handler(
|
|
347
|
-
request,
|
|
348
|
-
HTTPException(
|
|
349
|
-
status_code=504,
|
|
350
|
-
detail=f"{type(error).__name__}: {str(error)}",
|
|
351
|
-
),
|
|
352
|
-
)
|
|
353
|
-
|
|
354
|
-
|
|
355
207
|
@router.api_route(methods=["GET", "HEAD"], path="/", tags=["Capabilities"])
|
|
356
208
|
async def catalogs_root(request: Request) -> ORJSONResponse:
|
|
357
209
|
"""STAC catalogs root"""
|
|
358
|
-
logger.
|
|
210
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
359
211
|
|
|
360
212
|
response = await get_stac_catalogs(
|
|
361
213
|
request=request,
|
|
@@ -367,9 +219,9 @@ async def catalogs_root(request: Request) -> ORJSONResponse:
|
|
|
367
219
|
|
|
368
220
|
|
|
369
221
|
@router.api_route(methods=["GET", "HEAD"], path="/conformance", tags=["Capabilities"])
|
|
370
|
-
def conformance() -> ORJSONResponse:
|
|
222
|
+
def conformance(request: Request) -> ORJSONResponse:
|
|
371
223
|
"""STAC conformance"""
|
|
372
|
-
logger.
|
|
224
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
373
225
|
response = get_stac_conformance()
|
|
374
226
|
|
|
375
227
|
return ORJSONResponse(response)
|
|
@@ -382,7 +234,7 @@ def conformance() -> ORJSONResponse:
|
|
|
382
234
|
)
|
|
383
235
|
def stac_extension_oseo(request: Request) -> ORJSONResponse:
|
|
384
236
|
"""STAC OGC / OpenSearch extension for EO"""
|
|
385
|
-
logger.
|
|
237
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
386
238
|
response = get_stac_extension_oseo(url=request.state.url)
|
|
387
239
|
|
|
388
240
|
return ORJSONResponse(response)
|
|
@@ -398,14 +250,14 @@ def stac_collections_item_download(
|
|
|
398
250
|
collection_id: str, item_id: str, request: Request
|
|
399
251
|
) -> StarletteResponse:
|
|
400
252
|
"""STAC collection item download"""
|
|
401
|
-
logger.
|
|
253
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
402
254
|
|
|
403
255
|
arguments = dict(request.query_params)
|
|
404
256
|
provider = arguments.pop("provider", None)
|
|
405
257
|
|
|
406
258
|
return download_stac_item(
|
|
407
259
|
request=request,
|
|
408
|
-
|
|
260
|
+
collection_id=collection_id,
|
|
409
261
|
item_id=item_id,
|
|
410
262
|
provider=provider,
|
|
411
263
|
**arguments,
|
|
@@ -422,14 +274,14 @@ def stac_collections_item_download_asset(
|
|
|
422
274
|
collection_id: str, item_id: str, asset: str, request: Request
|
|
423
275
|
):
|
|
424
276
|
"""STAC collection item asset download"""
|
|
425
|
-
logger.
|
|
277
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
426
278
|
|
|
427
279
|
arguments = dict(request.query_params)
|
|
428
280
|
provider = arguments.pop("provider", None)
|
|
429
281
|
|
|
430
282
|
return download_stac_item(
|
|
431
283
|
request=request,
|
|
432
|
-
|
|
284
|
+
collection_id=collection_id,
|
|
433
285
|
item_id=item_id,
|
|
434
286
|
provider=provider,
|
|
435
287
|
asset=asset,
|
|
@@ -446,7 +298,7 @@ def stac_collections_item(
|
|
|
446
298
|
collection_id: str, item_id: str, request: Request, provider: Optional[str] = None
|
|
447
299
|
) -> ORJSONResponse:
|
|
448
300
|
"""STAC collection item by id"""
|
|
449
|
-
logger.
|
|
301
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
450
302
|
|
|
451
303
|
search_request = SearchPostRequest(
|
|
452
304
|
provider=provider, ids=[item_id], collections=[collection_id], limit=1
|
|
@@ -522,7 +374,7 @@ async def list_collection_queryables(
|
|
|
522
374
|
:param collection_id: The identifier of the collection for which to retrieve queryable properties.
|
|
523
375
|
:returns: A json object containing the list of available queryable properties for the specified collection.
|
|
524
376
|
"""
|
|
525
|
-
logger.
|
|
377
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
526
378
|
additional_params = dict(request.query_params)
|
|
527
379
|
provider = additional_params.pop("provider", None)
|
|
528
380
|
|
|
@@ -545,7 +397,7 @@ async def collection_by_id(
|
|
|
545
397
|
collection_id: str, request: Request, provider: Optional[str] = None
|
|
546
398
|
) -> ORJSONResponse:
|
|
547
399
|
"""STAC collection by id"""
|
|
548
|
-
logger.
|
|
400
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
549
401
|
|
|
550
402
|
response = await get_collection(
|
|
551
403
|
request=request,
|
|
@@ -576,7 +428,7 @@ async def collections(
|
|
|
576
428
|
Can be filtered using parameters: instrument, platform, platformSerialIdentifier, sensorType,
|
|
577
429
|
processingLevel
|
|
578
430
|
"""
|
|
579
|
-
logger.
|
|
431
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
580
432
|
|
|
581
433
|
collections = await all_collections(
|
|
582
434
|
request, provider, q, platform, instrument, constellation, datetime
|
|
@@ -584,161 +436,6 @@ async def collections(
|
|
|
584
436
|
return ORJSONResponse(collections)
|
|
585
437
|
|
|
586
438
|
|
|
587
|
-
@router.api_route(
|
|
588
|
-
methods=["GET", "HEAD"],
|
|
589
|
-
path="/catalogs/{catalogs:path}/items/{item_id}/download",
|
|
590
|
-
tags=["Data"],
|
|
591
|
-
include_in_schema=False,
|
|
592
|
-
)
|
|
593
|
-
def stac_catalogs_item_download(
|
|
594
|
-
catalogs: str, item_id: str, request: Request
|
|
595
|
-
) -> StarletteResponse:
|
|
596
|
-
"""STAC Catalog item download"""
|
|
597
|
-
logger.debug("URL: %s", request.url)
|
|
598
|
-
|
|
599
|
-
arguments = dict(request.query_params)
|
|
600
|
-
provider = arguments.pop("provider", None)
|
|
601
|
-
|
|
602
|
-
list_catalog = catalogs.strip("/").split("/")
|
|
603
|
-
|
|
604
|
-
return download_stac_item(
|
|
605
|
-
request=request,
|
|
606
|
-
catalogs=list_catalog,
|
|
607
|
-
item_id=item_id,
|
|
608
|
-
provider=provider,
|
|
609
|
-
**arguments,
|
|
610
|
-
)
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
@router.api_route(
|
|
614
|
-
methods=["GET", "HEAD"],
|
|
615
|
-
path="/catalogs/{catalogs:path}/items/{item_id}/download/{asset_filter}",
|
|
616
|
-
tags=["Data"],
|
|
617
|
-
include_in_schema=False,
|
|
618
|
-
)
|
|
619
|
-
def stac_catalogs_item_download_asset(
|
|
620
|
-
catalogs: str, item_id: str, asset_filter: str, request: Request
|
|
621
|
-
):
|
|
622
|
-
"""STAC Catalog item asset download"""
|
|
623
|
-
logger.debug("URL: %s", request.url)
|
|
624
|
-
|
|
625
|
-
arguments = dict(request.query_params)
|
|
626
|
-
provider = arguments.pop("provider", None)
|
|
627
|
-
|
|
628
|
-
list_catalog = catalogs.strip("/").split("/")
|
|
629
|
-
|
|
630
|
-
return download_stac_item(
|
|
631
|
-
request,
|
|
632
|
-
catalogs=list_catalog,
|
|
633
|
-
item_id=item_id,
|
|
634
|
-
provider=provider,
|
|
635
|
-
asset=asset_filter,
|
|
636
|
-
**arguments,
|
|
637
|
-
)
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
@router.api_route(
|
|
641
|
-
methods=["GET", "HEAD"],
|
|
642
|
-
path="/catalogs/{catalogs:path}/items/{item_id}",
|
|
643
|
-
tags=["Data"],
|
|
644
|
-
include_in_schema=False,
|
|
645
|
-
)
|
|
646
|
-
def stac_catalogs_item(
|
|
647
|
-
catalogs: str, item_id: str, request: Request, provider: Optional[str] = None
|
|
648
|
-
):
|
|
649
|
-
"""Fetch catalog's single features."""
|
|
650
|
-
logger.debug("URL: %s", request.url)
|
|
651
|
-
|
|
652
|
-
list_catalog = catalogs.strip("/").split("/")
|
|
653
|
-
|
|
654
|
-
search_request = SearchPostRequest(provider=provider, ids=[item_id], limit=1)
|
|
655
|
-
|
|
656
|
-
item_collection = search_stac_items(request, search_request, catalogs=list_catalog)
|
|
657
|
-
|
|
658
|
-
if not item_collection["features"]:
|
|
659
|
-
raise HTTPException(
|
|
660
|
-
status_code=404,
|
|
661
|
-
detail=f"Item {item_id} in Catalog {catalogs} does not exist.",
|
|
662
|
-
)
|
|
663
|
-
|
|
664
|
-
return ORJSONResponse(item_collection["features"][0])
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
@router.api_route(
|
|
668
|
-
methods=["GET", "HEAD"],
|
|
669
|
-
path="/catalogs/{catalogs:path}/items",
|
|
670
|
-
tags=["Data"],
|
|
671
|
-
include_in_schema=False,
|
|
672
|
-
)
|
|
673
|
-
def stac_catalogs_items(
|
|
674
|
-
catalogs: str,
|
|
675
|
-
request: Request,
|
|
676
|
-
provider: Optional[str] = None,
|
|
677
|
-
bbox: Optional[str] = None,
|
|
678
|
-
datetime: Optional[str] = None,
|
|
679
|
-
limit: Optional[int] = None,
|
|
680
|
-
page: Optional[int] = None,
|
|
681
|
-
sortby: Optional[str] = None,
|
|
682
|
-
crunch: Optional[str] = None,
|
|
683
|
-
) -> ORJSONResponse:
|
|
684
|
-
"""Fetch catalog's features"""
|
|
685
|
-
logger.debug("URL: %s", request.state.url)
|
|
686
|
-
|
|
687
|
-
base_args = {
|
|
688
|
-
"provider": provider,
|
|
689
|
-
"datetime": datetime,
|
|
690
|
-
"bbox": str2list(bbox),
|
|
691
|
-
"limit": limit,
|
|
692
|
-
"page": page,
|
|
693
|
-
"sortby": sortby2list(sortby),
|
|
694
|
-
"crunch": crunch,
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
clean = {k: v for k, v in base_args.items() if v is not None and v != []}
|
|
698
|
-
|
|
699
|
-
list_catalog = catalogs.strip("/").split("/")
|
|
700
|
-
|
|
701
|
-
try:
|
|
702
|
-
search_request = SearchPostRequest.model_validate(clean)
|
|
703
|
-
except pydanticValidationError as e:
|
|
704
|
-
raise HTTPException(status_code=400, detail=format_pydantic_error(e)) from e
|
|
705
|
-
|
|
706
|
-
response = search_stac_items(
|
|
707
|
-
request=request,
|
|
708
|
-
search_request=search_request,
|
|
709
|
-
catalogs=list_catalog,
|
|
710
|
-
)
|
|
711
|
-
return ORJSONResponse(response)
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
@router.api_route(
|
|
715
|
-
methods=["GET", "HEAD"],
|
|
716
|
-
path="/catalogs/{catalogs:path}",
|
|
717
|
-
tags=["Capabilities"],
|
|
718
|
-
include_in_schema=False,
|
|
719
|
-
)
|
|
720
|
-
async def stac_catalogs(
|
|
721
|
-
catalogs: str, request: Request, provider: Optional[str] = None
|
|
722
|
-
) -> ORJSONResponse:
|
|
723
|
-
"""Describe the given catalog and list available sub-catalogs"""
|
|
724
|
-
logger.debug("URL: %s", request.url)
|
|
725
|
-
|
|
726
|
-
if not catalogs:
|
|
727
|
-
raise HTTPException(
|
|
728
|
-
status_code=404,
|
|
729
|
-
detail="Not found",
|
|
730
|
-
)
|
|
731
|
-
|
|
732
|
-
list_catalog = catalogs.strip("/").split("/")
|
|
733
|
-
response = await get_stac_catalogs(
|
|
734
|
-
request=request,
|
|
735
|
-
url=request.state.url,
|
|
736
|
-
catalogs=tuple(list_catalog),
|
|
737
|
-
provider=provider,
|
|
738
|
-
)
|
|
739
|
-
return ORJSONResponse(response)
|
|
740
|
-
|
|
741
|
-
|
|
742
439
|
@router.api_route(
|
|
743
440
|
methods=["GET", "HEAD"],
|
|
744
441
|
path="/queryables",
|
|
@@ -756,7 +453,7 @@ async def list_queryables(request: Request) -> ORJSONResponse:
|
|
|
756
453
|
:param request: The incoming request object.
|
|
757
454
|
:returns: A json object containing the list of available queryable terms.
|
|
758
455
|
"""
|
|
759
|
-
logger.
|
|
456
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
760
457
|
additional_params = dict(request.query_params.items())
|
|
761
458
|
provider = additional_params.pop("provider", None)
|
|
762
459
|
queryables = await get_queryables(
|
|
@@ -789,7 +486,7 @@ def get_search(
|
|
|
789
486
|
crunch: Optional[str] = None,
|
|
790
487
|
) -> ORJSONResponse:
|
|
791
488
|
"""Handler for GET /search"""
|
|
792
|
-
logger.
|
|
489
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
793
490
|
|
|
794
491
|
query_params = str(request.query_params)
|
|
795
492
|
|
|
@@ -843,7 +540,7 @@ def get_search(
|
|
|
843
540
|
)
|
|
844
541
|
async def post_search(request: Request) -> ORJSONResponse:
|
|
845
542
|
"""STAC post search"""
|
|
846
|
-
logger.
|
|
543
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
847
544
|
|
|
848
545
|
content_type = request.headers.get("Content-Type")
|
|
849
546
|
|
|
@@ -864,10 +561,12 @@ async def post_search(request: Request) -> ORJSONResponse:
|
|
|
864
561
|
|
|
865
562
|
logger.debug("Body: %s", search_request.model_dump(exclude_none=True))
|
|
866
563
|
|
|
867
|
-
response =
|
|
868
|
-
|
|
869
|
-
|
|
564
|
+
response = await run_in_threadpool(
|
|
565
|
+
search_stac_items,
|
|
566
|
+
request,
|
|
567
|
+
search_request,
|
|
870
568
|
)
|
|
569
|
+
|
|
871
570
|
return ORJSONResponse(content=response, media_type="application/json")
|
|
872
571
|
|
|
873
572
|
|