eodag 3.0.1__py3-none-any.whl → 3.1.0b2__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 (87) hide show
  1. eodag/api/core.py +164 -127
  2. eodag/api/product/_assets.py +11 -11
  3. eodag/api/product/_product.py +45 -30
  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 +101 -85
  10. eodag/api/search_result.py +13 -23
  11. eodag/cli.py +26 -5
  12. eodag/config.py +78 -81
  13. eodag/plugins/apis/base.py +1 -1
  14. eodag/plugins/apis/ecmwf.py +46 -22
  15. eodag/plugins/apis/usgs.py +16 -15
  16. eodag/plugins/authentication/aws_auth.py +16 -13
  17. eodag/plugins/authentication/base.py +5 -3
  18. eodag/plugins/authentication/header.py +3 -3
  19. eodag/plugins/authentication/keycloak.py +4 -4
  20. eodag/plugins/authentication/oauth.py +7 -3
  21. eodag/plugins/authentication/openid_connect.py +16 -16
  22. eodag/plugins/authentication/sas_auth.py +4 -4
  23. eodag/plugins/authentication/token.py +41 -10
  24. eodag/plugins/authentication/token_exchange.py +1 -1
  25. eodag/plugins/base.py +4 -4
  26. eodag/plugins/crunch/base.py +4 -4
  27. eodag/plugins/crunch/filter_date.py +4 -4
  28. eodag/plugins/crunch/filter_latest_intersect.py +6 -6
  29. eodag/plugins/crunch/filter_latest_tpl_name.py +7 -7
  30. eodag/plugins/crunch/filter_overlap.py +4 -4
  31. eodag/plugins/crunch/filter_property.py +6 -7
  32. eodag/plugins/download/aws.py +58 -78
  33. eodag/plugins/download/base.py +38 -56
  34. eodag/plugins/download/creodias_s3.py +29 -0
  35. eodag/plugins/download/http.py +173 -183
  36. eodag/plugins/download/s3rest.py +10 -11
  37. eodag/plugins/manager.py +10 -20
  38. eodag/plugins/search/__init__.py +6 -5
  39. eodag/plugins/search/base.py +87 -44
  40. eodag/plugins/search/build_search_result.py +1067 -329
  41. eodag/plugins/search/cop_marine.py +22 -12
  42. eodag/plugins/search/creodias_s3.py +9 -73
  43. eodag/plugins/search/csw.py +11 -11
  44. eodag/plugins/search/data_request_search.py +16 -15
  45. eodag/plugins/search/qssearch.py +103 -187
  46. eodag/plugins/search/stac_list_assets.py +85 -0
  47. eodag/plugins/search/static_stac_search.py +3 -3
  48. eodag/resources/ext_product_types.json +1 -1
  49. eodag/resources/product_types.yml +663 -304
  50. eodag/resources/providers.yml +823 -1749
  51. eodag/resources/stac_api.yml +2 -2
  52. eodag/resources/user_conf_template.yml +11 -0
  53. eodag/rest/cache.py +2 -2
  54. eodag/rest/config.py +3 -3
  55. eodag/rest/core.py +112 -82
  56. eodag/rest/errors.py +5 -5
  57. eodag/rest/server.py +33 -14
  58. eodag/rest/stac.py +40 -38
  59. eodag/rest/types/collections_search.py +3 -3
  60. eodag/rest/types/eodag_search.py +29 -23
  61. eodag/rest/types/queryables.py +15 -16
  62. eodag/rest/types/stac_search.py +15 -25
  63. eodag/rest/utils/__init__.py +14 -21
  64. eodag/rest/utils/cql_evaluate.py +6 -6
  65. eodag/rest/utils/rfc3339.py +2 -2
  66. eodag/types/__init__.py +75 -28
  67. eodag/types/bbox.py +2 -2
  68. eodag/types/download_args.py +3 -3
  69. eodag/types/queryables.py +183 -72
  70. eodag/types/search_args.py +4 -4
  71. eodag/types/whoosh.py +127 -3
  72. eodag/utils/__init__.py +152 -50
  73. eodag/utils/exceptions.py +28 -21
  74. eodag/utils/import_system.py +2 -2
  75. eodag/utils/repr.py +65 -6
  76. eodag/utils/requests.py +13 -13
  77. eodag/utils/rest.py +2 -2
  78. eodag/utils/s3.py +208 -0
  79. eodag/utils/stac_reader.py +10 -10
  80. {eodag-3.0.1.dist-info → eodag-3.1.0b2.dist-info}/METADATA +77 -76
  81. eodag-3.1.0b2.dist-info/RECORD +113 -0
  82. {eodag-3.0.1.dist-info → eodag-3.1.0b2.dist-info}/WHEEL +1 -1
  83. {eodag-3.0.1.dist-info → eodag-3.1.0b2.dist-info}/entry_points.txt +4 -2
  84. eodag/utils/constraints.py +0 -244
  85. eodag-3.0.1.dist-info/RECORD +0 -109
  86. {eodag-3.0.1.dist-info → eodag-3.1.0b2.dist-info}/LICENSE +0 -0
  87. {eodag-3.0.1.dist-info → eodag-3.1.0b2.dist-info}/top_level.txt +0 -0
