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/core.py CHANGED
@@ -50,13 +50,10 @@ from eodag.rest.constants import (
50
50
  CACHE_KEY_COLLECTIONS,
51
51
  CACHE_KEY_QUERYABLES,
52
52
  )
53
+ from eodag.rest.errors import ResponseSearchError
53
54
  from eodag.rest.stac import StacCatalog, StacCollection, StacCommon, StacItem
54
55
  from eodag.rest.types.eodag_search import EODAGSearch
55
- from eodag.rest.types.queryables import (
56
- QueryablesGetParams,
57
- StacQueryableProperty,
58
- StacQueryables,
59
- )
56
+ from eodag.rest.types.queryables import QueryablesGetParams, StacQueryables
60
57
  from eodag.rest.types.stac_search import SearchPostRequest
61
58
  from eodag.rest.utils import (
62
59
  Cruncher,
@@ -80,7 +77,7 @@ from eodag.utils.exceptions import (
80
77
  )
81
78
 
82
79
  if TYPE_CHECKING:
83
- from typing import Any, Dict, List, Optional, Tuple, Union
80
+ from typing import Any, Optional, Union
84
81
 
85
82
  from fastapi import Request
86
83
  from requests.auth import AuthBase
@@ -119,12 +116,12 @@ def get_home_page_content(base_url: str, ipp: Optional[int] = None) -> str:
119
116
  reason="Function internally used by get_home_page_content, also deprecated",
120
117
  version="2.6.1",
121
118
  )
122
- def format_product_types(product_types: List[Dict[str, Any]]) -> str:
119
+ def format_product_types(product_types: list[dict[str, Any]]) -> str:
123
120
  """Format product_types
124
121
 
125
122
  :param product_types: A list of EODAG product types as returned by the core api
126
123
  """
127
- result: List[str] = []
124
+ result: list[str] = []
128
125
  for pt in product_types:
129
126
  result.append(f'* *__{pt["ID"]}__*: {pt["abstract"]}')
130
127
  return "\n".join(sorted(result))
