eodag 3.0.0b3__py3-none-any.whl → 3.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. eodag/api/core.py +347 -247
  2. eodag/api/product/_assets.py +44 -15
  3. eodag/api/product/_product.py +58 -47
  4. eodag/api/product/drivers/__init__.py +81 -4
  5. eodag/api/product/drivers/base.py +65 -4
  6. eodag/api/product/drivers/generic.py +65 -0
  7. eodag/api/product/drivers/sentinel1.py +97 -0
  8. eodag/api/product/drivers/sentinel2.py +95 -0
  9. eodag/api/product/metadata_mapping.py +129 -93
  10. eodag/api/search_result.py +28 -12
  11. eodag/cli.py +61 -24
  12. eodag/config.py +457 -167
  13. eodag/plugins/apis/base.py +10 -4
  14. eodag/plugins/apis/ecmwf.py +53 -23
  15. eodag/plugins/apis/usgs.py +41 -17
  16. eodag/plugins/authentication/aws_auth.py +30 -18
  17. eodag/plugins/authentication/base.py +14 -3
  18. eodag/plugins/authentication/generic.py +14 -3
  19. eodag/plugins/authentication/header.py +14 -6
  20. eodag/plugins/authentication/keycloak.py +44 -25
  21. eodag/plugins/authentication/oauth.py +18 -4
  22. eodag/plugins/authentication/openid_connect.py +192 -171
  23. eodag/plugins/authentication/qsauth.py +12 -4
  24. eodag/plugins/authentication/sas_auth.py +22 -5
  25. eodag/plugins/authentication/token.py +95 -17
  26. eodag/plugins/authentication/token_exchange.py +19 -19
  27. eodag/plugins/base.py +4 -4
  28. eodag/plugins/crunch/base.py +8 -5
  29. eodag/plugins/crunch/filter_date.py +9 -6
  30. eodag/plugins/crunch/filter_latest_intersect.py +9 -8
  31. eodag/plugins/crunch/filter_latest_tpl_name.py +8 -8
  32. eodag/plugins/crunch/filter_overlap.py +9 -11
  33. eodag/plugins/crunch/filter_property.py +10 -10
  34. eodag/plugins/download/aws.py +181 -105
  35. eodag/plugins/download/base.py +49 -67
  36. eodag/plugins/download/creodias_s3.py +40 -2
  37. eodag/plugins/download/http.py +247 -223
  38. eodag/plugins/download/s3rest.py +29 -28
  39. eodag/plugins/manager.py +176 -41
  40. eodag/plugins/search/__init__.py +6 -5
  41. eodag/plugins/search/base.py +123 -60
  42. eodag/plugins/search/build_search_result.py +1046 -355
  43. eodag/plugins/search/cop_marine.py +132 -39
  44. eodag/plugins/search/creodias_s3.py +19 -68
  45. eodag/plugins/search/csw.py +48 -8
  46. eodag/plugins/search/data_request_search.py +124 -23
  47. eodag/plugins/search/qssearch.py +531 -310
  48. eodag/plugins/search/stac_list_assets.py +85 -0
  49. eodag/plugins/search/static_stac_search.py +23 -24
  50. eodag/resources/ext_product_types.json +1 -1
  51. eodag/resources/product_types.yml +1295 -355
  52. eodag/resources/providers.yml +1819 -3010
  53. eodag/resources/stac.yml +3 -163
  54. eodag/resources/stac_api.yml +2 -2
  55. eodag/resources/user_conf_template.yml +115 -99
  56. eodag/rest/cache.py +2 -2
  57. eodag/rest/config.py +3 -4
  58. eodag/rest/constants.py +0 -1
  59. eodag/rest/core.py +157 -117
  60. eodag/rest/errors.py +181 -0
  61. eodag/rest/server.py +57 -339
  62. eodag/rest/stac.py +133 -581
  63. eodag/rest/types/collections_search.py +3 -3
  64. eodag/rest/types/eodag_search.py +41 -30
  65. eodag/rest/types/queryables.py +42 -32
  66. eodag/rest/types/stac_search.py +15 -16
  67. eodag/rest/utils/__init__.py +14 -21
  68. eodag/rest/utils/cql_evaluate.py +6 -6
  69. eodag/rest/utils/rfc3339.py +2 -2
  70. eodag/types/__init__.py +153 -32
  71. eodag/types/bbox.py +2 -2
  72. eodag/types/download_args.py +4 -4
  73. eodag/types/queryables.py +183 -73
  74. eodag/types/search_args.py +6 -6
  75. eodag/types/whoosh.py +127 -3
  76. eodag/utils/__init__.py +228 -106
  77. eodag/utils/exceptions.py +47 -26
  78. eodag/utils/import_system.py +2 -2
  79. eodag/utils/logging.py +37 -77
  80. eodag/utils/repr.py +65 -6
  81. eodag/utils/requests.py +13 -15
  82. eodag/utils/rest.py +2 -2
  83. eodag/utils/s3.py +231 -0
  84. eodag/utils/stac_reader.py +11 -11
  85. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/METADATA +81 -81
  86. eodag-3.1.0.dist-info/RECORD +113 -0
  87. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
  88. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +5 -2
  89. eodag/resources/constraints/climate-dt.json +0 -13
  90. eodag/resources/constraints/extremes-dt.json +0 -8
  91. eodag/utils/constraints.py +0 -244
  92. eodag-3.0.0b3.dist-info/RECORD +0 -110
  93. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/LICENSE +0 -0
  94. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/top_level.txt +0 -0
