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/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,16 @@ 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
|
-
from eodag.rest.utils import
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
MisconfiguredError,
|
|
71
|
-
NoMatchingProductType,
|
|
72
|
-
NotAvailableError,
|
|
73
|
-
RequestError,
|
|
74
|
-
TimeOutError,
|
|
75
|
-
UnsupportedProductType,
|
|
76
|
-
UnsupportedProvider,
|
|
77
|
-
ValidationError,
|
|
64
|
+
from eodag.rest.utils import (
|
|
65
|
+
LIVENESS_PROBE_PATH,
|
|
66
|
+
format_pydantic_error,
|
|
67
|
+
str2json,
|
|
68
|
+
str2list,
|
|
78
69
|
)
|
|
70
|
+
from eodag.utils import parse_header, update_nested_dict
|
|
79
71
|
|
|
80
72
|
if TYPE_CHECKING:
|
|
81
73
|
from fastapi.types import DecoratedCallable
|
|
@@ -84,12 +76,6 @@ if TYPE_CHECKING:
|
|
|
84
76
|
from starlette.responses import Response as StarletteResponse
|
|
85
77
|
|
|
86
78
|
logger = logging.getLogger("eodag.rest.server")
|
|
87
|
-
ERRORS_WITH_500_STATUS_CODE = {
|
|
88
|
-
"MisconfiguredError",
|
|
89
|
-
"AuthenticationError",
|
|
90
|
-
"DownloadError",
|
|
91
|
-
"RequestError",
|
|
92
|
-
}
|
|
93
79
|
|
|
94
80
|
|
|
95
81
|
class APIRouter(FastAPIRouter):
|
|
@@ -139,6 +125,17 @@ app = FastAPI(lifespan=lifespan, title="EODAG", docs_url="/api.html")
|
|
|
139
125
|
stac_api_config = load_stac_api_config()
|
|
140
126
|
|
|
141
127
|
|
|
128
|
+
@router.api_route(
|
|
129
|
+
methods=["GET", "HEAD"],
|
|
130
|
+
path=LIVENESS_PROBE_PATH,
|
|
131
|
+
include_in_schema=False,
|
|
132
|
+
status_code=200,
|
|
133
|
+
)
|
|
134
|
+
async def liveness_probe(request: Request) -> Dict[str, bool]:
|
|
135
|
+
"Endpoint meant to be used as liveness probe by deployment platforms"
|
|
136
|
+
return {"success": True}
|
|
137
|
+
|
|
138
|
+
|
|
142
139
|
@router.api_route(
|
|
143
140
|
methods=["GET", "HEAD"], path="/api", tags=["Capabilities"], include_in_schema=False
|
|
144
141
|
)
|
|
@@ -197,6 +194,8 @@ app.add_middleware(
|
|
|
197
194
|
allow_headers=["*"],
|
|
198
195
|
)
|
|
199
196
|
|
|
197
|
+
add_exception_handlers(app)
|
|
198
|
+
|
|
200
199
|
|
|
201
200
|
@app.middleware("http")
|
|
202
201
|
async def forward_middleware(
|
|
@@ -221,141 +220,10 @@ async def forward_middleware(
|
|
|
221
220
|
return response
|
|
222
221
|
|
|
223
222
|
|
|
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
223
|
@router.api_route(methods=["GET", "HEAD"], path="/", tags=["Capabilities"])
|
|
356
224
|
async def catalogs_root(request: Request) -> ORJSONResponse:
|
|
357
225
|
"""STAC catalogs root"""
|
|
358
|
-
logger.
|
|
226
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
359
227
|
|
|
360
228
|
response = await get_stac_catalogs(
|
|
361
229
|
request=request,
|
|
@@ -367,9 +235,9 @@ async def catalogs_root(request: Request) -> ORJSONResponse:
|
|
|
367
235
|
|
|
368
236
|
|
|
369
237
|
@router.api_route(methods=["GET", "HEAD"], path="/conformance", tags=["Capabilities"])
|
|
370
|
-
def conformance() -> ORJSONResponse:
|
|
238
|
+
def conformance(request: Request) -> ORJSONResponse:
|
|
371
239
|
"""STAC conformance"""
|
|
372
|
-
logger.
|
|
240
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
373
241
|
response = get_stac_conformance()
|
|
374
242
|
|
|
375
243
|
return ORJSONResponse(response)
|
|
@@ -382,7 +250,7 @@ def conformance() -> ORJSONResponse:
|
|
|
382
250
|
)
|
|
383
251
|
def stac_extension_oseo(request: Request) -> ORJSONResponse:
|
|
384
252
|
"""STAC OGC / OpenSearch extension for EO"""
|
|
385
|
-
logger.
|
|
253
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
386
254
|
response = get_stac_extension_oseo(url=request.state.url)
|
|
387
255
|
|
|
388
256
|
return ORJSONResponse(response)
|
|
@@ -398,14 +266,14 @@ def stac_collections_item_download(
|
|
|
398
266
|
collection_id: str, item_id: str, request: Request
|
|
399
267
|
) -> StarletteResponse:
|
|
400
268
|
"""STAC collection item download"""
|
|
401
|
-
logger.
|
|
269
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
402
270
|
|
|
403
271
|
arguments = dict(request.query_params)
|
|
404
272
|
provider = arguments.pop("provider", None)
|
|
405
273
|
|
|
406
274
|
return download_stac_item(
|
|
407
275
|
request=request,
|
|
408
|
-
|
|
276
|
+
collection_id=collection_id,
|
|
409
277
|
item_id=item_id,
|
|
410
278
|
provider=provider,
|
|
411
279
|
**arguments,
|
|
@@ -422,14 +290,14 @@ def stac_collections_item_download_asset(
|
|
|
422
290
|
collection_id: str, item_id: str, asset: str, request: Request
|
|
423
291
|
):
|
|
424
292
|
"""STAC collection item asset download"""
|
|
425
|
-
logger.
|
|
293
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
426
294
|
|
|
427
295
|
arguments = dict(request.query_params)
|
|
428
296
|
provider = arguments.pop("provider", None)
|
|
429
297
|
|
|
430
298
|
return download_stac_item(
|
|
431
299
|
request=request,
|
|
432
|
-
|
|
300
|
+
collection_id=collection_id,
|
|
433
301
|
item_id=item_id,
|
|
434
302
|
provider=provider,
|
|
435
303
|
asset=asset,
|
|
@@ -446,7 +314,7 @@ def stac_collections_item(
|
|
|
446
314
|
collection_id: str, item_id: str, request: Request, provider: Optional[str] = None
|
|
447
315
|
) -> ORJSONResponse:
|
|
448
316
|
"""STAC collection item by id"""
|
|
449
|
-
logger.
|
|
317
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
450
318
|
|
|
451
319
|
search_request = SearchPostRequest(
|
|
452
320
|
provider=provider, ids=[item_id], collections=[collection_id], limit=1
|
|
@@ -522,14 +390,25 @@ async def list_collection_queryables(
|
|
|
522
390
|
:param collection_id: The identifier of the collection for which to retrieve queryable properties.
|
|
523
391
|
:returns: A json object containing the list of available queryable properties for the specified collection.
|
|
524
392
|
"""
|
|
525
|
-
logger.
|
|
526
|
-
|
|
393
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
394
|
+
# split by `,` to handle list of parameters
|
|
395
|
+
additional_params = {k: v.split(",") for k, v in dict(request.query_params).items()}
|
|
527
396
|
provider = additional_params.pop("provider", None)
|
|
528
397
|
|
|
398
|
+
datetime = additional_params.pop("datetime", None)
|
|
399
|
+
|
|
529
400
|
queryables = await get_queryables(
|
|
530
401
|
request,
|
|
531
|
-
QueryablesGetParams(
|
|
532
|
-
|
|
402
|
+
QueryablesGetParams.model_validate(
|
|
403
|
+
{
|
|
404
|
+
**additional_params,
|
|
405
|
+
**{
|
|
406
|
+
"collection": collection_id,
|
|
407
|
+
"datetime": datetime[0] if datetime else None,
|
|
408
|
+
},
|
|
409
|
+
}
|
|
410
|
+
),
|
|
411
|
+
provider=provider[0] if provider else None,
|
|
533
412
|
)
|
|
534
413
|
|
|
535
414
|
return ORJSONResponse(queryables)
|
|
@@ -545,7 +424,7 @@ async def collection_by_id(
|
|
|
545
424
|
collection_id: str, request: Request, provider: Optional[str] = None
|
|
546
425
|
) -> ORJSONResponse:
|
|
547
426
|
"""STAC collection by id"""
|
|
548
|
-
logger.
|
|
427
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
549
428
|
|
|
550
429
|
response = await get_collection(
|
|
551
430
|
request=request,
|
|
@@ -576,7 +455,7 @@ async def collections(
|
|
|
576
455
|
Can be filtered using parameters: instrument, platform, platformSerialIdentifier, sensorType,
|
|
577
456
|
processingLevel
|
|
578
457
|
"""
|
|
579
|
-
logger.
|
|
458
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
580
459
|
|
|
581
460
|
collections = await all_collections(
|
|
582
461
|
request, provider, q, platform, instrument, constellation, datetime
|
|
@@ -584,161 +463,6 @@ async def collections(
|
|
|
584
463
|
return ORJSONResponse(collections)
|
|
585
464
|
|
|
586
465
|
|
|
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
466
|
@router.api_route(
|
|
743
467
|
methods=["GET", "HEAD"],
|
|
744
468
|
path="/queryables",
|
|
@@ -756,7 +480,7 @@ async def list_queryables(request: Request) -> ORJSONResponse:
|
|
|
756
480
|
:param request: The incoming request object.
|
|
757
481
|
:returns: A json object containing the list of available queryable terms.
|
|
758
482
|
"""
|
|
759
|
-
logger.
|
|
483
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
760
484
|
additional_params = dict(request.query_params.items())
|
|
761
485
|
provider = additional_params.pop("provider", None)
|
|
762
486
|
queryables = await get_queryables(
|
|
@@ -789,7 +513,7 @@ def get_search(
|
|
|
789
513
|
crunch: Optional[str] = None,
|
|
790
514
|
) -> ORJSONResponse:
|
|
791
515
|
"""Handler for GET /search"""
|
|
792
|
-
logger.
|
|
516
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
793
517
|
|
|
794
518
|
query_params = str(request.query_params)
|
|
795
519
|
|
|
@@ -843,7 +567,7 @@ def get_search(
|
|
|
843
567
|
)
|
|
844
568
|
async def post_search(request: Request) -> ORJSONResponse:
|
|
845
569
|
"""STAC post search"""
|
|
846
|
-
logger.
|
|
570
|
+
logger.info(f"{request.method} {request.state.url}")
|
|
847
571
|
|
|
848
572
|
content_type = request.headers.get("Content-Type")
|
|
849
573
|
|
|
@@ -864,10 +588,12 @@ async def post_search(request: Request) -> ORJSONResponse:
|
|
|
864
588
|
|
|
865
589
|
logger.debug("Body: %s", search_request.model_dump(exclude_none=True))
|
|
866
590
|
|
|
867
|
-
response =
|
|
868
|
-
|
|
869
|
-
|
|
591
|
+
response = await run_in_threadpool(
|
|
592
|
+
search_stac_items,
|
|
593
|
+
request,
|
|
594
|
+
search_request,
|
|
870
595
|
)
|
|
596
|
+
|
|
871
597
|
return ORJSONResponse(content=response, media_type="application/json")
|
|
872
598
|
|
|
873
599
|
|