@@ -133,17 +130,15 @@ def format_product_types(product_types: List[Dict[str, Any]]) -> str:
133
130
  def search_stac_items(
134
131
  request: Request,
135
132
  search_request: SearchPostRequest,
136
- catalogs: Optional[List[str]] = None,
137
- ) -> Dict[str, Any]:
133
+ ) -> dict[str, Any]:
138
134
  """
139
- Search and retrieve STAC items from the given catalogs.
135
+ Search and retrieve STAC items based on the given search request.
140
136
 
141
- This function takes a search request and optional catalogs list, performs a search using EODAG API, and returns a
137
+ This function takes a search request, performs a search using EODAG API, and returns a
142
138
  dictionary of STAC items.
143
139
 
144
140
  :param request: The incoming HTTP request with state information.
145
- :param search_request: The search criteria for STAC items.
146
- :param catalogs: (optional) A list of catalogs to search within. Defaults to None.
141
+ :param search_request: The search criteria for STAC items
147
142
  :returns: A dictionary containing the STAC items and related metadata.
148
143
 
149
144
  The function handles the conversion of search criteria into STAC and EODAG compatible formats, validates the input
@@ -165,60 +160,54 @@ def search_stac_items(
165
160
  if search_request.spatial_filter:
166
161
  stac_args["geometry"] = search_request.spatial_filter
167
162
  try:
168
- eodag_args = EODAGSearch.model_validate(
169
- stac_args, context={"isCatalog": bool(catalogs)}
170
- )
163
+ eodag_args = EODAGSearch.model_validate(stac_args)
171
164
  except pydanticValidationError as e:
172
165
  raise ValidationError(format_pydantic_error(e)) from e
173
166
 
174
167
  catalog_url = re.sub("/items.*", "", request.state.url)
175
-
176
168
  catalog = StacCatalog(
177
- url=(
178
- catalog_url
179
- if catalogs
180
- else catalog_url.replace(
181
- "/search", f"/collections/{eodag_args.productType}"
182
- )
183
- ),
169
+ url=catalog_url.replace("/search", f"/collections/{eodag_args.productType}"),
184
170
  stac_config=stac_config,
185
171
  root=request.state.url_root,
186
172
  provider=eodag_args.provider,
187
173
  eodag_api=eodag_api,
188
- catalogs=catalogs or [eodag_args.productType], # type: ignore
174
+ collection=eodag_args.productType, # type: ignore
189
175
  )
190
176
 
191
177
  # get products by ids
192
178
  if eodag_args.ids:
193
- search_results = SearchResult([])
179
+ results = SearchResult([])
194
180
  for item_id in eodag_args.ids:
195
- sr = eodag_api.search(
196
- id=item_id,
197
- productType=catalogs[0] if catalogs else eodag_args.productType,
198
- provider=eodag_args.provider,
181
+ results.extend(
182
+ eodag_api.search(
183
+ id=item_id,
184
+ productType=eodag_args.productType,
185
+ provider=eodag_args.provider,
186
+ )
199
187
  )
200
- search_results.extend(sr)
201
- search_results.number_matched = len(search_results)
202
- total = len(search_results)
203
-
204
- elif time_interval_overlap(eodag_args, catalog):
205
- criteria = {
206
- **catalog.search_args,
207
- **eodag_args.model_dump(exclude_none=True),
208
- }
188
+ results.number_matched = len(results)
189
+ total = len(results)
209
190
 
210
- search_results = eodag_api.search(count=True, **criteria)
211
- total = search_results.number_matched or 0
212
- if search_request.crunch:
213
- search_results = crunch_products(
214
- search_results, search_request.crunch, **criteria
215
- )
216
191
  else:
217
- # return empty results
218
- search_results = SearchResult([], 0)
219
- total = 0
192
+ criteria = eodag_args.model_dump(exclude_none=True)
193
+ # remove provider prefixes
194
+ # quickfix for ecmwf fake extension to not impact items creation
195
+ stac_extensions = list(stac_config["extensions"].keys()) + ["ecmwf"]
196
+ for key in list(criteria):
197
+ if ":" in key and key.split(":")[0] not in stac_extensions:
198
+ new_key = key.split(":")[1]
199
+ criteria[new_key] = criteria.pop(key)
200
+
201
+ results = eodag_api.search(count=True, **criteria)
202
+ total = results.number_matched or 0
203
+
204
+ if len(results) == 0 and results.errors:
205
+ raise ResponseSearchError(results.errors)
206
+
207
+ if search_request.crunch:
208
+ results = crunch_products(results, search_request.crunch, **criteria)
220
209
 
221
- for record in search_results:
210
+ for record in results:
222
211
  record.product_type = eodag_api.get_alias_from_product_type(record.product_type)
223
212
 
224
213
  items = StacItem(
@@ -228,7 +217,7 @@ def search_stac_items(
228
217
  eodag_api=eodag_api,
229
218
  root=request.state.url_root,
230
219
  ).get_stac_items(
231
- search_results=search_results,
220
+ search_results=results,
232
221
  total=total,
233
222
  next_link=get_next_link(
234
223
  request, search_request, total, eodag_args.items_per_page
@@ -243,7 +232,7 @@ def search_stac_items(
243
232
 
244
233
  def download_stac_item(
245
234
  request: Request,
246
- catalogs: List[str],
235
+ collection_id: str,
247
236
  item_id: str,
248
237
  provider: Optional[str] = None,
249
238
  asset: Optional[str] = None,
@@ -251,13 +240,13 @@ def download_stac_item(
251
240
  ) -> Response:
252
241
  """Download item
253
242
 
254
- :param catalogs: Catalogs list (only first is used as product_type)
243
+ :param collection_id: id of the product type
255
244
  :param item_id: Product ID
256
245
  :param provider: (optional) Chosen provider
257
246
  :param kwargs: additional download parameters
258
247
  :returns: a stream of the downloaded data (zip file)