eodag/rest/server.py CHANGED
@@ -20,29 +20,20 @@ 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
27
- from typing import (
28
- TYPE_CHECKING,
29
- Any,
30
- AsyncGenerator,
31
- Awaitable,
32
- Callable,
33
- Dict,
34
- Optional,
35
- )
26
+ from typing import TYPE_CHECKING, Any, AsyncGenerator, Awaitable, Callable, Optional
36
27
 
37
28
  from fastapi import APIRouter as FastAPIRouter
38
29
  from fastapi import FastAPI, HTTPException, Request
30
+ from fastapi.concurrency import run_in_threadpool
39
31
  from fastapi.middleware.cors import CORSMiddleware
40
32
  from fastapi.openapi.utils import get_openapi
41
33
  from fastapi.responses import ORJSONResponse
42
34
  from pydantic import ValidationError as pydanticValidationError
43
35
  from pygeofilter.backends.cql2_json import to_cql2
44
36
  from pygeofilter.parsers.cql2_text import parse as parse_cql2_text
45
- from starlette.exceptions import HTTPException as StarletteHTTPException
46
37
 
47
38
  from eodag.config import load_stac_api_config
48
39
  from eodag.rest.cache import init_cache
@@ -59,23 +50,16 @@ from eodag.rest.core import (
59
50
  get_stac_extension_oseo,
60
51
  search_stac_items,
61
52
  )
62
- from eodag.rest.types.eodag_search import EODAGSearch
53
+ from eodag.rest.errors import add_exception_handlers
63
54
  from eodag.rest.types.queryables import QueryablesGetParams
64
55
  from eodag.rest.types.stac_search import SearchPostRequest, sortby2list
65
- from eodag.rest.utils import format_pydantic_error, str2json, str2list
66
- 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,
56
+ from eodag.rest.utils import (
57
+ LIVENESS_PROBE_PATH,
58
+ format_pydantic_error,
59
+ str2json,
60
+ str2list,
78
61
  )
62
+ from eodag.utils import parse_header, update_nested_dict
79
63
 
80
64
  if TYPE_CHECKING:
81
65
  from fastapi.types import DecoratedCallable
@@ -84,12 +68,6 @@ if TYPE_CHECKING:
84
68
  from starlette.responses import Response as StarletteResponse
85
69
 
86
70
  logger = logging.getLogger("eodag.rest.server")
87
- ERRORS_WITH_500_STATUS_CODE = {
88
- "MisconfiguredError",
89
- "AuthenticationError",
90
- "DownloadError",
91
- "RequestError",
92
- }
93
71
 
94
72
 
95
73
  class APIRouter(FastAPIRouter):
@@ -139,10 +117,21 @@ app = FastAPI(lifespan=lifespan, title="EODAG", docs_url="/api.html")
139
117
  stac_api_config = load_stac_api_config()
140
118
 
141
119
 
