eodag 3.0.1__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.
- eodag/api/core.py +174 -138
- eodag/api/product/_assets.py +44 -15
- eodag/api/product/_product.py +58 -47
- eodag/api/product/drivers/__init__.py +81 -4
- eodag/api/product/drivers/base.py +65 -4
- eodag/api/product/drivers/generic.py +65 -0
- eodag/api/product/drivers/sentinel1.py +97 -0
- eodag/api/product/drivers/sentinel2.py +95 -0
- eodag/api/product/metadata_mapping.py +117 -90
- eodag/api/search_result.py +13 -23
- eodag/cli.py +26 -5
- eodag/config.py +86 -92
- eodag/plugins/apis/base.py +1 -1
- eodag/plugins/apis/ecmwf.py +42 -22
- eodag/plugins/apis/usgs.py +17 -16
- eodag/plugins/authentication/aws_auth.py +16 -13
- eodag/plugins/authentication/base.py +5 -3
- eodag/plugins/authentication/header.py +3 -3
- eodag/plugins/authentication/keycloak.py +4 -4
- eodag/plugins/authentication/oauth.py +7 -3
- eodag/plugins/authentication/openid_connect.py +22 -16
- eodag/plugins/authentication/sas_auth.py +4 -4
- eodag/plugins/authentication/token.py +41 -10
- eodag/plugins/authentication/token_exchange.py +1 -1
- eodag/plugins/base.py +4 -4
- eodag/plugins/crunch/base.py +4 -4
- eodag/plugins/crunch/filter_date.py +4 -4
- eodag/plugins/crunch/filter_latest_intersect.py +6 -6
- eodag/plugins/crunch/filter_latest_tpl_name.py +7 -7
- eodag/plugins/crunch/filter_overlap.py +4 -4
- eodag/plugins/crunch/filter_property.py +6 -7
- eodag/plugins/download/aws.py +146 -87
- eodag/plugins/download/base.py +38 -56
- eodag/plugins/download/creodias_s3.py +29 -0
- eodag/plugins/download/http.py +173 -183
- eodag/plugins/download/s3rest.py +10 -11
- eodag/plugins/manager.py +10 -20
- eodag/plugins/search/__init__.py +6 -5
- eodag/plugins/search/base.py +90 -46
- eodag/plugins/search/build_search_result.py +1048 -361
- eodag/plugins/search/cop_marine.py +22 -12
- eodag/plugins/search/creodias_s3.py +9 -73
- eodag/plugins/search/csw.py +11 -11
- eodag/plugins/search/data_request_search.py +19 -18
- eodag/plugins/search/qssearch.py +99 -258
- eodag/plugins/search/stac_list_assets.py +85 -0
- eodag/plugins/search/static_stac_search.py +4 -4
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +1134 -325
- eodag/resources/providers.yml +906 -2006
- eodag/resources/stac_api.yml +2 -2
- eodag/resources/user_conf_template.yml +10 -9
- eodag/rest/cache.py +2 -2
- eodag/rest/config.py +3 -3
- eodag/rest/core.py +112 -82
- eodag/rest/errors.py +5 -5
- eodag/rest/server.py +33 -14
- eodag/rest/stac.py +41 -38
- eodag/rest/types/collections_search.py +3 -3
- eodag/rest/types/eodag_search.py +29 -23
- eodag/rest/types/queryables.py +42 -31
- eodag/rest/types/stac_search.py +15 -25
- eodag/rest/utils/__init__.py +14 -21
- eodag/rest/utils/cql_evaluate.py +6 -6
- eodag/rest/utils/rfc3339.py +2 -2
- eodag/types/__init__.py +141 -32
- eodag/types/bbox.py +2 -2
- eodag/types/download_args.py +3 -3
- eodag/types/queryables.py +183 -72
- eodag/types/search_args.py +4 -4
- eodag/types/whoosh.py +127 -3
- eodag/utils/__init__.py +153 -51
- eodag/utils/exceptions.py +28 -21
- eodag/utils/import_system.py +2 -2
- eodag/utils/repr.py +65 -6
- eodag/utils/requests.py +13 -13
- eodag/utils/rest.py +2 -2
- eodag/utils/s3.py +231 -0
- eodag/utils/stac_reader.py +10 -10
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/METADATA +77 -76
- eodag-3.1.0.dist-info/RECORD +113 -0
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +4 -2
- eodag/utils/constraints.py +0 -244
- eodag-3.0.1.dist-info/RECORD +0 -109
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/LICENSE +0 -0
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/top_level.txt +0 -0
eodag/resources/stac_api.yml
CHANGED
|
@@ -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:
|
|
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
|
-
`
|
|
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,15 +157,18 @@ geodes:
|
|
|
157
157
|
download:
|
|
158
158
|
extract:
|
|
159
159
|
output_dir:
|
|
160
|
-
|
|
160
|
+
geodes_s3:
|
|
161
161
|
priority: # Lower value means lower priority (Default: 0)
|
|
162
162
|
search: # Search parameters configuration
|
|
163
163
|
auth:
|
|
164
164
|
credentials:
|
|
165
|
-
|
|
165
|
+
aws_access_key_id:
|
|
166
|
+
aws_secret_access_key:
|
|
167
|
+
aws_session_token:
|
|
166
168
|
download:
|
|
169
|
+
extract:
|
|
167
170
|
output_dir:
|
|
168
|
-
|
|
171
|
+
hydroweb_next:
|
|
169
172
|
priority: # Lower value means lower priority (Default: 0)
|
|
170
173
|
search: # Search parameters configuration
|
|
171
174
|
auth:
|
|
@@ -173,16 +176,14 @@ meteoblue:
|
|
|
173
176
|
apikey:
|
|
174
177
|
download:
|
|
175
178
|
output_dir:
|
|
176
|
-
|
|
179
|
+
meteoblue:
|
|
177
180
|
priority: # Lower value means lower priority (Default: 0)
|
|
178
181
|
search: # Search parameters configuration
|
|
179
|
-
download:
|
|
180
|
-
extract:
|
|
181
|
-
output_dir:
|
|
182
182
|
auth:
|
|
183
183
|
credentials:
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
apikey:
|
|
185
|
+
download:
|
|
186
|
+
output_dir:
|
|
186
187
|
peps:
|
|
187
188
|
priority: # Lower value means lower priority (Default: 1)
|
|
188
189
|
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,
|
|
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:
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
-
) ->
|
|
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
|
-
|
|
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
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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,
|
|
328
|
-
query_args:
|
|
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, "
|
|
330
|
+
and hasattr(product.downloader, "_order")
|
|
346
331
|
):
|
|
347
332
|
# first order
|
|
348
333
|
logger.debug("Order product")
|
|
349
|
-
order_status_dict = product.downloader.
|
|
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, "
|
|
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.
|
|
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() ->
|
|
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
|
-
) ->
|
|
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() ->
|
|
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
|
-
{
|
|
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
|
-
) ->
|
|
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() ->
|
|
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
|
-
) ->
|
|
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() ->
|
|
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
|
-
[
|
|
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() ->
|
|
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) ->
|
|
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
|
-
) ->
|
|
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() ->
|
|
581
|
+
async def _fetch() -> dict[str, Any]:
|
|
593
582
|
python_queryables = eodag_api.list_queryables(
|
|
594
|
-
provider=provider,
|
|
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
|
-
|
|
600
|
-
|
|
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(
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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
|
-
|
|
632
|
-
|
|
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=
|
|
637
|
-
properties=
|
|
638
|
-
|
|
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:
|
|
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,
|
|
723
|
+
platform: Union[str, list[str]] = ext_col.get("summaries", {}).get(
|
|
699
724
|
"platform"
|
|
700
725
|
)
|
|
701
|
-
constellation: Union[str,
|
|
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":
|
|
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
|
|
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:
|
|
59
|
+
def __init__(self, errors: list[tuple[str, Exception]]) -> None:
|
|
60
60
|
self._errors = errors
|
|
61
61
|
|
|
62
62
|
@property
|
|
63
|
-
def errors(self) ->
|
|
63
|
+
def errors(self) -> list[dict[str, Union[str, int]]]:
|
|
64
64
|
"""return errors as a list of dict"""
|
|
65
|
-
error_list:
|
|
65
|
+
error_list: list[dict[str, Union[str, int]]] = []
|
|
66
66
|
for name, exception in self._errors:
|
|
67
67
|
|
|
68
|
-
error_dict:
|
|
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
|
|
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) ->
|
|
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
|
-
|
|
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(
|
|
384
|
-
|
|
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)
|