259
248
  """
260
- product_type = catalogs[0]
249
+ product_type = collection_id
261
250
 
262
251
  search_results = eodag_api.search(
263
252
  id=item_id, productType=product_type, provider=provider, **kwargs
@@ -320,8 +309,8 @@ def download_stac_item(
320
309
 
321
310
  def _order_and_update(
322
311
  product: EOProduct,
323
- auth: Union[AuthBase, Dict[str, str], None],
324
- query_args: Dict[str, Any],
312
+ auth: Union[AuthBase, dict[str, str], None],
313
+ query_args: dict[str, Any],
325
314
  ) -> None:
326
315
  """Order product if needed and update given kwargs with order-status-dict"""
327
316
  if product.properties.get("storageStatus") != ONLINE_STATUS and hasattr(
@@ -338,13 +327,11 @@ def _order_and_update(
338
327
  if (
339
328
  product.properties.get("storageStatus") != ONLINE_STATUS
340
329
  and NOT_AVAILABLE in product.properties.get("orderStatusLink", "")
341
- and hasattr(product.downloader, "order_download")
330
+ and hasattr(product.downloader, "_order")
342
331
  ):
343
332
  # first order
344
333
  logger.debug("Order product")
345
- order_status_dict = product.downloader.order_download(
346
- product=product, auth=auth
347
- )
334
+ order_status_dict = product.downloader._order(product=product, auth=auth)
348
335
  query_args.update(order_status_dict or {})
349
336
 
350
337
  if (
@@ -355,18 +342,18 @@ def _order_and_update(
355
342
  product.properties["storageStatus"] = STAGING_STATUS
356
343
 
357
344
  if product.properties.get("storageStatus") == STAGING_STATUS and hasattr(
358
- product.downloader, "order_download_status"
345
+ product.downloader, "_order_status"
359
346
  ):
360
347
  # check order status if needed
361
348
  logger.debug("Checking product order status")
362
- product.downloader.order_download_status(product=product, auth=auth)
349
+ product.downloader._order_status(product=product, auth=auth)
363
350
 
364
351
  if product.properties.get("storageStatus") != ONLINE_STATUS:
365
352
  raise NotAvailableError("Product is not available yet")
366
353
 
367
354
 
368
355
  @lru_cache(maxsize=1)
369
- def get_detailled_collections_list() -> List[Dict[str, Any]]:
356
+ def get_detailled_collections_list() -> list[dict[str, Any]]:
370
357
  """Returns detailled collections / product_types list as a list of
371
358
  config dicts
372
359
 
@@ -383,17 +370,17 @@ async def all_collections(
383
370
  instrument: Optional[str] = None,
384
371
  constellation: Optional[str] = None,
385
372
  datetime: Optional[str] = None,
386
- ) -> Dict[str, Any]:
373
+ ) -> dict[str, Any]:
387
374
  """Build STAC collections
388
375
 
389
376
  :param url: Requested URL
390
377
  :param root: The API root
391
378
  :param filters: Search collections filters
392
379
  :param provider: (optional) Chosen provider
393
- :returns: Collections dictionnary
380
+ :returns: Collections dictionary
394
381
  """
395
382
 