120
+ @router.api_route(
121
+ methods=["GET", "HEAD"],
122
+ path=LIVENESS_PROBE_PATH,
123
+ include_in_schema=False,
124
+ status_code=200,
125
+ )
126
+ async def liveness_probe(request: Request) -> dict[str, bool]:
127
+ "Endpoint meant to be used as liveness probe by deployment platforms"
128
+ return {"success": True}
129
+
130
+
142
131
  @router.api_route(
143
132
  methods=["GET", "HEAD"], path="/api", tags=["Capabilities"], include_in_schema=False
144
133
  )
145
- async def eodag_openapi(request: Request) -> Dict[str, Any]:
134
+ async def eodag_openapi(request: Request) -> dict[str, Any]:
146
135
  """Customized openapi"""
147
136
  logger.debug("URL: /api")
148
137
  if app.openapi_schema:
@@ -197,6 +186,8 @@ app.add_middleware(
197
186
  allow_headers=["*"],
198
187
  )
199
188
 
189
+ add_exception_handlers(app)
190
+
200
191
 
201
192
  @app.middleware("http")
202
193
  async def forward_middleware(
@@ -221,141 +212,10 @@ async def forward_middleware(
221
212
  return response
222
213
 
223
214
 
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
215
  @router.api_route(methods=["GET", "HEAD"], path="/", tags=["Capabilities"])
356
216
  async def catalogs_root(request: Request) -> ORJSONResponse:
357
217
  """STAC catalogs root"""
358
- logger.debug("URL: %s", request.url)
218
+ logger.info(f"{request.method} {request.state.url}")
359
219
 
360
220
  response = await get_stac_catalogs(
361
221
  request=request,
@@ -367,9 +227,9 @@ async def catalogs_root(request: Request) -> ORJSONResponse:
367
227
 
368
228
 
369
229
  @router.api_route(methods=["GET", "HEAD"], path="/conformance", tags=["Capabilities"])
370
- def conformance() -> ORJSONResponse:
230
+ def conformance(request: Request) -> ORJSONResponse:
371
231
  """STAC conformance"""
372
- logger.debug("URL: /conformance")
232
+ logger.info(f"{request.method} {request.state.url}")
373
233
  response = get_stac_conformance()
374
234
 
375
235
  return ORJSONResponse(response)
@@ -382,7 +242,7 @@ def conformance() -> ORJSONResponse:
382
242
  )
383
243
  def stac_extension_oseo(request: Request) -> ORJSONResponse:
384
244
  """STAC OGC / OpenSearch extension for EO"""
385
- logger.debug("URL: %s", request.url)
245
+ logger.info(f"{request.method} {request.state.url}")
386
246
  response = get_stac_extension_oseo(url=request.state.url)
387
247
 
388
248
  return ORJSONResponse(response)
@@ -398,14 +258,14 @@ def stac_collections_item_download(
398
258
  collection_id: str, item_id: str, request: Request
399
259
  ) -> StarletteResponse:
400
260
  """STAC collection item download"""
401
- logger.debug("URL: %s", request.url)
261
+ logger.info(f"{request.method} {request.state.url}")
402
262
 
403
263
  arguments = dict(request.query_params)
404
264
  provider = arguments.pop("provider", None)
405
265
 
406
266
  return download_stac_item(
407
267
  request=request,
408
- catalogs=[collection_id],
268
+ collection_id=collection_id,
409
269
  item_id=item_id,
410
270
  provider=provider,
411
271
  **arguments,
@@ -422,14 +282,14 @@ def stac_collections_item_download_asset(
422
282
  collection_id: str, item_id: str, asset: str, request: Request
423
283
  ):
424
284
  """STAC collection item asset download"""
425
- logger.debug("URL: %s", request.url)
285
+ logger.info(f"{request.method} {request.state.url}")
426
286
 
427
287
  arguments = dict(request.query_params)
428
288
  provider = arguments.pop("provider", None)
429
289
 
430
290
  return download_stac_item(
431
291
  request=request,
432
- catalogs=[collection_id],
292
+ collection_id=collection_id,
433
293
  item_id=item_id,
434
294
  provider=provider,
435
295
  asset=asset,
@@ -446,7 +306,7 @@ def stac_collections_item(
446
306
  collection_id: str, item_id: str, request: Request, provider: Optional[str] = None
447
307
  ) -> ORJSONResponse:
448
308
  """STAC collection item by id"""
449
- logger.debug("URL: %s", request.url)
309
+ logger.info(f"{request.method} {request.state.url}")
450
310
 
451
311
  search_request = SearchPostRequest(
452
312
  provider=provider, ids=[item_id], collections=[collection_id], limit=1
@@ -522,14 +382,25 @@ async def list_collection_queryables(
522
382
  :param collection_id: The identifier of the collection for which to retrieve queryable properties.
523
383
  :returns: A json object containing the list of available queryable properties for the specified collection.
524
384
  """
525
- logger.debug(f"URL: {request.url}")
526
- additional_params = dict(request.query_params)
385
+ logger.info(f"{request.method} {request.state.url}")
386
+ # split by `,` to handle list of parameters
387
+ additional_params = {k: v.split(",") for k, v in dict(request.query_params).items()}
527
388
  provider = additional_params.pop("provider", None)
528
389
 
390
+ datetime = additional_params.pop("datetime", None)
391
+
529
392
  queryables = await get_queryables(
530
393
  request,
531
- QueryablesGetParams(collection=collection_id, **additional_params),
532
- provider=provider,
394
+ QueryablesGetParams.model_validate(
395
+ {
396
+ **additional_params,
397
+ **{
398
+ "collection": collection_id,
399
+ "datetime": datetime[0] if datetime else None,
400
+ },
401
+ }
402
+ ),
403
+ provider=provider[0] if provider else None,
533
404
  )
534
405
 
535
406
  return ORJSONResponse(queryables)
@@ -545,7 +416,7 @@ async def collection_by_id(
545
416
  collection_id: str, request: Request, provider: Optional[str] = None
546
417
  ) -> ORJSONResponse:
547
418
  """STAC collection by id"""
548
- logger.debug("URL: %s", request.url)
419
+ logger.info(f"{request.method} {request.state.url}")
549
420
 
550
421
  response = await get_collection(
551
422
  request=request,
@@ -576,7 +447,7 @@ async def collections(
576
447
  Can be filtered using parameters: instrument, platform, platformSerialIdentifier, sensorType,
577
448
  processingLevel
578
449
  """
579
- logger.debug("URL: %s", request.url)
450
+ logger.info(f"{request.method} {request.state.url}")
580
451
 
581
452
  collections = await all_collections(
582
453
  request, provider, q, platform, instrument, constellation, datetime
@@ -584,161 +455,6 @@ async def collections(
584
455
  return ORJSONResponse(collections)
585
456
 
586
457
 
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
458
  @router.api_route(
743
459
  methods=["GET", "HEAD"],
744
460
  path="/queryables",
@@ -756,7 +472,7 @@ async def list_queryables(request: Request) -> ORJSONResponse:
756
472
  :param request: The incoming request object.
757
473
  :returns: A json object containing the list of available queryable terms.
758
474
  """
759
- logger.debug(f"URL: {request.url}")
475
+ logger.info(f"{request.method} {request.state.url}")
760
476
  additional_params = dict(request.query_params.items())
761
477
  provider = additional_params.pop("provider", None)
762
478
  queryables = await get_queryables(
@@ -789,7 +505,7 @@ def get_search(
789
505
  crunch: Optional[str] = None,
790
506
  ) -> ORJSONResponse:
791
507
  """Handler for GET /search"""
792
- logger.debug("URL: %s", request.state.url)
508
+ logger.info(f"{request.method} {request.state.url}")
793
509
 
794
510
  query_params = str(request.query_params)
795
511
 
@@ -843,7 +559,7 @@ def get_search(
843
559
  )
844
560
  async def post_search(request: Request) -> ORJSONResponse:
845
561
  """STAC post search"""
846
- logger.debug("URL: %s", request.url)
562
+ logger.info(f"{request.method} {request.state.url}")
847
563
 
848
564
  content_type = request.headers.get("Content-Type")
849
565
 
@@ -864,10 +580,12 @@ async def post_search(request: Request) -> ORJSONResponse:
864
580
 
865
581
  logger.debug("Body: %s", search_request.model_dump(exclude_none=True))
866
582
 
867
- response = search_stac_items(
868
- request=request,
869
- search_request=search_request,
583
+ response = await run_in_threadpool(
584
+ search_stac_items,
585
+ request,
586
+ search_request,
870
587
  )
588
+
871
589
  return ORJSONResponse(content=response, media_type="application/json")
872
590
 
873
591