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.
- eodag/api/core.py +347 -247
- 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 +129 -93
- eodag/api/search_result.py +28 -12
- eodag/cli.py +61 -24
- eodag/config.py +457 -167
- eodag/plugins/apis/base.py +10 -4
- eodag/plugins/apis/ecmwf.py +53 -23
- eodag/plugins/apis/usgs.py +41 -17
- eodag/plugins/authentication/aws_auth.py +30 -18
- eodag/plugins/authentication/base.py +14 -3
- eodag/plugins/authentication/generic.py +14 -3
- eodag/plugins/authentication/header.py +14 -6
- eodag/plugins/authentication/keycloak.py +44 -25
- eodag/plugins/authentication/oauth.py +18 -4
- eodag/plugins/authentication/openid_connect.py +192 -171
- eodag/plugins/authentication/qsauth.py +12 -4
- eodag/plugins/authentication/sas_auth.py +22 -5
- eodag/plugins/authentication/token.py +95 -17
- eodag/plugins/authentication/token_exchange.py +19 -19
- eodag/plugins/base.py +4 -4
- eodag/plugins/crunch/base.py +8 -5
- eodag/plugins/crunch/filter_date.py +9 -6
- eodag/plugins/crunch/filter_latest_intersect.py +9 -8
- eodag/plugins/crunch/filter_latest_tpl_name.py +8 -8
- eodag/plugins/crunch/filter_overlap.py +9 -11
- eodag/plugins/crunch/filter_property.py +10 -10
- eodag/plugins/download/aws.py +181 -105
- eodag/plugins/download/base.py +49 -67
- eodag/plugins/download/creodias_s3.py +40 -2
- eodag/plugins/download/http.py +247 -223
- eodag/plugins/download/s3rest.py +29 -28
- eodag/plugins/manager.py +176 -41
- eodag/plugins/search/__init__.py +6 -5
- eodag/plugins/search/base.py +123 -60
- eodag/plugins/search/build_search_result.py +1046 -355
- eodag/plugins/search/cop_marine.py +132 -39
- eodag/plugins/search/creodias_s3.py +19 -68
- eodag/plugins/search/csw.py +48 -8
- eodag/plugins/search/data_request_search.py +124 -23
- eodag/plugins/search/qssearch.py +531 -310
- eodag/plugins/search/stac_list_assets.py +85 -0
- eodag/plugins/search/static_stac_search.py +23 -24
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +1295 -355
- eodag/resources/providers.yml +1819 -3010
- eodag/resources/stac.yml +3 -163
- eodag/resources/stac_api.yml +2 -2
- eodag/resources/user_conf_template.yml +115 -99
- eodag/rest/cache.py +2 -2
- eodag/rest/config.py +3 -4
- eodag/rest/constants.py +0 -1
- eodag/rest/core.py +157 -117
- eodag/rest/errors.py +181 -0
- eodag/rest/server.py +57 -339
- eodag/rest/stac.py +133 -581
- eodag/rest/types/collections_search.py +3 -3
- eodag/rest/types/eodag_search.py +41 -30
- eodag/rest/types/queryables.py +42 -32
- eodag/rest/types/stac_search.py +15 -16
- 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 +153 -32
- eodag/types/bbox.py +2 -2
- eodag/types/download_args.py +4 -4
- eodag/types/queryables.py +183 -73
- eodag/types/search_args.py +6 -6
- eodag/types/whoosh.py +127 -3
- eodag/utils/__init__.py +228 -106
- eodag/utils/exceptions.py +47 -26
- eodag/utils/import_system.py +2 -2
- eodag/utils/logging.py +37 -77
- eodag/utils/repr.py +65 -6
- eodag/utils/requests.py +13 -15
- eodag/utils/rest.py +2 -2
- eodag/utils/s3.py +231 -0
- eodag/utils/stac_reader.py +11 -11
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/METADATA +81 -81
- eodag-3.1.0.dist-info/RECORD +113 -0
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +5 -2
- eodag/resources/constraints/climate-dt.json +0 -13
- eodag/resources/constraints/extremes-dt.json +0 -8
- eodag/utils/constraints.py +0 -244
- eodag-3.0.0b3.dist-info/RECORD +0 -110
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/LICENSE +0 -0
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/top_level.txt +0 -0
|
@@ -19,9 +19,10 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import copy
|
|
21
21
|
import logging
|
|
22
|
+
import os
|
|
22
23
|
import re
|
|
23
24
|
from datetime import datetime
|
|
24
|
-
from typing import TYPE_CHECKING, Any,
|
|
25
|
+
from typing import TYPE_CHECKING, Any, Optional, cast
|
|
25
26
|
from urllib.parse import urlsplit
|
|
26
27
|
|
|
27
28
|
import boto3
|
|
@@ -37,7 +38,7 @@ from eodag.config import PluginConfig
|
|
|
37
38
|
from eodag.plugins.search import PreparedSearch
|
|
38
39
|
from eodag.plugins.search.static_stac_search import StaticStacSearch
|
|
39
40
|
from eodag.utils import get_bucket_name_and_prefix
|
|
40
|
-
from eodag.utils.exceptions import UnsupportedProductType, ValidationError
|
|
41
|
+
from eodag.utils.exceptions import RequestError, UnsupportedProductType, ValidationError
|
|
41
42
|
|
|
42
43
|
if TYPE_CHECKING:
|
|
43
44
|
from mypy_boto3_s3 import S3Client
|
|
@@ -67,6 +68,21 @@ def _get_date_from_yyyymmdd(date_str: str, item_key: str) -> Optional[datetime]:
|
|
|
67
68
|
return date
|
|
68
69
|
|
|
69
70
|
|
|
71
|
+
def _get_dates_from_dataset_data(
|
|
72
|
+
dataset_item: dict[str, Any]
|
|
73
|
+
) -> Optional[dict[str, str]]:
|
|
74
|
+
dates = {}
|
|
75
|
+
if "start_datetime" in dataset_item["properties"]:
|
|
76
|
+
dates["start"] = dataset_item["properties"]["start_datetime"]
|
|
77
|
+
dates["end"] = dataset_item["properties"]["end_datetime"]
|
|
78
|
+
elif "datetime" in dataset_item["properties"]:
|
|
79
|
+
dates["start"] = dataset_item["properties"]["datetime"]
|
|
80
|
+
dates["end"] = dataset_item["properties"]["datetime"]
|
|
81
|
+
else:
|
|
82
|
+
return None
|
|
83
|
+
return dates
|
|
84
|
+
|
|
85
|
+
|
|
70
86
|
def _get_s3_client(endpoint_url: str) -> S3Client:
|
|
71
87
|
s3_session = boto3.Session()
|
|
72
88
|
return s3_session.client(
|
|
@@ -80,7 +96,7 @@ def _get_s3_client(endpoint_url: str) -> S3Client:
|
|
|
80
96
|
)
|
|
81
97
|
|
|
82
98
|
|
|
83
|
-
def _check_int_values_properties(properties:
|
|
99
|
+
def _check_int_values_properties(properties: dict[str, Any]):
|
|
84
100
|
# remove int values with a bit length of more than 64 from the properties
|
|
85
101
|
invalid = []
|
|
86
102
|
for prop, prop_value in properties.items():
|
|
@@ -94,7 +110,21 @@ def _check_int_values_properties(properties: Dict[str, Any]):
|
|
|
94
110
|
|
|
95
111
|
|
|
96
112
|
class CopMarineSearch(StaticStacSearch):
|
|
97
|
-
"""class that implements search for the Copernicus Marine provider
|
|
113
|
+
"""class that implements search for the Copernicus Marine provider
|
|
114
|
+
|
|
115
|
+
It calls :meth:`~eodag.plugins.search.static_stac_search.StaticStacSearch.discover_product_types`
|
|
116
|
+
inherited from :class:`~eodag.plugins.search.static_stac_search.StaticStacSearch`
|
|
117
|
+
but for the actual search a special method which fetches the urls of the available products from an S3 storage and
|
|
118
|
+
filters them has been written.
|
|
119
|
+
|
|
120
|
+
The configuration parameters are inherited from the parent and grand-parent classes. The
|
|
121
|
+
:attr:`~eodag.config.PluginConfig.DiscoverMetadata.auto_discovery` parameter in the
|
|
122
|
+
:attr:`~eodag.config.PluginConfig.discover_metadata` section has to be set to ``false`` and the
|
|
123
|
+
:attr:`~eodag.config.PluginConfig.DiscoverQueryables.fetch_url` in the
|
|
124
|
+
:attr:`~eodag.config.PluginConfig.discover_queryables` queryables section has to be set to ``null`` to
|
|
125
|
+
overwrite the default config from the stac provider configuration because those functionalities
|
|
126
|
+
are not available.
|
|
127
|
+
"""
|
|
98
128
|
|
|
99
129
|
def __init__(self, provider: str, config: PluginConfig):
|
|
100
130
|
original_metadata_mapping = copy.deepcopy(config.metadata_mapping)
|
|
@@ -104,15 +134,13 @@ class CopMarineSearch(StaticStacSearch):
|
|
|
104
134
|
|
|
105
135
|
def _get_product_type_info(
|
|
106
136
|
self, product_type: str
|
|
107
|
-
) ->
|
|
137
|
+
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
|
108
138
|
"""Fetch product type and associated datasets info"""
|
|
109
139
|
|
|
110
|
-
fetch_url = cast(
|
|
111
|
-
|
|
112
|
-
self.config.discover_product_types["fetch_url"].format(
|
|
113
|
-
**self.config.__dict__
|
|
114
|
-
),
|
|
140
|
+
fetch_url = cast(str, self.config.discover_product_types["fetch_url"]).format(
|
|
141
|
+
**self.config.__dict__
|
|
115
142
|
)
|
|
143
|
+
|
|
116
144
|
logger.debug("fetch data for collection %s", product_type)
|
|
117
145
|
provider_product_type = self.config.products.get(product_type, {}).get(
|
|
118
146
|
"productType", None
|
|
@@ -125,9 +153,14 @@ class CopMarineSearch(StaticStacSearch):
|
|
|
125
153
|
)
|
|
126
154
|
try:
|
|
127
155
|
collection_data = requests.get(collection_url).json()
|
|
128
|
-
except requests.RequestException:
|
|
156
|
+
except requests.RequestException as exc:
|
|
157
|
+
if exc.errno == 404:
|
|
158
|
+
logger.error("product %s not found", product_type)
|
|
159
|
+
raise UnsupportedProductType(product_type)
|
|
129
160
|
logger.error("data for product %s could not be fetched", product_type)
|
|
130
|
-
raise
|
|
161
|
+
raise RequestError.from_error(
|
|
162
|
+
exc, f"data for product {product_type} could not be fetched"
|
|
163
|
+
) from exc
|
|
131
164
|
|
|
132
165
|
datasets = []
|
|
133
166
|
for link in [li for li in collection_data["links"] if li["rel"] == "item"]:
|
|
@@ -150,13 +183,23 @@ class CopMarineSearch(StaticStacSearch):
|
|
|
150
183
|
product_id: str,
|
|
151
184
|
s3_url: str,
|
|
152
185
|
product_type: str,
|
|
153
|
-
dataset_item:
|
|
154
|
-
collection_dict:
|
|
186
|
+
dataset_item: dict[str, Any],
|
|
187
|
+
collection_dict: dict[str, Any],
|
|
155
188
|
):
|
|
189
|
+
# try to find date(s) in product id
|
|
190
|
+
item_dates = re.findall(r"(\d{4})(0[1-9]|1[0-2])([0-3]\d)", product_id)
|
|
191
|
+
if not item_dates:
|
|
192
|
+
item_dates = re.findall(r"_(\d{4})(0[1-9]|1[0-2])", product_id)
|
|
193
|
+
use_dataset_dates = not bool(item_dates)
|
|
156
194
|
for obj in collection_objects["Contents"]:
|
|
157
195
|
if product_id in obj["Key"]:
|
|
158
196
|
return self._create_product(
|
|
159
|
-
product_type,
|
|
197
|
+
product_type,
|
|
198
|
+
obj["Key"],
|
|
199
|
+
s3_url,
|
|
200
|
+
dataset_item,
|
|
201
|
+
collection_dict,
|
|
202
|
+
use_dataset_dates,
|
|
160
203
|
)
|
|
161
204
|
return None
|
|
162
205
|
|
|
@@ -165,12 +208,12 @@ class CopMarineSearch(StaticStacSearch):
|
|
|
165
208
|
product_type: str,
|
|
166
209
|
item_key: str,
|
|
167
210
|
s3_url: str,
|
|
168
|
-
dataset_item:
|
|
169
|
-
collection_dict:
|
|
211
|
+
dataset_item: dict[str, Any],
|
|
212
|
+
collection_dict: dict[str, Any],
|
|
170
213
|
use_dataset_dates: bool = False,
|
|
171
214
|
) -> Optional[EOProduct]:
|
|
172
215
|
|
|
173
|
-
item_id = item_key.split("/")[-1]
|
|
216
|
+
item_id = os.path.splitext(item_key.split("/")[-1])[0]
|
|
174
217
|
download_url = s3_url + "/" + item_key
|
|
175
218
|
properties = {
|
|
176
219
|
"id": item_id,
|
|
@@ -180,20 +223,16 @@ class CopMarineSearch(StaticStacSearch):
|
|
|
180
223
|
"dataset": dataset_item["id"],
|
|
181
224
|
}
|
|
182
225
|
if use_dataset_dates:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
"end_datetime"
|
|
189
|
-
]
|
|
190
|
-
elif "datetime" in dataset_item:
|
|
191
|
-
properties["startTimeFromAscendingNode"] = dataset_item["datetime"]
|
|
192
|
-
properties["completionTimeFromAscendingNode"] = dataset_item["datetime"]
|
|
226
|
+
dates = _get_dates_from_dataset_data(dataset_item)
|
|
227
|
+
if not dates:
|
|
228
|
+
return None
|
|
229
|
+
properties["startTimeFromAscendingNode"] = dates["start"]
|
|
230
|
+
properties["completionTimeFromAscendingNode"] = dates["end"]
|
|
193
231
|
else:
|
|
194
|
-
item_dates = re.findall(r"\d{
|
|
232
|
+
item_dates = re.findall(r"(\d{4})(0[1-9]|1[0-2])([0-3]\d)", item_id)
|
|
195
233
|
if not item_dates:
|
|
196
|
-
item_dates = re.findall(r"\d{
|
|
234
|
+
item_dates = re.findall(r"_(\d{4})(0[1-9]|1[0-2])", item_id)
|
|
235
|
+
item_dates = ["".join(row) for row in item_dates]
|
|
197
236
|
item_start = _get_date_from_yyyymmdd(item_dates[0], item_key)
|
|
198
237
|
if not item_start: # identified pattern was not a valid datetime
|
|
199
238
|
return None
|
|
@@ -209,11 +248,26 @@ class CopMarineSearch(StaticStacSearch):
|
|
|
209
248
|
).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
210
249
|
|
|
211
250
|
for key, value in collection_dict["properties"].items():
|
|
212
|
-
if key not in ["id", "title", "start_datetime", "end_datetime"]:
|
|
251
|
+
if key not in ["id", "title", "start_datetime", "end_datetime", "datetime"]:
|
|
213
252
|
properties[key] = value
|
|
214
253
|
for key, value in dataset_item["properties"].items():
|
|
215
|
-
if key not in ["id", "title", "start_datetime", "end_datetime"]:
|
|
254
|
+
if key not in ["id", "title", "start_datetime", "end_datetime", "datetime"]:
|
|
216
255
|
properties[key] = value
|
|
256
|
+
|
|
257
|
+
code_mapping = self.config.products.get(product_type, {}).get(
|
|
258
|
+
"code_mapping", None
|
|
259
|
+
)
|
|
260
|
+
if code_mapping:
|
|
261
|
+
id_parts = item_id.split("_")
|
|
262
|
+
if len(id_parts) > code_mapping["index"]:
|
|
263
|
+
code = id_parts[code_mapping["index"]]
|
|
264
|
+
if "pattern" not in code_mapping:
|
|
265
|
+
properties[code_mapping["param"]] = code
|
|
266
|
+
elif re.findall(code_mapping["pattern"], code):
|
|
267
|
+
properties[code_mapping["param"]] = re.findall(
|
|
268
|
+
code_mapping["pattern"], code
|
|
269
|
+
)[0]
|
|
270
|
+
|
|
217
271
|
_check_int_values_properties(properties)
|
|
218
272
|
|
|
219
273
|
properties["thumbnail"] = collection_dict["assets"]["thumbnail"]["href"]
|
|
@@ -234,7 +288,7 @@ class CopMarineSearch(StaticStacSearch):
|
|
|
234
288
|
self,
|
|
235
289
|
prep: PreparedSearch = PreparedSearch(),
|
|
236
290
|
**kwargs: Any,
|
|
237
|
-
) ->
|
|
291
|
+
) -> tuple[list[EOProduct], Optional[int]]:
|
|
238
292
|
"""
|
|
239
293
|
Implementation of search for the Copernicus Marine provider
|
|
240
294
|
:param prep: object containing search parameterds
|
|
@@ -254,7 +308,7 @@ class CopMarineSearch(StaticStacSearch):
|
|
|
254
308
|
"parameter product type is required for search with cop_marine provider"
|
|
255
309
|
)
|
|
256
310
|
collection_dict, datasets_items_list = self._get_product_type_info(product_type)
|
|
257
|
-
products:
|
|
311
|
+
products: list[EOProduct] = []
|
|
258
312
|
start_index = items_per_page * (page - 1) + 1
|
|
259
313
|
num_total = 0
|
|
260
314
|
for i, dataset_item in enumerate(datasets_items_list):
|
|
@@ -348,16 +402,54 @@ class CopMarineSearch(StaticStacSearch):
|
|
|
348
402
|
|
|
349
403
|
for obj in s3_objects["Contents"]:
|
|
350
404
|
item_key = obj["Key"]
|
|
405
|
+
item_id = os.path.splitext(item_key.split("/")[-1])[0]
|
|
351
406
|
# filter according to date(s) in item id
|
|
352
|
-
item_dates = re.findall(r"\d{
|
|
407
|
+
item_dates = re.findall(r"(\d{4})(0[1-9]|1[0-2])([0-3]\d)", item_id)
|
|
353
408
|
if not item_dates:
|
|
354
|
-
item_dates = re.findall(r"\d{
|
|
355
|
-
|
|
356
|
-
|
|
409
|
+
item_dates = re.findall(r"_(\d{4})(0[1-9]|1[0-2])", item_id)
|
|
410
|
+
item_dates = [
|
|
411
|
+
"".join(row) for row in item_dates
|
|
412
|
+
] # join tuples returned by findall
|
|
413
|
+
item_start = None
|
|
414
|
+
item_end = None
|
|
415
|
+
use_dataset_dates = False
|
|
416
|
+
if item_dates:
|
|
417
|
+
item_start = _get_date_from_yyyymmdd(item_dates[0], item_key)
|
|
418
|
+
if len(item_dates) > 2: # start, end and created_at timestamps
|
|
419
|
+
item_end = _get_date_from_yyyymmdd(item_dates[1], item_key)
|
|
420
|
+
if not item_start:
|
|
421
|
+
# no valid datetime given in id
|
|
422
|
+
use_dataset_dates = True
|
|
423
|
+
dates = _get_dates_from_dataset_data(dataset_item)
|
|
424
|
+
if dates:
|
|
425
|
+
item_start_str = dates["start"].replace("Z", "+0000")
|
|
426
|
+
item_end_str = dates["end"].replace("Z", "+0000")
|
|
427
|
+
try:
|
|
428
|
+
item_start = datetime.strptime(
|
|
429
|
+
item_start_str, "%Y-%m-%dT%H:%M:%S.%f%z"
|
|
430
|
+
)
|
|
431
|
+
item_end = datetime.strptime(
|
|
432
|
+
item_end_str, "%Y-%m-%dT%H:%M:%S.%f%z"
|
|
433
|
+
)
|
|
434
|
+
except ValueError:
|
|
435
|
+
item_start = datetime.strptime(
|
|
436
|
+
item_start_str, "%Y-%m-%dT%H:%M:%S%z"
|
|
437
|
+
)
|
|
438
|
+
item_end = datetime.strptime(
|
|
439
|
+
item_end_str, "%Y-%m-%dT%H:%M:%S%z"
|
|
440
|
+
)
|
|
441
|
+
if not item_start:
|
|
442
|
+
# no valid datetime in id and dataset data
|
|
357
443
|
continue
|
|
358
444
|
if item_start > end_date:
|
|
359
445
|
stop_search = True
|
|
360
|
-
if
|
|
446
|
+
if (
|
|
447
|
+
(start_date <= item_start <= end_date)
|
|
448
|
+
or (item_end and start_date <= item_end <= end_date)
|
|
449
|
+
or (
|
|
450
|
+
item_end and item_start < start_date and item_end > end_date
|
|
451
|
+
)
|
|
452
|
+
):
|
|
361
453
|
num_total += 1
|
|
362
454
|
if num_total < start_index:
|
|
363
455
|
continue
|
|
@@ -368,6 +460,7 @@ class CopMarineSearch(StaticStacSearch):
|
|
|
368
460
|
endpoint_url + "/" + bucket,
|
|
369
461
|
dataset_item,
|
|
370
462
|
collection_dict,
|
|
463
|
+
use_dataset_dates,
|
|
371
464
|
)
|
|
372
465
|
if product:
|
|
373
466
|
products.append(product)
|
|
@@ -17,26 +17,22 @@
|
|
|
17
17
|
# limitations under the License.
|
|
18
18
|
import logging
|
|
19
19
|
from types import MethodType
|
|
20
|
-
from typing import Any
|
|
20
|
+
from typing import Any
|
|
21
21
|
|
|
22
|
-
import boto3
|
|
23
|
-
import botocore
|
|
24
22
|
from botocore.exceptions import BotoCoreError
|
|
25
23
|
|
|
26
|
-
from eodag.api.product import
|
|
24
|
+
from eodag.api.product import EOProduct # type: ignore
|
|
27
25
|
from eodag.api.search_result import RawSearchResult
|
|
28
|
-
from eodag.config import PluginConfig
|
|
29
|
-
from eodag.plugins.authentication.aws_auth import AwsAuth
|
|
30
26
|
from eodag.plugins.search.qssearch import ODataV4Search
|
|
31
|
-
from eodag.utils import
|
|
32
|
-
from eodag.utils.
|
|
27
|
+
from eodag.utils.exceptions import RequestError
|
|
28
|
+
from eodag.utils.s3 import update_assets_from_s3
|
|
33
29
|
|
|
34
|
-
DATA_EXTENSIONS = ["jp2", "tiff", "nc", "grib"]
|
|
35
30
|
logger = logging.getLogger("eodag.search.creodiass3")
|
|
36
31
|
|
|
37
32
|
|
|
38
33
|
def patched_register_downloader(self, downloader, authenticator):
|
|
39
34
|
"""Add the download information to the product.
|
|
35
|
+
|
|
40
36
|
:param self: product to which information should be added
|
|
41
37
|
:param downloader: The download method that it can use
|
|
42
38
|
:class:`~eodag.plugins.download.base.Download` or
|
|
@@ -48,69 +44,24 @@ def patched_register_downloader(self, downloader, authenticator):
|
|
|
48
44
|
self.register_downloader_only(downloader, authenticator)
|
|
49
45
|
# and also update assets
|
|
50
46
|
try:
|
|
51
|
-
|
|
47
|
+
update_assets_from_s3(
|
|
48
|
+
self, authenticator, getattr(downloader.config, "s3_endpoint", None)
|
|
49
|
+
)
|
|
52
50
|
except BotoCoreError as e:
|
|
53
|
-
raise RequestError(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _update_assets(product: EOProduct, config: PluginConfig, auth: AwsAuth):
|
|
57
|
-
product.assets = {}
|
|
58
|
-
prefix = (
|
|
59
|
-
product.properties.get("productIdentifier", None).replace("/eodata/", "") + "/"
|
|
60
|
-
)
|
|
61
|
-
if prefix:
|
|
62
|
-
try:
|
|
63
|
-
auth_dict = auth.authenticate()
|
|
64
|
-
required_creds = ["aws_access_key_id", "aws_secret_access_key"]
|
|
65
|
-
if not all(x in auth_dict for x in required_creds):
|
|
66
|
-
raise MisconfiguredError(
|
|
67
|
-
f"Incomplete credentials for {product.provider}, missing "
|
|
68
|
-
f"{[x for x in required_creds if x not in auth_dict]}"
|
|
69
|
-
)
|
|
70
|
-
if not getattr(auth, "s3_client", None):
|
|
71
|
-
auth.s3_client = boto3.client(
|
|
72
|
-
"s3",
|
|
73
|
-
endpoint_url=config.base_uri,
|
|
74
|
-
aws_access_key_id=auth_dict["aws_access_key_id"],
|
|
75
|
-
aws_secret_access_key=auth_dict["aws_secret_access_key"],
|
|
76
|
-
)
|
|
77
|
-
logger.debug("Listing assets in %s", prefix)
|
|
78
|
-
product.assets = AssetsDict(product)
|
|
79
|
-
for asset in auth.s3_client.list_objects(
|
|
80
|
-
Bucket=config.s3_bucket, Prefix=prefix, MaxKeys=300
|
|
81
|
-
)["Contents"]:
|
|
82
|
-
asset_basename = (
|
|
83
|
-
asset["Key"].split("/")[-1] if "/" in asset["Key"] else asset["Key"]
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
if len(asset_basename) > 0 and asset_basename not in product.assets:
|
|
87
|
-
role = (
|
|
88
|
-
"data"
|
|
89
|
-
if asset_basename.split(".")[-1] in DATA_EXTENSIONS
|
|
90
|
-
else "metadata"
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
product.assets[asset_basename] = {
|
|
94
|
-
"title": asset_basename,
|
|
95
|
-
"roles": [role],
|
|
96
|
-
"href": f"s3://{config.s3_bucket}/{asset['Key']}",
|
|
97
|
-
}
|
|
98
|
-
if mime_type := guess_file_type(asset["Key"]):
|
|
99
|
-
product.assets[asset_basename]["type"] = mime_type
|
|
100
|
-
# update driver
|
|
101
|
-
product.driver = product.get_driver()
|
|
102
|
-
|
|
103
|
-
except botocore.exceptions.ClientError as e:
|
|
104
|
-
if str(auth.config.auth_error_code) in str(e):
|
|
105
|
-
raise AuthenticationError(
|
|
106
|
-
f"Authentication failed on {config.base_uri} s3"
|
|
107
|
-
) from e
|
|
108
|
-
raise RequestError(f"assets for product {prefix} could not be found") from e
|
|
51
|
+
raise RequestError.from_error(e, "could not update assets") from e
|
|
109
52
|
|
|
110
53
|
|
|
111
54
|
class CreodiasS3Search(ODataV4Search):
|
|
112
55
|
"""
|
|
113
|
-
|
|
56
|
+
``CreodiasS3Search`` is an extension of :class:`~eodag.plugins.search.qssearch.ODataV4Search`,
|
|
57
|
+
it executes a Search on creodias and adapts results so that the assets contain links to s3.
|
|
58
|
+
It has the same configuration parameters as :class:`~eodag.plugins.search.qssearch.ODataV4Search` and
|
|
59
|
+
one additional parameter:
|
|
60
|
+
|
|
61
|
+
:param provider: provider name
|
|
62
|
+
:param config: Search plugin configuration:
|
|
63
|
+
|
|
64
|
+
* :attr:`~eodag.config.PluginConfig.s3_endpoint` (``str``) (**mandatory**): base url of the s3
|
|
114
65
|
"""
|
|
115
66
|
|
|
116
67
|
def __init__(self, provider, config):
|
|
@@ -118,7 +69,7 @@ class CreodiasS3Search(ODataV4Search):
|
|
|
118
69
|
|
|
119
70
|
def normalize_results(
|
|
120
71
|
self, results: RawSearchResult, **kwargs: Any
|
|
121
|
-
) ->
|
|
72
|
+
) -> list[EOProduct]:
|
|
122
73
|
"""Build EOProducts from provider results"""
|
|
123
74
|
|
|
124
75
|
products = super(CreodiasS3Search, self).normalize_results(results, **kwargs)
|
eodag/plugins/search/csw.py
CHANGED
|
@@ -19,7 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import logging
|
|
21
21
|
import re
|
|
22
|
-
from typing import TYPE_CHECKING, Any,
|
|
22
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
23
23
|
|
|
24
24
|
import pyproj
|
|
25
25
|
from owslib.csw import CatalogueServiceWeb
|
|
@@ -52,7 +52,47 @@ SUPPORTED_REFERENCE_SCHEMES = ["WWW:DOWNLOAD-1.0-http--download"]
|
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
class CSWSearch(Search):
|
|
55
|
-
"""A plugin for implementing search based on OGC CSW
|
|
55
|
+
"""A plugin for implementing search based on OGC CSW
|
|
56
|
+
|
|
57
|
+
:param provider: provider name
|
|
58
|
+
:param config: Search plugin configuration:
|
|
59
|
+
|
|
60
|
+
* :attr:`~eodag.config.PluginConfig.api_endpoint` (``str``) (**mandatory**): The endpoint of the
|
|
61
|
+
provider's search interface
|
|
62
|
+
* :attr:`~eodag.config.PluginConfig.version` (``str``): OGC Catalogue Service version; default: ``2.0.2``
|
|
63
|
+
* :attr:`~eodag.config.PluginConfig.search_definition` (``dict[str, Any]``) (**mandatory**):
|
|
64
|
+
|
|
65
|
+
* **product_type_tags** (``list[dict[str, Any]``): dict of product type tags
|
|
66
|
+
* **resource_location_filter** (``str``): regex string
|
|
67
|
+
* **date_tags** (``dict[str, Any]``): tags for start and end
|
|
68
|
+
|
|
69
|
+
* :attr:`~eodag.config.PluginConfig.metadata_mapping` (``dict[str, Any]``): The search plugins of this kind can
|
|
70
|
+
detect when a metadata mapping is "query-able", and get the semantics of how to format the query string
|
|
71
|
+
parameter that enables to make a query on the corresponding metadata. To make a metadata query-able,
|
|
72
|
+
just configure it in the metadata mapping to be a list of 2 items, the first one being the
|
|
73
|
+
specification of the query string search formatting. The later is a string following the
|
|
74
|
+
specification of Python string formatting, with a special behaviour added to it. For example,
|
|
75
|
+
an entry in the metadata mapping of this kind::
|
|
76
|
+
|
|
77
|
+
completionTimeFromAscendingNode:
|
|
78
|
+
- 'f=acquisition.endViewingDate:lte:{completionTimeFromAscendingNode#timestamp}'
|
|
79
|
+
- '$.properties.acquisition.endViewingDate'
|
|
80
|
+
|
|
81
|
+
means that the search url will have a query string parameter named ``f`` with a value of
|
|
82
|
+
``acquisition.endViewingDate:lte:1543922280.0`` if the search was done with the value
|
|
83
|
+
of ``completionTimeFromAscendingNode`` being ``2018-12-04T12:18:00``. What happened is that
|
|
84
|
+
``{completionTimeFromAscendingNode#timestamp}`` was replaced with the timestamp of the value
|
|
85
|
+
of ``completionTimeFromAscendingNode``. This example shows all there is to know about the
|
|
86
|
+
semantics of the query string formatting introduced by this plugin: any eodag search parameter
|
|
87
|
+
can be referenced in the query string with an additional optional conversion function that
|
|
88
|
+
is separated from it by a ``#`` (see :func:`~eodag.api.product.metadata_mapping.format_metadata` for further
|
|
89
|
+
details on the available converters). Note that for the values in the
|
|
90
|
+
:attr:`~eodag.config.PluginConfig.free_text_search_operations` configuration parameter follow the same rule.
|
|
91
|
+
If the metadata_mapping is not a list but only a string, this means that the parameters is not queryable but
|
|
92
|
+
it is included in the result obtained from the provider. The string indicates how the provider result should
|
|
93
|
+
be mapped to the eodag parameter.
|
|
94
|
+
|
|
95
|
+
"""
|
|
56
96
|
|
|
57
97
|
def __init__(self, provider: str, config: PluginConfig) -> None:
|
|
58
98
|
super(CSWSearch, self).__init__(provider, config)
|
|
@@ -67,7 +107,7 @@ class CSWSearch(Search):
|
|
|
67
107
|
self,
|
|
68
108
|
prep: PreparedSearch = PreparedSearch(),
|
|
69
109
|
**kwargs: Any,
|
|
70
|
-
) ->
|
|
110
|
+
) -> tuple[list[EOProduct], Optional[int]]:
|
|
71
111
|
"""Perform a search on a OGC/CSW-like interface"""
|
|
72
112
|
product_type = kwargs.get("productType")
|
|
73
113
|
if product_type is None:
|
|
@@ -77,7 +117,7 @@ class CSWSearch(Search):
|
|
|
77
117
|
self.__init_catalog(**getattr(auth.config, "credentials", {}))
|
|
78
118
|
else:
|
|
79
119
|
self.__init_catalog()
|
|
80
|
-
results:
|
|
120
|
+
results: list[EOProduct] = []
|
|
81
121
|
if self.catalog:
|
|
82
122
|
provider_product_type = self.config.products[product_type]["productType"]
|
|
83
123
|
for product_type_def in self.config.search_definition["product_type_tags"]:
|
|
@@ -189,12 +229,12 @@ class CSWSearch(Search):
|
|
|
189
229
|
|
|
190
230
|
def __convert_query_params(
|
|
191
231
|
self,
|
|
192
|
-
product_type_def:
|
|
232
|
+
product_type_def: dict[str, Any],
|
|
193
233
|
product_type: str,
|
|
194
|
-
params:
|
|
195
|
-
) -> Union[
|
|
234
|
+
params: dict[str, Any],
|
|
235
|
+
) -> Union[list[OgcExpression], list[list[OgcExpression]]]:
|
|
196
236
|
"""Translates eodag search to CSW constraints using owslib constraint classes"""
|
|
197
|
-
constraints:
|
|
237
|
+
constraints: list[OgcExpression] = []
|
|
198
238
|
# How the match should be performed (fuzzy, prefix, postfix or exact).
|
|
199
239
|
# defaults to fuzzy
|
|
200
240
|
pt_tag, matching = (
|