396
- async def _fetch() -> Dict[str, Any]:
383
+ async def _fetch() -> dict[str, Any]:
397
384
  stac_collection = StacCollection(
398
385
  url=request.state.url,
399
386
  stac_config=stac_config,
@@ -413,7 +400,10 @@ async def all_collections(
413
400
  # # parse f-strings
414
401
  format_args = deepcopy(stac_config)
415
402
  format_args["collections"].update(
416
- {"url": stac_collection.url, "root": stac_collection.root}
403
+ {
404
+ "url": stac_collection.url,
405
+ "root": stac_collection.root,
406
+ }
417
407
  )
418
408
 
419
409
  collections["links"] = [
@@ -423,14 +413,16 @@ async def all_collections(
423
413
  collections = format_dict_items(collections, **format_args)
424
414
  return collections
425
415
 
426
- hashed_collections = hash(f"{provider}:{q}:{platform}:{instrument}:{constellation}")
416
+ hashed_collections = hash(
417
+ f"{provider}:{q}:{platform}:{instrument}:{constellation}:{datetime}"
418
+ )
427
419
  cache_key = f"{CACHE_KEY_COLLECTIONS}:{hashed_collections}"
428
420
  return await cached(_fetch, cache_key, request)
429
421
 
430
422
 
431
423
  async def get_collection(
432
424
  request: Request, collection_id: str, provider: Optional[str] = None
433
- ) -> Dict[str, Any]:
425
+ ) -> dict[str, Any]:
434
426
  """Build STAC collection by id
435
427
 
436
428
  :param url: Requested URL
@@ -440,7 +432,7 @@ async def get_collection(
440
432
  :returns: Collection dictionary
441
433
  """
442
434
 
443
- async def _fetch() -> Dict[str, Any]:
435
+ async def _fetch() -> dict[str, Any]:
444
436
  stac_collection = StacCollection(
445
437
  url=request.state.url,
446
438
  stac_config=stac_config,
@@ -462,32 +454,26 @@ async def get_collection(
462
454
  async def get_stac_catalogs(
463
455
  request: Request,
464
456
  url: str,
465
- catalogs: Optional[Tuple[str, ...]] = None,
466
457
  provider: Optional[str] = None,
467
- ) -> Dict[str, Any]:
458
+ ) -> dict[str, Any]:
468
459
  """Build STAC catalog
469
460
 
470
461
  :param url: Requested URL
471
462
  :param root: (optional) API root
472
- :param catalogs: (optional) Catalogs list
473
463
  :param provider: (optional) Chosen provider
474
464
  :returns: Catalog dictionary
475
465
  """
476
466
 
477
- async def _fetch() -> Dict[str, Any]:
467
+ async def _fetch() -> dict[str, Any]:
478
468
  return StacCatalog(
479
469
  url=url,
480
470
  stac_config=stac_config,
481
471
  root=request.state.url_root,
482
472
  provider=provider,
483
473
  eodag_api=eodag_api,
484
- catalogs=list(catalogs) if catalogs else None,
485
474
  ).data
486
475
 
487
- hashed_catalogs = hash(":".join(catalogs) if catalogs else None)
488
- return await cached(
489
- _fetch, f"{CACHE_KEY_COLLECTION}:{provider}:{hashed_catalogs}", request
490
- )
476
+ return await cached(_fetch, f"{CACHE_KEY_COLLECTION}:{provider}", request)
491
477
 
492
478
 
493
479
  def time_interval_overlap(eodag_args: EODAGSearch, catalog: StacCatalog) -> bool:
@@ -495,7 +481,10 @@ def time_interval_overlap(eodag_args: EODAGSearch, catalog: StacCatalog) -> bool
495
481
  # check if time filtering appears both in search arguments and catalog
496
482
  # (for catalogs built by date: i.e. `year/2020/month/05`)
497
483
  if not set(["start", "end"]) <= set(eodag_args.model_dump().keys()) or not set(
498
- ["start", "end"]
484
+ [
485
+ "start",
486
+ "end",
487
+ ]
499
488
  ) <= set(catalog.search_args.keys()):
500
489
  return True
501
490
 
@@ -534,10 +523,10 @@ 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
- :returns: conformance dictionnary
529
+ :returns: conformance dictionary
541
530
  """
542
531
  return stac_config["conformance"]
543
532
 
@@ -551,11 +540,11 @@ 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
558
- :returns: Catalog dictionnary
547
+ :returns: Catalog dictionary
559
548
  """
560
549
 
561
550
  def apply_method(_: str, x: str) -> str:
@@ -582,50 +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
-
599
- # productType and id are already default in stac collection and id
600
- python_queryables.pop("productType", None)
601
- python_queryables.pop("id", None)
602
587
 
603
- 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
  )
606
- for param, queryable in python_queryables.items():
607
- stac_param = EODAGSearch.to_stac(param)
608
- # only keep "datetime" queryable for dates
609
- if stac_param in stac_queryables or stac_param in (
610
- "start_datetime",
611
- "end_datetime",
612
- ):
613
- continue
614
591
 
615
- stac_queryables[
616
- stac_param
617
- ] = StacQueryableProperty.from_python_field_definition(
618
- stac_param, queryable
619
- )
592
+ properties: dict[str, Any] = python_queryables_json["properties"]
593
+ required: list[str] = python_queryables_json.get("required") or []
620
594
 
621
- if params.collection:
622
- stac_queryables.pop("collection")
595
+ # productType is either simply removed or replaced by collection later.
596
+ if "productType" in properties:
597
+ properties.pop("productType")
598
+ if "productType" in required:
599
+ required.remove("productType")
600
+
601
+ stac_properties: dict[str, Any] = {}
602
+
603
+ # get stac default properties to set prefixes
604
+ stac_item_properties = list(stac_config["item"]["properties"].values())
605
+ stac_item_properties.extend(stac_config["metadata_ignore"])
606
+ for param, queryable in properties.items():
607
+ # convert key to STAC format
608
+ if param in OSEO_METADATA_MAPPING.keys() and not any(
609
+ param in str(prop) for prop in stac_item_properties
610
+ ):
611
+ param = f"oseo:{param}"
612
+ stac_param = EODAGSearch.to_stac(param, stac_item_properties, provider)
613
+
614
+ queryable["title"] = stac_param.split(":")[-1]
615
+
616
+ # remove null default values
617
+ if not queryable.get("default"):
618
+ queryable.pop("default", None)
619
+
620
+ stac_properties[stac_param] = queryable
621
+ required = list(map(lambda x: x.replace(param, stac_param), required))
622
+
623
+ # due to certain metadata mappings we might only get end_datetime but we can
624
+ # assume that start_datetime is also available
625
+ if (
626
+ "end_datetime" in stac_properties
627
+ and "start_datetime" not in stac_properties
628
+ ):
629
+ stac_properties["start_datetime"] = deepcopy(
630
+ stac_properties["end_datetime"]
631
+ )
632
+ stac_properties["start_datetime"]["title"] = "start_datetime"
633
+ # if we can search by start_datetime we can search by datetime
634
+ if "start_datetime" in stac_properties:
635
+ stac_properties["datetime"] = StacQueryables.possible_properties[
636
+ "datetime"
637
+ ].model_dump()
638
+
639
+ # format spatial extend properties to STAC format.
640
+ if "geometry" in stac_properties:
641
+ stac_properties["bbox"] = StacQueryables.possible_properties[
642
+ "bbox"
643
+ ].model_dump()
644
+ stac_properties["geometry"] = StacQueryables.possible_properties[
645
+ "geometry"
646
+ ].model_dump()
647
+
648
+ if not params.collection:
649
+ stac_properties["collection"] = StacQueryables.default_properties[
650
+ "collection"
651
+ ].model_dump()
652
+
653
+ additional_properties = python_queryables.additional_properties
654
+ description = "Queryable names for the EODAG STAC API Item Search filter. "
655
+ description += python_queryables.additional_information
623
656
 
624
657
  return StacQueryables(
625
658
  q_id=request.state.url,
626
- additional_properties=bool(not params.collection),
627
- properties=stac_queryables,
628
- ).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)
629
664
 
630
665
  hashed_queryables = hash(params.model_dump_json())
631
666
  return await cached(
@@ -652,7 +687,7 @@ def crunch_products(
652
687
  f'Unknown crunch name. Use one of: {", ".join(crunchers.keys())}'
653
688
  )
654
689
 
655
- cruncher_config: Dict[str, Any] = {}
690
+ cruncher_config: dict[str, Any] = {}
656
691
  for config_param in cruncher.config_params:
657
692
  config_param_value = kwargs.get(config_param)
658
693
  if not config_param_value:
@@ -685,17 +720,22 @@ def eodag_api_init() -> None:
685
720
  ext_col = StacCollection.ext_stac_collections.get(key)
686
721
  if not ext_col:
687
722
  continue
688
- platform: Union[str, List[str]] = ext_col.get("summaries", {}).get(
723
+ platform: Union[str, list[str]] = ext_col.get("summaries", {}).get(
689
724
  "platform"
690
725
  )
691
- constellation: Union[str, List[str]] = ext_col.get("summaries", {}).get(
726
+ constellation: Union[str, list[str]] = ext_col.get("summaries", {}).get(
692
727
  "constellation"
693
728
  )
729
+ processing_level: Union[str, list[str]] = ext_col.get("summaries", {}).get(
730
+ "processing:level"
731
+ )
694
732
  # Check if platform or constellation are lists and join them into a string if they are
695
733
  if isinstance(platform, list):
696
734
  platform = ",".join(platform)
697
735
  if isinstance(constellation, list):
698
736
  constellation = ",".join(constellation)
737
+ if isinstance(processing_level, list):
738
+ processing_level = ",".join(processing_level)
699
739
 
700
740
  update_fields = {
701
741
  "title": ext_col.get("title"),
@@ -706,7 +746,7 @@ def eodag_api_init() -> None:
706
746
  ),
707
747
  "platform": constellation,
708
748
  "platformSerialIdentifier": platform,
709
- "processingLevel": ext_col.get("summaries", {}).get("processing:level"),
749
+ "processingLevel": processing_level,
710
750
  "license": ext_col["license"],
711
751
  "missionStartDate": ext_col["extent"]["temporal"]["interval"][0][0],
712
752
  "missionEndDate": ext_col["extent"]["temporal"]["interval"][-1][1],