@@ -799,7 +799,7 @@ components:
799
799
  description: |
800
800
  Sentinel-2 is a wide-swath, high-resolution, multi-spectral
801
801
  imaging mission...
802
- license: proprietary
802
+ license: other
803
803
  keywords:
804
804
  - copernicus
805
805
  - esa
@@ -1416,7 +1416,7 @@ components:
1416
1416
  description: |-
1417
1417
  License(s) of the data as a SPDX
1418
1418
  [License identifier](https://spdx.org/licenses/). Alternatively, use
1419
- `proprietary` if the license is not on the SPDX license list or
1419
+ `other` if the license is not on the SPDX license list or
1420
1420
  `various` if multiple licenses apply. In these two cases links to the
1421
1421
  license texts SHOULD be added, see the `license` link relation type.
1422
1422
 
@@ -157,6 +157,17 @@ geodes:
157
157
  download:
158
158
  extract:
159
159
  output_dir:
160
+ geodes:
161
+ priority: # Lower value means lower priority (Default: 0)
162
+ search: # Search parameters configuration
163
+ auth:
164
+ credentials:
165
+ aws_access_key_id:
166
+ aws_secret_access_key:
167
+ aws_session_token:
168
+ download:
169
+ extract:
170
+ output_dir:
160
171
  hydroweb_next:
161
172
  priority: # Lower value means lower priority (Default: 0)
162
173
  search: # Search parameters configuration
eodag/rest/cache.py CHANGED
@@ -16,7 +16,7 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
  import logging
19
- from typing import Any, Callable, Coroutine, Dict, TypeVar, cast
19
+ from typing import Any, Callable, Coroutine, TypeVar, cast
20
20
 
21
21
  import orjson
22
22
  from cachetools import LRUCache
@@ -48,7 +48,7 @@ async def cached(
48
48
  host_cache_key = f"{cache_key}:{host}"
49
49
 
50
50
  try:
51
- c: Dict[str, Any] = request.app.state.cache
51
+ c: dict[str, Any] = request.app.state.cache
52
52
 
53
53
  if cached := c.get(host_cache_key):
54
54
  logger.debug("Cache result hit")
eodag/rest/config.py CHANGED
@@ -18,7 +18,7 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  from functools import lru_cache
21
- from typing import Annotated, List, Union
21
+ from typing import Annotated, Union
22
22
 
23
23
  from pydantic import Field
24
24
  from pydantic.functional_validators import BeforeValidator
@@ -28,7 +28,7 @@ from typing_extensions import Doc
28
28
  from eodag.rest.constants import DEFAULT_MAXSIZE, DEFAULT_TTL
29
29
 
30
30
 
31
- def str2liststr(raw: Union[str, List[str]]) -> List[str]:
31
+ def str2liststr(raw: Union[str, list[str]]) -> list[str]:
32
32
  """Convert str to list[str]"""
33
33
  if isinstance(raw, list):
34
34
  return raw
@@ -49,7 +49,7 @@ class Settings(BaseSettings):
49
49
  stac_api_landing_id: str = "eodag-stac-api"
50
50
 
51
51
  origin_url_blacklist: Annotated[
52
- Union[str, List[str]],
52
+ Union[str, list[str]],
53
53
  BeforeValidator(str2liststr),
54
54
  Doc(
55
55
  "Hide from clients items assets' alternative URLs starting with URLs from the list"
eodag/rest/core.py CHANGED
@@ -33,7 +33,6 @@ from requests.models import Response as RequestsResponse
33
33
  import eodag
34
34
  from eodag import EOProduct
35
35
  from eodag.api.product.metadata_mapping import (
36
- DEFAULT_METADATA_MAPPING,
37
36
  NOT_AVAILABLE,
38
37
  OFFLINE_STATUS,
39
38
  ONLINE_STATUS,
@@ -54,11 +53,7 @@ from eodag.rest.constants import (
54
53
  from eodag.rest.errors import ResponseSearchError
55
54
  from eodag.rest.stac import StacCatalog, StacCollection, StacCommon, StacItem
56
55
  from eodag.rest.types.eodag_search import EODAGSearch
57
- from eodag.rest.types.queryables import (
58
- QueryablesGetParams,
59
- StacQueryableProperty,
60
- StacQueryables,
61
- )
56
+ from eodag.rest.types.queryables import QueryablesGetParams, StacQueryables
62
57
  from eodag.rest.types.stac_search import SearchPostRequest
63
58
  from eodag.rest.utils import (
64
59
  Cruncher,
@@ -82,7 +77,7 @@ from eodag.utils.exceptions import (
82
77
  )
83
78
 
84
79
  if TYPE_CHECKING:
85
- from typing import Any, Dict, List, Optional, Union
80
+ from typing import Any, Optional, Union
86
81
 
87
82
  from fastapi import Request
88
83
  from requests.auth import AuthBase
@@ -121,12 +116,12 @@ def get_home_page_content(base_url: str, ipp: Optional[int] = None) -> str:
121
116
  reason="Function internally used by get_home_page_content, also deprecated",
122
117
  version="2.6.1",
123
118
  )
124
- def format_product_types(product_types: List[Dict[str, Any]]) -> str:
119
+ def format_product_types(product_types: list[dict[str, Any]]) -> str:
125
120
  """Format product_types
126
121
 
127
122
  :param product_types: A list of EODAG product types as returned by the core api
128
123
  """
129
- result: List[str] = []
124
+ result: list[str] = []
130
125
  for pt in product_types:
131
126
  result.append(f'* *__{pt["ID"]}__*: {pt["abstract"]}')
132
127
  return "\n".join(sorted(result))
@@ -135,7 +130,7 @@ def format_product_types(product_types: List[Dict[str, Any]]) -> str:
135
130
  def search_stac_items(
136
131
  request: Request,
137
132
  search_request: SearchPostRequest,
138
- ) -> Dict[str, Any]:
133
+ ) -> dict[str, Any]:
139
134
  """
140
135
  Search and retrieve STAC items based on the given search request.
141
136
 
@@ -193,28 +188,18 @@ def search_stac_items(
193
188
  results.number_matched = len(results)
194
189
  total = len(results)
195
190
 
196
- elif time_interval_overlap(eodag_args, catalog):
197
- criteria = {
198
- **catalog.search_args,
199
- **eodag_args.model_dump(exclude_none=True),
200
- }
191
+ else:
192
+ criteria = eodag_args.model_dump(exclude_none=True)
201
193
  # remove provider prefixes
202
- stac_extensions = stac_config["extensions"]
203
- keys_to_update = {}
204
- for key in criteria:
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):
205
197
  if ":" in key and key.split(":")[0] not in stac_extensions:
206
198
  new_key = key.split(":")[1]
207
- keys_to_update[key] = new_key
208
- for key, new_key in keys_to_update.items():
209
- criteria[new_key] = criteria[key]
210
- criteria.pop(key)
199
+ criteria[new_key] = criteria.pop(key)
211
200
 
212
201
  results = eodag_api.search(count=True, **criteria)
213
202
  total = results.number_matched or 0
214
- else:
215
- # return empty results
216
- results = SearchResult([], 0)
217
- total = 0
218
203
 
219
204
  if len(results) == 0 and results.errors:
220
205
  raise ResponseSearchError(results.errors)
@@ -324,8 +309,8 @@ def download_stac_item(
324
309
 
325
310
  def _order_and_update(
326
311
  product: EOProduct,
327
- auth: Union[AuthBase, Dict[str, str], None],
328
- query_args: Dict[str, Any],
312
+ auth: Union[AuthBase, dict[str, str], None],
313
+ query_args: dict[str, Any],
329
314
  ) -> None:
330
315
  """Order product if needed and update given kwargs with order-status-dict"""
331
316
  if product.properties.get("storageStatus") != ONLINE_STATUS and hasattr(
@@ -342,13 +327,11 @@ def _order_and_update(
342
327
  if (
343
328
  product.properties.get("storageStatus") != ONLINE_STATUS
344
329
  and NOT_AVAILABLE in product.properties.get("orderStatusLink", "")
345
- and hasattr(product.downloader, "order_download")
330
+ and hasattr(product.downloader, "_order")
346
331
  ):
347
332
  # first order
348
333
  logger.debug("Order product")
349
- order_status_dict = product.downloader.order_download(
350
- product=product, auth=auth
351
- )
334
+ order_status_dict = product.downloader._order(product=product, auth=auth)
352
335
  query_args.update(order_status_dict or {})
353
336
 
354
337
  if (
@@ -359,18 +342,18 @@ def _order_and_update(
359
342
  product.properties["storageStatus"] = STAGING_STATUS
360
343
 
361
344
  if product.properties.get("storageStatus") == STAGING_STATUS and hasattr(
362
- product.downloader, "order_download_status"
345
+ product.downloader, "_order_status"
363
346
  ):
364
347
  # check order status if needed
365
348
  logger.debug("Checking product order status")
366
- product.downloader.order_download_status(product=product, auth=auth)
349
+ product.downloader._order_status(product=product, auth=auth)
367
350
 
368
351
  if product.properties.get("storageStatus") != ONLINE_STATUS:
369
352
  raise NotAvailableError("Product is not available yet")
370
353
 
371
354
 
372
355
  @lru_cache(maxsize=1)
373
- def get_detailled_collections_list() -> List[Dict[str, Any]]:
356
+ def get_detailled_collections_list() -> list[dict[str, Any]]:
374
357
  """Returns detailled collections / product_types list as a list of
375
358
  config dicts
376
359
 
@@ -387,7 +370,7 @@ async def all_collections(
387
370
  instrument: Optional[str] = None,
388
371
  constellation: Optional[str] = None,
389
372
  datetime: Optional[str] = None,
390
- ) -> Dict[str, Any]:
373
+ ) -> dict[str, Any]:
391
374
  """Build STAC collections
392
375
 
393
376
  :param url: Requested URL
@@ -397,7 +380,7 @@ async def all_collections(
397
380
  :returns: Collections dictionary
398
381
  """
399
382
 
400
- async def _fetch() -> Dict[str, Any]:
383
+ async def _fetch() -> dict[str, Any]:
401
384
  stac_collection = StacCollection(
402
385
  url=request.state.url,
403
386
  stac_config=stac_config,
@@ -417,7 +400,10 @@ async def all_collections(
417
400
  # # parse f-strings
418
401
  format_args = deepcopy(stac_config)
419
402
  format_args["collections"].update(
420
- {"url": stac_collection.url, "root": stac_collection.root}
403
+ {
404
+ "url": stac_collection.url,
405
+ "root": stac_collection.root,
406
+ }
421
407
  )
422
408
 
423
409
  collections["links"] = [
@@ -436,7 +422,7 @@ async def all_collections(
436
422
 
437
423
  async def get_collection(
438
424
  request: Request, collection_id: str, provider: Optional[str] = None
439
- ) -> Dict[str, Any]:
425
+ ) -> dict[str, Any]:
440
426
  """Build STAC collection by id
441
427
 
442
428
  :param url: Requested URL
@@ -446,7 +432,7 @@ async def get_collection(
446
432
  :returns: Collection dictionary
447
433
  """
448
434
 
449
- async def _fetch() -> Dict[str, Any]:
435
+ async def _fetch() -> dict[str, Any]:
450
436
  stac_collection = StacCollection(
451
437
  url=request.state.url,
452
438
  stac_config=stac_config,
@@ -469,7 +455,7 @@ async def get_stac_catalogs(
469
455
  request: Request,
470
456
  url: str,
471
457
  provider: Optional[str] = None,
472
- ) -> Dict[str, Any]:
458
+ ) -> dict[str, Any]:
473
459
  """Build STAC catalog
474
460
 
475
461
  :param url: Requested URL
@@ -478,7 +464,7 @@ async def get_stac_catalogs(
478
464
  :returns: Catalog dictionary
479
465
  """
480
466
 
481
- async def _fetch() -> Dict[str, Any]:
467
+ async def _fetch() -> dict[str, Any]:
482
468
  return StacCatalog(
483
469
  url=url,
484
470
  stac_config=stac_config,
@@ -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
- ["start", "end"]
484
+ [
485
+ "start",
486
+ "end",
487
+ ]
499
488
  ) <= set(catalog.search_args.keys()):
500
489
  return True
501
490
 
@@ -534,7 +523,7 @@ def time_interval_overlap(eodag_args: EODAGSearch, catalog: StacCatalog) -> bool
534
523
 
535
524
 
536
525
  @lru_cache(maxsize=1)
537
- def get_stac_conformance() -> Dict[str, str]:
526
+ def get_stac_conformance() -> dict[str, str]:
538
527
  """Build STAC conformance
539
528
 
540
529
  :returns: conformance dictionary
@@ -551,7 +540,7 @@ def get_stac_api_version() -> str:
551
540
 
552
541
 
553
542
  @lru_cache(maxsize=1)
554
- def get_stac_extension_oseo(url: str) -> Dict[str, str]:
543
+ 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
@@ -582,60 +571,96 @@ async def get_queryables(
582
571
  request: Request,
583
572
  params: QueryablesGetParams,
584
573
  provider: Optional[str] = None,
585
- ) -> Dict[str, Any]:
574
+ ) -> dict[str, Any]:
586
575
  """Fetch the queryable properties for a collection.
587
576
 
588
577
  :param collection_id: The ID of the collection.
589
578
  :returns: A set containing the STAC standardized queryable properties for a collection.
590
579
  """
591
580
 
592
- async def _fetch() -> Dict[str, Any]:
581
+ async def _fetch() -> dict[str, Any]:
593
582
  python_queryables = eodag_api.list_queryables(
594
- provider=provider, **params.model_dump(exclude_none=True, by_alias=True)
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
587
 
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
-
603
- stac_queryables: Dict[str, StacQueryableProperty] = deepcopy(
604
- StacQueryables.default_properties
588
+ python_queryables_json = python_queryables.get_model().model_json_schema(
589
+ by_alias=True
605
590
  )
591
+
592
+ properties: dict[str, Any] = python_queryables_json["properties"]
593
+ required: list[str] = python_queryables_json.get("required") or []
594
+
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
+
606
603
  # get stac default properties to set prefixes
607
604
  stac_item_properties = list(stac_config["item"]["properties"].values())
608
- stac_item_properties.extend(list(stac_queryables.keys()))
609
- ignore = stac_config["metadata_ignore"]
610
- stac_item_properties.extend(ignore)
611
- default_mapping = DEFAULT_METADATA_MAPPING.keys()
612
- for param, queryable in python_queryables.items():
613
- if param in default_mapping and not any(
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(
614
609
  param in str(prop) for prop in stac_item_properties
615
610
  ):
616
611
  param = f"oseo:{param}"
617
612
  stac_param = EODAGSearch.to_stac(param, stac_item_properties, provider)
618
- # only keep "datetime" queryable for dates
619
- if stac_param in stac_queryables or stac_param in (
620
- "start_datetime",
621
- "end_datetime",
622
- ):
623
- continue
624
613
 
625
- stac_queryables[
626
- stac_param
627
- ] = StacQueryableProperty.from_python_field_definition(
628
- stac_param, queryable
629
- )
614
+ queryable["title"] = stac_param.split(":")[-1]
615
+
616
+ # remove null default values
617
+ if not queryable.get("default"):
618
+ queryable.pop("default", None)
630
619
 
631
- if params.collection:
632
- stac_queryables.pop("collection")
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
633
656
 
634
657
  return StacQueryables(
635
658
  q_id=request.state.url,
636
- additional_properties=bool(not params.collection),
637
- properties=stac_queryables,
638
- ).model_dump(mode="json", by_alias=True)
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)
639
664
 
640
665
  hashed_queryables = hash(params.model_dump_json())
641
666
  return await cached(
@@ -662,7 +687,7 @@ def crunch_products(
662
687
  f'Unknown crunch name. Use one of: {", ".join(crunchers.keys())}'
663
688
  )
664
689
 
665
- cruncher_config: Dict[str, Any] = {}
690
+ cruncher_config: dict[str, Any] = {}
666
691
  for config_param in cruncher.config_params:
667
692
  config_param_value = kwargs.get(config_param)
668
693
  if not config_param_value:
@@ -695,17 +720,22 @@ def eodag_api_init() -> None:
695
720
  ext_col = StacCollection.ext_stac_collections.get(key)
696
721
  if not ext_col:
697
722
  continue
698
- platform: Union[str, List[str]] = ext_col.get("summaries", {}).get(
723
+ platform: Union[str, list[str]] = ext_col.get("summaries", {}).get(
699
724
  "platform"
700
725
  )
701
- constellation: Union[str, List[str]] = ext_col.get("summaries", {}).get(
726
+ constellation: Union[str, list[str]] = ext_col.get("summaries", {}).get(
702
727
  "constellation"
703
728
  )
729
+ processing_level: Union[str, list[str]] = ext_col.get("summaries", {}).get(
730
+ "processing:level"
731
+ )
704
732
  # Check if platform or constellation are lists and join them into a string if they are
705
733
  if isinstance(platform, list):
706
734
  platform = ",".join(platform)
707
735
  if isinstance(constellation, list):
708
736
  constellation = ",".join(constellation)
737
+ if isinstance(processing_level, list):
738
+ processing_level = ",".join(processing_level)
709
739
 
710
740
  update_fields = {
711
741
  "title": ext_col.get("title"),
@@ -716,7 +746,7 @@ def eodag_api_init() -> None:
716
746
  ),
717
747
  "platform": constellation,
718
748
  "platformSerialIdentifier": platform,
719
- "processingLevel": ext_col.get("summaries", {}).get("processing:level"),
749
+ "processingLevel": processing_level,
720
750
  "license": ext_col["license"],
721
751
  "missionStartDate": ext_col["extent"]["temporal"]["interval"][0][0],
722
752
  "missionEndDate": ext_col["extent"]["temporal"]["interval"][-1][1],
eodag/rest/errors.py CHANGED
@@ -16,7 +16,7 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
  import logging
19
- from typing import Dict, List, Tuple, Union
19
+ from typing import Union
20
20
 
21
21
  from fastapi import FastAPI, Request
22
22
  from fastapi.responses import ORJSONResponse
@@ -56,16 +56,16 @@ logger = logging.getLogger("eodag.rest.server")
56
56
  class ResponseSearchError(Exception):
57
57
  """Represent a EODAG search error response"""
58
58
 
59
- def __init__(self, errors: List[Tuple[str, Exception]]) -> None:
59
+ def __init__(self, errors: list[tuple[str, Exception]]) -> None:
60
60
  self._errors = errors
61
61
 
62
62
  @property
63
- def errors(self) -> List[Dict[str, Union[str, int]]]:
63
+ def errors(self) -> list[dict[str, Union[str, int]]]:
64
64
  """return errors as a list of dict"""
65
- error_list: List[Dict[str, Union[str, int]]] = []
65
+ error_list: list[dict[str, Union[str, int]]] = []
66
66
  for name, exception in self._errors:
67
67
 
68
- error_dict: Dict[str, Union[str, int]] = {
68
+ error_dict: dict[str, Union[str, int]] = {
69
69
  "provider": name,
70
70
  "error": exception.__class__.__name__,
71
71
  }
eodag/rest/server.py CHANGED
@@ -23,15 +23,7 @@ import re
23
23
  from contextlib import asynccontextmanager
24
24
  from importlib.metadata import version
25
25
  from json import JSONDecodeError
26
- from typing import (
27
- TYPE_CHECKING,
28
- Any,
29
- AsyncGenerator,
30
- Awaitable,
31
- Callable,
32
- Dict,
33
- Optional,
34
- )
26
+ from typing import TYPE_CHECKING, Any, AsyncGenerator, Awaitable, Callable, Optional
35
27
 
36
28
  from fastapi import APIRouter as FastAPIRouter
37
29
  from fastapi import FastAPI, HTTPException, Request
@@ -61,7 +53,12 @@ from eodag.rest.core import (
61
53
  from eodag.rest.errors import add_exception_handlers
62
54
  from eodag.rest.types.queryables import QueryablesGetParams
63
55
  from eodag.rest.types.stac_search import SearchPostRequest, sortby2list
64
- from eodag.rest.utils import format_pydantic_error, str2json, str2list
56
+ from eodag.rest.utils import (
57
+ LIVENESS_PROBE_PATH,
58
+ format_pydantic_error,
59
+ str2json,
60
+ str2list,
61
+ )
65
62
  from eodag.utils import parse_header, update_nested_dict
66
63
 
67
64
  if TYPE_CHECKING:
@@ -120,10 +117,21 @@ app = FastAPI(lifespan=lifespan, title="EODAG", docs_url="/api.html")
120
117
  stac_api_config = load_stac_api_config()
121
118
 
122
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
+
123
131
  @router.api_route(
124
132
  methods=["GET", "HEAD"], path="/api", tags=["Capabilities"], include_in_schema=False
125
133
  )
126
- async def eodag_openapi(request: Request) -> Dict[str, Any]:
134
+ async def eodag_openapi(request: Request) -> dict[str, Any]:
127
135
  """Customized openapi"""
128
136
  logger.debug("URL: /api")
129
137
  if app.openapi_schema:
@@ -375,13 +383,24 @@ async def list_collection_queryables(
375
383
  :returns: A json object containing the list of available queryable properties for the specified collection.
376
384
  """
377
385
  logger.info(f"{request.method} {request.state.url}")
378
- additional_params = dict(request.query_params)
386
+ # split by `,` to handle list of parameters
387
+ additional_params = {k: v.split(",") for k, v in dict(request.query_params).items()}
379
388
  provider = additional_params.pop("provider", None)
380
389
 
390
+ datetime = additional_params.pop("datetime", None)
391
+
381
392
  queryables = await get_queryables(
382
393
  request,
383
- QueryablesGetParams(collection=collection_id, **additional_params),
384
- 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,
385
404
  )
386
405
 
387
406
  return ORJSONResponse(queryables)