eodag 3.9.1__py3-none-any.whl → 4.0.0a1__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 +378 -419
- eodag/api/product/__init__.py +3 -3
- eodag/api/product/_product.py +68 -40
- eodag/api/product/drivers/__init__.py +3 -5
- eodag/api/product/drivers/base.py +1 -18
- eodag/api/product/metadata_mapping.py +151 -215
- eodag/api/search_result.py +13 -7
- eodag/cli.py +72 -395
- eodag/config.py +46 -50
- eodag/plugins/apis/base.py +2 -2
- eodag/plugins/apis/ecmwf.py +20 -21
- eodag/plugins/apis/usgs.py +37 -33
- eodag/plugins/authentication/aws_auth.py +36 -1
- eodag/plugins/authentication/base.py +18 -3
- eodag/plugins/authentication/sas_auth.py +15 -0
- eodag/plugins/crunch/filter_date.py +3 -3
- eodag/plugins/crunch/filter_latest_intersect.py +2 -2
- eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
- eodag/plugins/download/aws.py +45 -41
- eodag/plugins/download/base.py +13 -14
- eodag/plugins/download/http.py +65 -65
- eodag/plugins/manager.py +28 -29
- eodag/plugins/search/__init__.py +3 -4
- eodag/plugins/search/base.py +128 -77
- eodag/plugins/search/build_search_result.py +105 -107
- eodag/plugins/search/cop_marine.py +44 -47
- eodag/plugins/search/csw.py +33 -33
- eodag/plugins/search/qssearch.py +335 -354
- eodag/plugins/search/stac_list_assets.py +1 -1
- eodag/plugins/search/static_stac_search.py +31 -31
- eodag/resources/{product_types.yml → collections.yml} +2353 -2429
- eodag/resources/ext_collections.json +1 -0
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/providers.yml +2432 -2714
- eodag/resources/stac_provider.yml +46 -90
- eodag/types/queryables.py +55 -91
- eodag/types/search_args.py +1 -1
- eodag/utils/__init__.py +94 -21
- eodag/utils/exceptions.py +6 -6
- eodag/utils/free_text_search.py +3 -3
- {eodag-3.9.1.dist-info → eodag-4.0.0a1.dist-info}/METADATA +11 -88
- eodag-4.0.0a1.dist-info/RECORD +92 -0
- {eodag-3.9.1.dist-info → eodag-4.0.0a1.dist-info}/entry_points.txt +0 -4
- eodag/plugins/authentication/oauth.py +0 -60
- eodag/plugins/download/creodias_s3.py +0 -64
- eodag/plugins/download/s3rest.py +0 -351
- eodag/plugins/search/data_request_search.py +0 -565
- eodag/resources/stac.yml +0 -294
- eodag/resources/stac_api.yml +0 -2105
- eodag/rest/__init__.py +0 -24
- eodag/rest/cache.py +0 -70
- eodag/rest/config.py +0 -67
- eodag/rest/constants.py +0 -26
- eodag/rest/core.py +0 -764
- eodag/rest/errors.py +0 -210
- eodag/rest/server.py +0 -604
- eodag/rest/server.wsgi +0 -6
- eodag/rest/stac.py +0 -1032
- eodag/rest/templates/README +0 -1
- eodag/rest/types/__init__.py +0 -18
- eodag/rest/types/collections_search.py +0 -44
- eodag/rest/types/eodag_search.py +0 -386
- eodag/rest/types/queryables.py +0 -174
- eodag/rest/types/stac_search.py +0 -272
- eodag/rest/utils/__init__.py +0 -207
- eodag/rest/utils/cql_evaluate.py +0 -119
- eodag/rest/utils/rfc3339.py +0 -64
- eodag-3.9.1.dist-info/RECORD +0 -115
- {eodag-3.9.1.dist-info → eodag-4.0.0a1.dist-info}/WHEEL +0 -0
- {eodag-3.9.1.dist-info → eodag-4.0.0a1.dist-info}/licenses/LICENSE +0 -0
- {eodag-3.9.1.dist-info → eodag-4.0.0a1.dist-info}/top_level.txt +0 -0
eodag/rest/core.py
DELETED
|
@@ -1,764 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
# Copyright 2023, CS GROUP - France, https://www.csgroup.eu/
|
|
3
|
-
#
|
|
4
|
-
# This file is part of EODAG project
|
|
5
|
-
# https://www.github.com/CS-SI/EODAG
|
|
6
|
-
#
|
|
7
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
-
# you may not use this file except in compliance with the License.
|
|
9
|
-
# You may obtain a copy of the License at
|
|
10
|
-
#
|
|
11
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
-
#
|
|
13
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
-
# See the License for the specific language governing permissions and
|
|
17
|
-
# limitations under the License.
|
|
18
|
-
from __future__ import annotations
|
|
19
|
-
|
|
20
|
-
import datetime
|
|
21
|
-
import logging
|
|
22
|
-
import os
|
|
23
|
-
import re
|
|
24
|
-
import warnings
|
|
25
|
-
from typing import TYPE_CHECKING, cast
|
|
26
|
-
from unittest.mock import Mock
|
|
27
|
-
from urllib.parse import urlencode
|
|
28
|
-
|
|
29
|
-
import dateutil
|
|
30
|
-
from cachetools.func import lru_cache
|
|
31
|
-
from fastapi.responses import ORJSONResponse, StreamingResponse
|
|
32
|
-
from pydantic import ValidationError as pydanticValidationError
|
|
33
|
-
from requests.models import Response as RequestsResponse
|
|
34
|
-
|
|
35
|
-
import eodag
|
|
36
|
-
from eodag import EOProduct
|
|
37
|
-
from eodag.api.product.metadata_mapping import (
|
|
38
|
-
NOT_AVAILABLE,
|
|
39
|
-
OFFLINE_STATUS,
|
|
40
|
-
ONLINE_STATUS,
|
|
41
|
-
OSEO_METADATA_MAPPING,
|
|
42
|
-
STAGING_STATUS,
|
|
43
|
-
)
|
|
44
|
-
from eodag.api.search_result import SearchResult
|
|
45
|
-
from eodag.config import load_stac_config
|
|
46
|
-
from eodag.plugins.crunch.filter_latest_intersect import FilterLatestIntersect
|
|
47
|
-
from eodag.plugins.crunch.filter_latest_tpl_name import FilterLatestByName
|
|
48
|
-
from eodag.plugins.crunch.filter_overlap import FilterOverlap
|
|
49
|
-
from eodag.rest.cache import cached
|
|
50
|
-
from eodag.rest.constants import (
|
|
51
|
-
CACHE_KEY_COLLECTION,
|
|
52
|
-
CACHE_KEY_COLLECTIONS,
|
|
53
|
-
CACHE_KEY_QUERYABLES,
|
|
54
|
-
)
|
|
55
|
-
from eodag.rest.errors import ResponseSearchError
|
|
56
|
-
from eodag.rest.stac import StacCatalog, StacCollection, StacCommon, StacItem
|
|
57
|
-
from eodag.rest.types.eodag_search import EODAGSearch
|
|
58
|
-
from eodag.rest.types.queryables import QueryablesGetParams, StacQueryables
|
|
59
|
-
from eodag.rest.types.stac_search import SearchPostRequest
|
|
60
|
-
from eodag.rest.utils import (
|
|
61
|
-
Cruncher,
|
|
62
|
-
file_to_stream,
|
|
63
|
-
format_pydantic_error,
|
|
64
|
-
get_next_link,
|
|
65
|
-
)
|
|
66
|
-
from eodag.rest.utils.rfc3339 import rfc3339_str_to_datetime
|
|
67
|
-
from eodag.utils import (
|
|
68
|
-
_deprecated,
|
|
69
|
-
deepcopy,
|
|
70
|
-
dict_items_recursive_apply,
|
|
71
|
-
format_dict_items,
|
|
72
|
-
)
|
|
73
|
-
from eodag.utils.exceptions import (
|
|
74
|
-
MisconfiguredError,
|
|
75
|
-
NotAvailableError,
|
|
76
|
-
ValidationError,
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
if TYPE_CHECKING:
|
|
80
|
-
from typing import Any, Optional, Union
|
|
81
|
-
|
|
82
|
-
from fastapi import Request
|
|
83
|
-
from requests.auth import AuthBase
|
|
84
|
-
from starlette.responses import Response
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
warnings.warn(
|
|
88
|
-
"The module `eodag.rest.core` is deprecated since v3.9.0 and will be removed in a future version. "
|
|
89
|
-
"The STAC server has moved to https://github.com/CS-SI/stac-fastapi-eodag",
|
|
90
|
-
category=DeprecationWarning,
|
|
91
|
-
stacklevel=2,
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
eodag_api = eodag.EODataAccessGateway()
|
|
95
|
-
|
|
96
|
-
logger = logging.getLogger("eodag.rest.core")
|
|
97
|
-
|
|
98
|
-
stac_config = load_stac_config()
|
|
99
|
-
|
|
100
|
-
crunchers = {
|
|
101
|
-
"filterLatestIntersect": Cruncher(FilterLatestIntersect, []),
|
|
102
|
-
"filterLatestByName": Cruncher(FilterLatestByName, ["name_pattern"]),
|
|
103
|
-
"filterOverlap": Cruncher(FilterOverlap, ["minimum_overlap"]),
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
@_deprecated(reason="No more needed with STAC API + Swagger", version="2.6.1")
|
|
108
|
-
def get_home_page_content(base_url: str, ipp: Optional[int] = None) -> str:
|
|
109
|
-
"""Compute eodag service home page content
|
|
110
|
-
|
|
111
|
-
:param base_url: The service root URL
|
|
112
|
-
:param ipp: (optional) Items per page number
|
|
113
|
-
"""
|
|
114
|
-
base_url = base_url.rstrip("/") + "/"
|
|
115
|
-
content = f"""<h1>EODAG Server</h1><br />
|
|
116
|
-
<a href='{base_url}'>root</a><br />
|
|
117
|
-
<a href='{base_url}service-doc'>service-doc</a><br />
|
|
118
|
-
"""
|
|
119
|
-
return content
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
@_deprecated(
|
|
123
|
-
reason="Function internally used by get_home_page_content, also deprecated",
|
|
124
|
-
version="2.6.1",
|
|
125
|
-
)
|
|
126
|
-
def format_product_types(product_types: list[dict[str, Any]]) -> str:
|
|
127
|
-
"""Format product_types
|
|
128
|
-
|
|
129
|
-
:param product_types: A list of EODAG product types as returned by the core api
|
|
130
|
-
"""
|
|
131
|
-
result: list[str] = []
|
|
132
|
-
for pt in product_types:
|
|
133
|
-
result.append(f"* *__{pt['ID']}__*: {pt['abstract']}")
|
|
134
|
-
return "\n".join(sorted(result))
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def search_stac_items(
|
|
138
|
-
request: Request,
|
|
139
|
-
search_request: SearchPostRequest,
|
|
140
|
-
) -> dict[str, Any]:
|
|
141
|
-
"""
|
|
142
|
-
Search and retrieve STAC items based on the given search request.
|
|
143
|
-
|
|
144
|
-
This function takes a search request, performs a search using EODAG API, and returns a
|
|
145
|
-
dictionary of STAC items.
|
|
146
|
-
|
|
147
|
-
:param request: The incoming HTTP request with state information.
|
|
148
|
-
:param search_request: The search criteria for STAC items
|
|
149
|
-
:returns: A dictionary containing the STAC items and related metadata.
|
|
150
|
-
|
|
151
|
-
The function handles the conversion of search criteria into STAC and EODAG compatible formats, validates the input
|
|
152
|
-
using pydantic, and constructs the appropriate URLs for querying the STAC API. It also manages pagination and the
|
|
153
|
-
construction of the 'next' link for the response.
|
|
154
|
-
|
|
155
|
-
If specific item IDs are provided, it retrieves the corresponding products. Otherwise, it performs a search based on
|
|
156
|
-
the provided criteria and time interval overlap checks.
|
|
157
|
-
|
|
158
|
-
The results are then formatted into STAC items and returned as part of the response dictionary, which includes the
|
|
159
|
-
items themselves, total count, and the next link if applicable.
|
|
160
|
-
"""
|
|
161
|
-
|
|
162
|
-
stac_args = search_request.model_dump(exclude_none=True)
|
|
163
|
-
if search_request.start_date:
|
|
164
|
-
stac_args["start_datetime"] = search_request.start_date
|
|
165
|
-
if search_request.end_date:
|
|
166
|
-
stac_args["end_datetime"] = search_request.end_date
|
|
167
|
-
if search_request.spatial_filter:
|
|
168
|
-
stac_args["geometry"] = search_request.spatial_filter
|
|
169
|
-
try:
|
|
170
|
-
eodag_args = EODAGSearch.model_validate(stac_args)
|
|
171
|
-
except pydanticValidationError as e:
|
|
172
|
-
raise ValidationError(format_pydantic_error(e)) from e
|
|
173
|
-
|
|
174
|
-
catalog_url = re.sub("/items.*", "", request.state.url)
|
|
175
|
-
catalog = StacCatalog(
|
|
176
|
-
url=catalog_url.replace("/search", f"/collections/{eodag_args.productType}"),
|
|
177
|
-
stac_config=stac_config,
|
|
178
|
-
root=request.state.url_root,
|
|
179
|
-
provider=eodag_args.provider,
|
|
180
|
-
eodag_api=eodag_api,
|
|
181
|
-
collection=eodag_args.productType, # type: ignore
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
# get products by ids
|
|
185
|
-
if eodag_args.ids:
|
|
186
|
-
results = SearchResult([])
|
|
187
|
-
for item_id in eodag_args.ids:
|
|
188
|
-
results.extend(
|
|
189
|
-
eodag_api.search(
|
|
190
|
-
id=item_id,
|
|
191
|
-
productType=eodag_args.productType,
|
|
192
|
-
provider=eodag_args.provider,
|
|
193
|
-
)
|
|
194
|
-
)
|
|
195
|
-
results.number_matched = len(results)
|
|
196
|
-
total = len(results)
|
|
197
|
-
|
|
198
|
-
else:
|
|
199
|
-
criteria = eodag_args.model_dump(exclude_none=True)
|
|
200
|
-
# remove provider prefixes
|
|
201
|
-
# quickfix for ecmwf fake extension to not impact items creation
|
|
202
|
-
stac_extensions = list(stac_config["extensions"].keys()) + ["ecmwf"]
|
|
203
|
-
for key in list(criteria):
|
|
204
|
-
if ":" in key and key.split(":")[0] not in stac_extensions:
|
|
205
|
-
new_key = key.split(":")[1]
|
|
206
|
-
criteria[new_key] = criteria.pop(key)
|
|
207
|
-
|
|
208
|
-
results = eodag_api.search(count=True, **criteria)
|
|
209
|
-
total = results.number_matched or 0
|
|
210
|
-
|
|
211
|
-
if len(results) == 0 and results.errors:
|
|
212
|
-
raise ResponseSearchError(results.errors)
|
|
213
|
-
|
|
214
|
-
if search_request.crunch:
|
|
215
|
-
results = crunch_products(results, search_request.crunch, **criteria)
|
|
216
|
-
|
|
217
|
-
for record in results:
|
|
218
|
-
record.product_type = eodag_api.get_alias_from_product_type(record.product_type)
|
|
219
|
-
|
|
220
|
-
items = StacItem(
|
|
221
|
-
url=request.state.url,
|
|
222
|
-
stac_config=stac_config,
|
|
223
|
-
provider=eodag_args.provider,
|
|
224
|
-
eodag_api=eodag_api,
|
|
225
|
-
root=request.state.url_root,
|
|
226
|
-
).get_stac_items(
|
|
227
|
-
search_results=results,
|
|
228
|
-
total=total,
|
|
229
|
-
next_link=get_next_link(
|
|
230
|
-
request, search_request, total, eodag_args.items_per_page
|
|
231
|
-
),
|
|
232
|
-
catalog={
|
|
233
|
-
**catalog.data,
|
|
234
|
-
**{"url": catalog.url, "root": catalog.root},
|
|
235
|
-
},
|
|
236
|
-
)
|
|
237
|
-
return items
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def download_stac_item(
|
|
241
|
-
request: Request,
|
|
242
|
-
collection_id: str,
|
|
243
|
-
item_id: str,
|
|
244
|
-
provider: Optional[str] = None,
|
|
245
|
-
asset: Optional[str] = None,
|
|
246
|
-
**kwargs: Any,
|
|
247
|
-
) -> Response:
|
|
248
|
-
"""Download item
|
|
249
|
-
|
|
250
|
-
:param collection_id: id of the product type
|
|
251
|
-
:param item_id: Product ID
|
|
252
|
-
:param provider: (optional) Chosen provider
|
|
253
|
-
:param kwargs: additional download parameters
|
|
254
|
-
:returns: a stream of the downloaded data (zip file)
|
|
255
|
-
"""
|
|
256
|
-
product_type = collection_id
|
|
257
|
-
|
|
258
|
-
search_results = eodag_api.search(
|
|
259
|
-
id=item_id, productType=product_type, provider=provider, **kwargs
|
|
260
|
-
)
|
|
261
|
-
if len(search_results) > 0:
|
|
262
|
-
product = cast(EOProduct, search_results[0])
|
|
263
|
-
|
|
264
|
-
else:
|
|
265
|
-
raise NotAvailableError(
|
|
266
|
-
f"Could not find {item_id} item in {product_type} collection"
|
|
267
|
-
+ (f" for provider {provider}" if provider else "")
|
|
268
|
-
)
|
|
269
|
-
auth = product.downloader_auth.authenticate() if product.downloader_auth else None
|
|
270
|
-
|
|
271
|
-
try:
|
|
272
|
-
if product.properties.get("orderLink"):
|
|
273
|
-
_order_and_update(product, auth, kwargs)
|
|
274
|
-
|
|
275
|
-
download_stream = product.downloader._stream_download_dict(
|
|
276
|
-
product, auth=auth, asset=asset, wait=-1, timeout=-1
|
|
277
|
-
)
|
|
278
|
-
except NotImplementedError:
|
|
279
|
-
logger.warning(
|
|
280
|
-
"Download streaming not supported for %s: downloading locally then delete",
|
|
281
|
-
product.downloader,
|
|
282
|
-
)
|
|
283
|
-
download_stream = file_to_stream(
|
|
284
|
-
eodag_api.download(product, extract=False, asset=asset)
|
|
285
|
-
)
|
|
286
|
-
except NotAvailableError:
|
|
287
|
-
if product.properties.get("storageStatus") != ONLINE_STATUS:
|
|
288
|
-
kwargs["orderId"] = kwargs.get("orderId") or product.properties.get(
|
|
289
|
-
"orderId"
|
|
290
|
-
)
|
|
291
|
-
kwargs["provider"] = provider
|
|
292
|
-
qs = urlencode(kwargs, doseq=True)
|
|
293
|
-
download_link = f"{request.state.url}?{qs}"
|
|
294
|
-
return ORJSONResponse(
|
|
295
|
-
status_code=202,
|
|
296
|
-
headers={"Location": download_link},
|
|
297
|
-
content={
|
|
298
|
-
"description": "Product is not available yet, please try again using given updated location",
|
|
299
|
-
"status": product.properties.get("orderStatus"),
|
|
300
|
-
"location": download_link,
|
|
301
|
-
},
|
|
302
|
-
)
|
|
303
|
-
else:
|
|
304
|
-
raise
|
|
305
|
-
|
|
306
|
-
return StreamingResponse(
|
|
307
|
-
content=download_stream.content,
|
|
308
|
-
headers=download_stream.headers,
|
|
309
|
-
media_type=download_stream.media_type,
|
|
310
|
-
)
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
def _order_and_update(
|
|
314
|
-
product: EOProduct,
|
|
315
|
-
auth: Union[AuthBase, dict[str, str], None],
|
|
316
|
-
query_args: dict[str, Any],
|
|
317
|
-
) -> None:
|
|
318
|
-
"""Order product if needed and update given kwargs with order-status-dict"""
|
|
319
|
-
if product.properties.get("storageStatus") != ONLINE_STATUS and hasattr(
|
|
320
|
-
product.downloader, "order_response_process"
|
|
321
|
-
):
|
|
322
|
-
# update product (including orderStatusLink) if product was previously ordered
|
|
323
|
-
logger.debug("Use given download query arguments to parse order link")
|
|
324
|
-
response = Mock(spec=RequestsResponse)
|
|
325
|
-
response.status_code = 200
|
|
326
|
-
response.json.return_value = query_args
|
|
327
|
-
response.headers = {}
|
|
328
|
-
product.downloader.order_response_process(response, product)
|
|
329
|
-
|
|
330
|
-
if (
|
|
331
|
-
product.properties.get("storageStatus") != ONLINE_STATUS
|
|
332
|
-
and NOT_AVAILABLE in product.properties.get("orderStatusLink", "")
|
|
333
|
-
and hasattr(product.downloader, "_order")
|
|
334
|
-
):
|
|
335
|
-
# first order
|
|
336
|
-
logger.debug("Order product")
|
|
337
|
-
order_status_dict = product.downloader._order(product=product, auth=auth)
|
|
338
|
-
query_args.update(order_status_dict or {})
|
|
339
|
-
|
|
340
|
-
if (
|
|
341
|
-
product.properties.get("storageStatus") == OFFLINE_STATUS
|
|
342
|
-
and product.properties.get("orderStatusLink")
|
|
343
|
-
and NOT_AVAILABLE not in product.properties.get("orderStatusLink", "")
|
|
344
|
-
):
|
|
345
|
-
product.properties["storageStatus"] = STAGING_STATUS
|
|
346
|
-
|
|
347
|
-
if product.properties.get("storageStatus") == STAGING_STATUS and hasattr(
|
|
348
|
-
product.downloader, "_order_status"
|
|
349
|
-
):
|
|
350
|
-
# check order status if needed
|
|
351
|
-
logger.debug("Checking product order status")
|
|
352
|
-
product.downloader._order_status(product=product, auth=auth)
|
|
353
|
-
|
|
354
|
-
if product.properties.get("storageStatus") != ONLINE_STATUS:
|
|
355
|
-
raise NotAvailableError("Product is not available yet")
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
@lru_cache(maxsize=1)
|
|
359
|
-
def get_detailled_collections_list() -> list[dict[str, Any]]:
|
|
360
|
-
"""Returns detailled collections / product_types list as a list of
|
|
361
|
-
config dicts
|
|
362
|
-
|
|
363
|
-
:returns: List of config dicts
|
|
364
|
-
"""
|
|
365
|
-
return eodag_api.list_product_types(fetch_providers=False)
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
async def all_collections(
|
|
369
|
-
request: Request,
|
|
370
|
-
provider: Optional[str] = None,
|
|
371
|
-
q: Optional[str] = None,
|
|
372
|
-
platform: Optional[str] = None,
|
|
373
|
-
instrument: Optional[str] = None,
|
|
374
|
-
constellation: Optional[str] = None,
|
|
375
|
-
datetime: Optional[str] = None,
|
|
376
|
-
bbox: Optional[str] = None,
|
|
377
|
-
) -> dict[str, Any]:
|
|
378
|
-
"""Build STAC collections
|
|
379
|
-
|
|
380
|
-
:param url: Requested URL
|
|
381
|
-
:param root: The API root
|
|
382
|
-
:param filters: Search collections filters
|
|
383
|
-
:param provider: (optional) Chosen provider
|
|
384
|
-
:returns: Collections dictionary
|
|
385
|
-
"""
|
|
386
|
-
|
|
387
|
-
async def _fetch() -> dict[str, Any]:
|
|
388
|
-
stac_collection = StacCollection(
|
|
389
|
-
url=request.state.url,
|
|
390
|
-
stac_config=stac_config,
|
|
391
|
-
provider=provider,
|
|
392
|
-
eodag_api=eodag_api,
|
|
393
|
-
root=request.state.url_root,
|
|
394
|
-
)
|
|
395
|
-
collections = deepcopy(stac_config["collections"])
|
|
396
|
-
collections["collections"] = stac_collection.get_collection_list(
|
|
397
|
-
q=q,
|
|
398
|
-
platform=platform,
|
|
399
|
-
instrument=instrument,
|
|
400
|
-
constellation=constellation,
|
|
401
|
-
datetime=datetime,
|
|
402
|
-
bbox=bbox,
|
|
403
|
-
)
|
|
404
|
-
|
|
405
|
-
# # parse f-strings
|
|
406
|
-
format_args = deepcopy(stac_config)
|
|
407
|
-
format_args["collections"].update(
|
|
408
|
-
{
|
|
409
|
-
"url": stac_collection.url,
|
|
410
|
-
"root": stac_collection.root,
|
|
411
|
-
}
|
|
412
|
-
)
|
|
413
|
-
|
|
414
|
-
collections["links"] = [
|
|
415
|
-
format_dict_items(link, **format_args) for link in collections["links"]
|
|
416
|
-
]
|
|
417
|
-
|
|
418
|
-
collections = format_dict_items(collections, **format_args)
|
|
419
|
-
return collections
|
|
420
|
-
|
|
421
|
-
hashed_collections = hash(
|
|
422
|
-
f"{provider}:{q}:{platform}:{instrument}:{constellation}:{datetime}:{bbox}"
|
|
423
|
-
)
|
|
424
|
-
cache_key = f"{CACHE_KEY_COLLECTIONS}:{hashed_collections}"
|
|
425
|
-
return await cached(_fetch, cache_key, request)
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
async def get_collection(
|
|
429
|
-
request: Request, collection_id: str, provider: Optional[str] = None
|
|
430
|
-
) -> dict[str, Any]:
|
|
431
|
-
"""Build STAC collection by id
|
|
432
|
-
|
|
433
|
-
:param url: Requested URL
|
|
434
|
-
:param root: API root
|
|
435
|
-
:param collection_id: Product_type as ID of the collection
|
|
436
|
-
:param provider: (optional) Chosen provider
|
|
437
|
-
:returns: Collection dictionary
|
|
438
|
-
"""
|
|
439
|
-
|
|
440
|
-
async def _fetch() -> dict[str, Any]:
|
|
441
|
-
stac_collection = StacCollection(
|
|
442
|
-
url=request.state.url,
|
|
443
|
-
stac_config=stac_config,
|
|
444
|
-
provider=provider,
|
|
445
|
-
eodag_api=eodag_api,
|
|
446
|
-
root=request.state.url_root,
|
|
447
|
-
)
|
|
448
|
-
collection_list = stac_collection.get_collection_list(collection=collection_id)
|
|
449
|
-
|
|
450
|
-
if not collection_list:
|
|
451
|
-
raise NotAvailableError(f"Collection {collection_id} does not exist.")
|
|
452
|
-
|
|
453
|
-
return collection_list[0]
|
|
454
|
-
|
|
455
|
-
cache_key = f"{CACHE_KEY_COLLECTION}:{provider}:{collection_id}"
|
|
456
|
-
return await cached(_fetch, cache_key, request)
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
async def get_stac_catalogs(
|
|
460
|
-
request: Request,
|
|
461
|
-
url: str,
|
|
462
|
-
provider: Optional[str] = None,
|
|
463
|
-
) -> dict[str, Any]:
|
|
464
|
-
"""Build STAC catalog
|
|
465
|
-
|
|
466
|
-
:param url: Requested URL
|
|
467
|
-
:param root: (optional) API root
|
|
468
|
-
:param provider: (optional) Chosen provider
|
|
469
|
-
:returns: Catalog dictionary
|
|
470
|
-
"""
|
|
471
|
-
|
|
472
|
-
async def _fetch() -> dict[str, Any]:
|
|
473
|
-
return StacCatalog(
|
|
474
|
-
url=url,
|
|
475
|
-
stac_config=stac_config,
|
|
476
|
-
root=request.state.url_root,
|
|
477
|
-
provider=provider,
|
|
478
|
-
eodag_api=eodag_api,
|
|
479
|
-
).data
|
|
480
|
-
|
|
481
|
-
return await cached(_fetch, f"{CACHE_KEY_COLLECTION}:{provider}", request)
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
def time_interval_overlap(eodag_args: EODAGSearch, catalog: StacCatalog) -> bool:
|
|
485
|
-
"""fix search date filter based on catalog date range"""
|
|
486
|
-
# check if time filtering appears both in search arguments and catalog
|
|
487
|
-
# (for catalogs built by date: i.e. `year/2020/month/05`)
|
|
488
|
-
if not set(["start", "end"]) <= set(eodag_args.model_dump().keys()) or not set(
|
|
489
|
-
[
|
|
490
|
-
"start",
|
|
491
|
-
"end",
|
|
492
|
-
]
|
|
493
|
-
) <= set(catalog.search_args.keys()):
|
|
494
|
-
return True
|
|
495
|
-
|
|
496
|
-
search_date_min = cast(
|
|
497
|
-
datetime.datetime,
|
|
498
|
-
(
|
|
499
|
-
dateutil.parser.parse(eodag_args.start) # type: ignore
|
|
500
|
-
if eodag_args.start
|
|
501
|
-
else datetime.datetime.min.replace(tzinfo=datetime.timezone.utc)
|
|
502
|
-
),
|
|
503
|
-
)
|
|
504
|
-
search_date_max = cast(
|
|
505
|
-
datetime.datetime,
|
|
506
|
-
(
|
|
507
|
-
dateutil.parser.parse(eodag_args.end) # type: ignore
|
|
508
|
-
if eodag_args.end
|
|
509
|
-
else datetime.datetime.now(tz=datetime.timezone.utc)
|
|
510
|
-
),
|
|
511
|
-
)
|
|
512
|
-
|
|
513
|
-
catalog_date_min = rfc3339_str_to_datetime(catalog.search_args["start"])
|
|
514
|
-
catalog_date_max = rfc3339_str_to_datetime(catalog.search_args["end"])
|
|
515
|
-
# check if date intervals overlap
|
|
516
|
-
if (search_date_min <= catalog_date_max) and (search_date_max >= catalog_date_min):
|
|
517
|
-
# use intersection
|
|
518
|
-
eodag_args.start = (
|
|
519
|
-
max(search_date_min, catalog_date_min).isoformat().replace("+00:00", "Z")
|
|
520
|
-
)
|
|
521
|
-
eodag_args.end = (
|
|
522
|
-
min(search_date_max, catalog_date_max).isoformat().replace("+00:00", "Z")
|
|
523
|
-
)
|
|
524
|
-
return True
|
|
525
|
-
|
|
526
|
-
logger.warning("Time intervals do not overlap")
|
|
527
|
-
return False
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
@lru_cache(maxsize=1)
|
|
531
|
-
def get_stac_conformance() -> dict[str, str]:
|
|
532
|
-
"""Build STAC conformance
|
|
533
|
-
|
|
534
|
-
:returns: conformance dictionary
|
|
535
|
-
"""
|
|
536
|
-
return stac_config["conformance"]
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
def get_stac_api_version() -> str:
|
|
540
|
-
"""Get STAC API version
|
|
541
|
-
|
|
542
|
-
:returns: STAC API version
|
|
543
|
-
"""
|
|
544
|
-
return stac_config["stac_api_version"]
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
@lru_cache(maxsize=1)
|
|
548
|
-
def get_stac_extension_oseo(url: str) -> dict[str, str]:
|
|
549
|
-
"""Build STAC OGC / OpenSearch Extension for EO
|
|
550
|
-
|
|
551
|
-
:param url: Requested URL
|
|
552
|
-
:returns: Catalog dictionary
|
|
553
|
-
"""
|
|
554
|
-
|
|
555
|
-
def apply_method(_: str, x: str) -> str:
|
|
556
|
-
return str(x).replace("$.product.", "$.")
|
|
557
|
-
|
|
558
|
-
item_mapping = dict_items_recursive_apply(stac_config["item"], apply_method)
|
|
559
|
-
|
|
560
|
-
# all properties as string type by default
|
|
561
|
-
oseo_properties = {
|
|
562
|
-
"oseo:{}".format(k): {
|
|
563
|
-
"type": "string",
|
|
564
|
-
"title": k[0].upper() + re.sub(r"([A-Z][a-z]+)", r" \1", k[1:]),
|
|
565
|
-
}
|
|
566
|
-
for k, v in OSEO_METADATA_MAPPING.items()
|
|
567
|
-
if v not in str(item_mapping)
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
return StacCommon.get_stac_extension(
|
|
571
|
-
url=url, stac_config=stac_config, extension="oseo", properties=oseo_properties
|
|
572
|
-
)
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
async def get_queryables(
|
|
576
|
-
request: Request,
|
|
577
|
-
params: QueryablesGetParams,
|
|
578
|
-
provider: Optional[str] = None,
|
|
579
|
-
) -> dict[str, Any]:
|
|
580
|
-
"""Fetch the queryable properties for a collection.
|
|
581
|
-
|
|
582
|
-
:param collection_id: The ID of the collection.
|
|
583
|
-
:returns: A set containing the STAC standardized queryable properties for a collection.
|
|
584
|
-
"""
|
|
585
|
-
|
|
586
|
-
async def _fetch() -> dict[str, Any]:
|
|
587
|
-
python_queryables = eodag_api.list_queryables(
|
|
588
|
-
provider=provider,
|
|
589
|
-
fetch_providers=False,
|
|
590
|
-
**params.model_dump(exclude_none=True, by_alias=True),
|
|
591
|
-
)
|
|
592
|
-
|
|
593
|
-
python_queryables_json = python_queryables.get_model().model_json_schema(
|
|
594
|
-
by_alias=True
|
|
595
|
-
)
|
|
596
|
-
|
|
597
|
-
properties: dict[str, Any] = python_queryables_json["properties"]
|
|
598
|
-
required: list[str] = python_queryables_json.get("required") or []
|
|
599
|
-
|
|
600
|
-
# productType is either simply removed or replaced by collection later.
|
|
601
|
-
if "productType" in properties:
|
|
602
|
-
properties.pop("productType")
|
|
603
|
-
if "productType" in required:
|
|
604
|
-
required.remove("productType")
|
|
605
|
-
|
|
606
|
-
stac_properties: dict[str, Any] = {}
|
|
607
|
-
|
|
608
|
-
# get stac default properties to set prefixes
|
|
609
|
-
stac_item_properties = list(stac_config["item"]["properties"].values())
|
|
610
|
-
stac_item_properties.extend(stac_config["metadata_ignore"])
|
|
611
|
-
for param, queryable in properties.items():
|
|
612
|
-
# convert key to STAC format
|
|
613
|
-
if param in OSEO_METADATA_MAPPING.keys() and not any(
|
|
614
|
-
param in str(prop) for prop in stac_item_properties
|
|
615
|
-
):
|
|
616
|
-
param = f"oseo:{param}"
|
|
617
|
-
stac_param = EODAGSearch.to_stac(param, stac_item_properties, provider)
|
|
618
|
-
|
|
619
|
-
queryable["title"] = stac_param.split(":")[-1]
|
|
620
|
-
|
|
621
|
-
# remove null default values
|
|
622
|
-
if not queryable.get("default"):
|
|
623
|
-
queryable.pop("default", None)
|
|
624
|
-
|
|
625
|
-
stac_properties[stac_param] = queryable
|
|
626
|
-
required = list(map(lambda x: x.replace(param, stac_param), required))
|
|
627
|
-
|
|
628
|
-
# due to certain metadata mappings we might only get end_datetime but we can
|
|
629
|
-
# assume that start_datetime is also available
|
|
630
|
-
if (
|
|
631
|
-
"end_datetime" in stac_properties
|
|
632
|
-
and "start_datetime" not in stac_properties
|
|
633
|
-
):
|
|
634
|
-
stac_properties["start_datetime"] = deepcopy(
|
|
635
|
-
stac_properties["end_datetime"]
|
|
636
|
-
)
|
|
637
|
-
stac_properties["start_datetime"]["title"] = "start_datetime"
|
|
638
|
-
# if we can search by start_datetime we can search by datetime
|
|
639
|
-
if "start_datetime" in stac_properties:
|
|
640
|
-
stac_properties["datetime"] = StacQueryables.possible_properties[
|
|
641
|
-
"datetime"
|
|
642
|
-
].model_dump()
|
|
643
|
-
|
|
644
|
-
# format spatial extend properties to STAC format.
|
|
645
|
-
if "geometry" in stac_properties:
|
|
646
|
-
stac_properties["bbox"] = StacQueryables.possible_properties[
|
|
647
|
-
"bbox"
|
|
648
|
-
].model_dump()
|
|
649
|
-
stac_properties["geometry"] = StacQueryables.possible_properties[
|
|
650
|
-
"geometry"
|
|
651
|
-
].model_dump()
|
|
652
|
-
|
|
653
|
-
if not params.collection:
|
|
654
|
-
stac_properties["collection"] = StacQueryables.default_properties[
|
|
655
|
-
"collection"
|
|
656
|
-
].model_dump()
|
|
657
|
-
|
|
658
|
-
additional_properties = python_queryables.additional_properties
|
|
659
|
-
description = "Queryable names for the EODAG STAC API Item Search filter. "
|
|
660
|
-
description += python_queryables.additional_information
|
|
661
|
-
|
|
662
|
-
return StacQueryables(
|
|
663
|
-
q_id=request.state.url,
|
|
664
|
-
additional_properties=additional_properties,
|
|
665
|
-
properties=stac_properties,
|
|
666
|
-
required=required or None,
|
|
667
|
-
description=description,
|
|
668
|
-
).model_dump(mode="json", by_alias=True, exclude_none=True)
|
|
669
|
-
|
|
670
|
-
hashed_queryables = hash(params.model_dump_json())
|
|
671
|
-
return await cached(
|
|
672
|
-
_fetch, f"{CACHE_KEY_QUERYABLES}:{provider}:{hashed_queryables}", request
|
|
673
|
-
)
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
@_deprecated(
|
|
677
|
-
reason="Used to format output from deprecated function get_home_page_content",
|
|
678
|
-
version="2.6.1",
|
|
679
|
-
)
|
|
680
|
-
def get_templates_path() -> str:
|
|
681
|
-
"""Returns Jinja templates path"""
|
|
682
|
-
return os.path.join(os.path.dirname(__file__), "templates")
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
def crunch_products(
|
|
686
|
-
products: SearchResult, cruncher_name: str, **kwargs: Any
|
|
687
|
-
) -> SearchResult:
|
|
688
|
-
"""Apply an eodag cruncher to filter products"""
|
|
689
|
-
cruncher = crunchers.get(cruncher_name)
|
|
690
|
-
if not cruncher:
|
|
691
|
-
raise ValidationError(
|
|
692
|
-
f"Unknown crunch name. Use one of: {', '.join(crunchers.keys())}"
|
|
693
|
-
)
|
|
694
|
-
|
|
695
|
-
cruncher_config: dict[str, Any] = {}
|
|
696
|
-
for config_param in cruncher.config_params:
|
|
697
|
-
config_param_value = kwargs.get(config_param)
|
|
698
|
-
if not config_param_value:
|
|
699
|
-
raise ValidationError(
|
|
700
|
-
(
|
|
701
|
-
f"cruncher {cruncher} require additional parameters:"
|
|
702
|
-
f" {', '.join(cruncher.config_params)}"
|
|
703
|
-
)
|
|
704
|
-
)
|
|
705
|
-
cruncher_config[config_param] = config_param_value
|
|
706
|
-
|
|
707
|
-
try:
|
|
708
|
-
products = products.crunch(cruncher.clazz(cruncher_config), **kwargs)
|
|
709
|
-
except MisconfiguredError as e:
|
|
710
|
-
raise ValidationError(str(e)) from e
|
|
711
|
-
|
|
712
|
-
return products
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
def eodag_api_init() -> None:
|
|
716
|
-
"""Init EODataAccessGateway server instance, pre-running all time consuming tasks"""
|
|
717
|
-
eodag_api.fetch_product_types_list()
|
|
718
|
-
StacCollection.fetch_external_stac_collections(eodag_api)
|
|
719
|
-
|
|
720
|
-
# update eodag product_types config form external stac collections
|
|
721
|
-
for p, p_f in eodag_api.product_types_config.source.items():
|
|
722
|
-
for key in (p, p_f.get("alias")):
|
|
723
|
-
if key is None:
|
|
724
|
-
continue
|
|
725
|
-
ext_col = StacCollection.ext_stac_collections.get(key)
|
|
726
|
-
if not ext_col:
|
|
727
|
-
continue
|
|
728
|
-
platform: Union[str, list[str]] = ext_col.get("summaries", {}).get(
|
|
729
|
-
"platform"
|
|
730
|
-
)
|
|
731
|
-
constellation: Union[str, list[str]] = ext_col.get("summaries", {}).get(
|
|
732
|
-
"constellation"
|
|
733
|
-
)
|
|
734
|
-
processing_level: Union[str, list[str]] = ext_col.get("summaries", {}).get(
|
|
735
|
-
"processing:level"
|
|
736
|
-
)
|
|
737
|
-
# Check if platform or constellation are lists and join them into a string if they are
|
|
738
|
-
if isinstance(platform, list):
|
|
739
|
-
platform = ",".join(platform)
|
|
740
|
-
if isinstance(constellation, list):
|
|
741
|
-
constellation = ",".join(constellation)
|
|
742
|
-
if isinstance(processing_level, list):
|
|
743
|
-
processing_level = ",".join(processing_level)
|
|
744
|
-
|
|
745
|
-
update_fields = {
|
|
746
|
-
"title": ext_col.get("title"),
|
|
747
|
-
"abstract": ext_col["description"],
|
|
748
|
-
"keywords": ext_col.get("keywords"),
|
|
749
|
-
"instrument": ",".join(
|
|
750
|
-
ext_col.get("summaries", {}).get("instruments", [])
|
|
751
|
-
),
|
|
752
|
-
"platform": constellation,
|
|
753
|
-
"platformSerialIdentifier": platform,
|
|
754
|
-
"processingLevel": processing_level,
|
|
755
|
-
"license": ext_col["license"],
|
|
756
|
-
"missionStartDate": ext_col["extent"]["temporal"]["interval"][0][0],
|
|
757
|
-
"missionEndDate": ext_col["extent"]["temporal"]["interval"][-1][1],
|
|
758
|
-
}
|
|
759
|
-
clean = {k: v for k, v in update_fields.items() if v}
|
|
760
|
-
p_f.update(clean)
|
|
761
|
-
|
|
762
|
-
# pre-build search plugins
|
|
763
|
-
for provider in eodag_api.available_providers():
|
|
764
|
-
next(eodag_api._plugins_manager.get_search_plugins(provider=provider))
|