eodag 3.2.1__py3-none-any.whl → 3.3.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 +2 -1
- eodag/api/search_result.py +3 -4
- eodag/config.py +3 -0
- eodag/plugins/authentication/token.py +16 -1
- eodag/plugins/download/http.py +46 -17
- eodag/plugins/search/build_search_result.py +269 -83
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/providers.yml +57 -10
- eodag/utils/__init__.py +7 -1
- {eodag-3.2.1.dist-info → eodag-3.3.0.dist-info}/METADATA +2 -2
- {eodag-3.2.1.dist-info → eodag-3.3.0.dist-info}/RECORD +15 -15
- {eodag-3.2.1.dist-info → eodag-3.3.0.dist-info}/WHEEL +0 -0
- {eodag-3.2.1.dist-info → eodag-3.3.0.dist-info}/entry_points.txt +0 -0
- {eodag-3.2.1.dist-info → eodag-3.3.0.dist-info}/licenses/LICENSE +0 -0
- {eodag-3.2.1.dist-info → eodag-3.3.0.dist-info}/top_level.txt +0 -0
|
@@ -23,6 +23,7 @@ import logging
|
|
|
23
23
|
import re
|
|
24
24
|
from collections import OrderedDict
|
|
25
25
|
from datetime import date, datetime, timedelta, timezone
|
|
26
|
+
from types import MethodType
|
|
26
27
|
from typing import TYPE_CHECKING, Annotated, Any, Optional, Union
|
|
27
28
|
from urllib.parse import quote_plus, unquote_plus
|
|
28
29
|
|
|
@@ -35,19 +36,22 @@ from pydantic import Field
|
|
|
35
36
|
from pydantic.fields import FieldInfo
|
|
36
37
|
from requests.auth import AuthBase
|
|
37
38
|
from shapely.geometry.base import BaseGeometry
|
|
38
|
-
from typing_extensions import get_args
|
|
39
|
+
from typing_extensions import get_args # noqa: F401
|
|
39
40
|
|
|
40
41
|
from eodag.api.product import EOProduct
|
|
41
42
|
from eodag.api.product.metadata_mapping import (
|
|
43
|
+
DEFAULT_GEOMETRY,
|
|
42
44
|
NOT_AVAILABLE,
|
|
43
45
|
OFFLINE_STATUS,
|
|
46
|
+
STAGING_STATUS,
|
|
44
47
|
format_metadata,
|
|
48
|
+
mtd_cfg_as_conversion_and_querypath,
|
|
45
49
|
properties_from_json,
|
|
46
50
|
)
|
|
47
51
|
from eodag.api.search_result import RawSearchResult
|
|
48
52
|
from eodag.plugins.search import PreparedSearch
|
|
49
53
|
from eodag.plugins.search.qssearch import PostJsonSearch, QueryStringSearch
|
|
50
|
-
from eodag.types import json_field_definition_to_python
|
|
54
|
+
from eodag.types import json_field_definition_to_python # noqa: F401
|
|
51
55
|
from eodag.types.queryables import Queryables, QueryablesDict
|
|
52
56
|
from eodag.utils import (
|
|
53
57
|
DEFAULT_MISSION_START_DATE,
|
|
@@ -57,7 +61,7 @@ from eodag.utils import (
|
|
|
57
61
|
get_geometry_from_various,
|
|
58
62
|
is_range_in_range,
|
|
59
63
|
)
|
|
60
|
-
from eodag.utils.exceptions import ValidationError
|
|
64
|
+
from eodag.utils.exceptions import DownloadError, NotAvailableError, ValidationError
|
|
61
65
|
from eodag.utils.requests import fetch_json
|
|
62
66
|
|
|
63
67
|
if TYPE_CHECKING:
|
|
@@ -315,7 +319,7 @@ def append_time(input_date: date, time: Optional[str]) -> datetime:
|
|
|
315
319
|
def parse_date(
|
|
316
320
|
date_str: str, time: Optional[Union[str, list[str]]]
|
|
317
321
|
) -> tuple[datetime, datetime]:
|
|
318
|
-
"""Parses a date string in
|
|
322
|
+
"""Parses a date string in formats YYYY-MM-DD, YYYMMDD, solo or in start/end or start/to/end intervals."""
|
|
319
323
|
if "to" in date_str:
|
|
320
324
|
start_date_str, end_date_str = date_str.split("/to/")
|
|
321
325
|
elif "/" in date_str:
|
|
@@ -325,6 +329,14 @@ def parse_date(
|
|
|
325
329
|
else:
|
|
326
330
|
start_date_str = end_date_str = date_str
|
|
327
331
|
|
|
332
|
+
# Update YYYYMMDD formatted dates
|
|
333
|
+
if re.match(r"^\d{8}$", start_date_str):
|
|
334
|
+
start_date_str = (
|
|
335
|
+
f"{start_date_str[:4]}-{start_date_str[4:6]}-{start_date_str[6:]}"
|
|
336
|
+
)
|
|
337
|
+
if re.match(r"^\d{8}$", end_date_str):
|
|
338
|
+
end_date_str = f"{end_date_str[:4]}-{end_date_str[4:6]}-{end_date_str[6:]}"
|
|
339
|
+
|
|
328
340
|
start_date = datetime.fromisoformat(start_date_str.rstrip("Z"))
|
|
329
341
|
end_date = datetime.fromisoformat(end_date_str.rstrip("Z"))
|
|
330
342
|
|
|
@@ -454,6 +466,16 @@ class ECMWFSearch(PostJsonSearch):
|
|
|
454
466
|
self.config.__dict__.setdefault("api_endpoint", "")
|
|
455
467
|
self.config.pagination.setdefault("next_page_query_obj", "{{}}")
|
|
456
468
|
|
|
469
|
+
# defaut conf for accepting custom query params
|
|
470
|
+
self.config.__dict__.setdefault(
|
|
471
|
+
"discover_metadata",
|
|
472
|
+
{
|
|
473
|
+
"auto_discovery": True,
|
|
474
|
+
"search_param": "{metadata}",
|
|
475
|
+
"metadata_pattern": "^[a-zA-Z0-9][a-zA-Z0-9_]*$",
|
|
476
|
+
},
|
|
477
|
+
)
|
|
478
|
+
|
|
457
479
|
def do_search(self, *args: Any, **kwargs: Any) -> list[dict[str, Any]]:
|
|
458
480
|
"""Should perform the actual search request.
|
|
459
481
|
|
|
@@ -616,14 +638,6 @@ class ECMWFSearch(PostJsonSearch):
|
|
|
616
638
|
if not isinstance(mapping, list):
|
|
617
639
|
mapping = product_type_conf[END]
|
|
618
640
|
if isinstance(mapping, list):
|
|
619
|
-
# get time parameters (date, year, month, ...) from metadata mapping
|
|
620
|
-
input_mapping = mapping[0].replace("{{", "").replace("}}", "")
|
|
621
|
-
time_params = [
|
|
622
|
-
values.split(":")[0].strip() for values in input_mapping.split(",")
|
|
623
|
-
]
|
|
624
|
-
time_params = [
|
|
625
|
-
tp.replace('"', "").replace("'", "") for tp in time_params
|
|
626
|
-
]
|
|
627
641
|
# if startTime is not given but other time params (e.g. year/month/(day)) are given,
|
|
628
642
|
# no default date is required
|
|
629
643
|
start, end = ecmwf_temporal_to_eodag(keywords)
|
|
@@ -638,9 +652,6 @@ class ECMWFSearch(PostJsonSearch):
|
|
|
638
652
|
"missionEndDate", today().isoformat()
|
|
639
653
|
)
|
|
640
654
|
)
|
|
641
|
-
else:
|
|
642
|
-
keywords[START] = start
|
|
643
|
-
keywords[END] = end
|
|
644
655
|
|
|
645
656
|
def _get_product_type_queryables(
|
|
646
657
|
self, product_type: Optional[str], alias: Optional[str], filters: dict[str, Any]
|
|
@@ -1030,7 +1041,7 @@ class ECMWFSearch(PostJsonSearch):
|
|
|
1030
1041
|
"""
|
|
1031
1042
|
# Rename keywords from form with metadata mapping.
|
|
1032
1043
|
# Needed to map constraints like "xxxx" to eodag parameter "ecmwf:xxxx"
|
|
1033
|
-
required = [ecmwf_format(k) for k in required_keywords]
|
|
1044
|
+
required = [ecmwf_format(k) for k in required_keywords] # noqa: F841
|
|
1034
1045
|
|
|
1035
1046
|
queryables: dict[str, Annotated[Any, FieldInfo]] = {}
|
|
1036
1047
|
for name, values in available_values.items():
|
|
@@ -1116,92 +1127,74 @@ class ECMWFSearch(PostJsonSearch):
|
|
|
1116
1127
|
_dc_qs = kwargs.pop("_dc_qs", None)
|
|
1117
1128
|
if _dc_qs is not None:
|
|
1118
1129
|
qs = unquote_plus(unquote_plus(_dc_qs))
|
|
1119
|
-
|
|
1130
|
+
sorted_unpaginated_qp = geojson.loads(qs)
|
|
1120
1131
|
else:
|
|
1121
|
-
|
|
1122
|
-
if isinstance(
|
|
1123
|
-
self.config.pagination["next_page_query_obj"], str
|
|
1124
|
-
) and hasattr(results, "query_params_unpaginated"):
|
|
1125
|
-
unpaginated_query_params = results.query_params_unpaginated
|
|
1126
|
-
elif isinstance(self.config.pagination["next_page_query_obj"], str):
|
|
1127
|
-
next_page_query_obj = orjson.loads(
|
|
1128
|
-
self.config.pagination["next_page_query_obj"].format()
|
|
1129
|
-
)
|
|
1130
|
-
unpaginated_query_params = {
|
|
1131
|
-
k: v
|
|
1132
|
-
for k, v in results.query_params.items()
|
|
1133
|
-
if (k, v) not in next_page_query_obj.items()
|
|
1134
|
-
}
|
|
1135
|
-
else:
|
|
1136
|
-
unpaginated_query_params = self.query_params
|
|
1137
|
-
# query hash, will be used to build a product id
|
|
1138
|
-
sorted_unpaginated_query_params = dict_items_recursive_sort(
|
|
1139
|
-
unpaginated_query_params
|
|
1140
|
-
)
|
|
1141
|
-
|
|
1142
|
-
# use all available query_params to parse properties
|
|
1143
|
-
result = dict(
|
|
1144
|
-
result,
|
|
1145
|
-
**sorted_unpaginated_query_params,
|
|
1146
|
-
qs=sorted_unpaginated_query_params,
|
|
1147
|
-
)
|
|
1132
|
+
sorted_unpaginated_qp = dict_items_recursive_sort(results.query_params)
|
|
1148
1133
|
|
|
1149
1134
|
# remove unwanted query params
|
|
1150
1135
|
for param in getattr(self.config, "remove_from_query", []):
|
|
1151
|
-
|
|
1136
|
+
sorted_unpaginated_qp.pop(param, None)
|
|
1152
1137
|
|
|
1153
|
-
|
|
1138
|
+
if result:
|
|
1139
|
+
properties = result
|
|
1140
|
+
properties.update(result.pop("request_params", None) or {})
|
|
1154
1141
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
# update result with product_type_def_params and search args if not None (and not auth)
|
|
1158
|
-
kwargs.pop("auth", None)
|
|
1159
|
-
result.update(results.product_type_def_params)
|
|
1160
|
-
result = dict(result, **{k: v for k, v in kwargs.items() if v is not None})
|
|
1142
|
+
properties = {k: v for k, v in properties.items() if not k.startswith("__")}
|
|
1161
1143
|
|
|
1162
|
-
|
|
1163
|
-
parsed_properties = properties_from_json(
|
|
1164
|
-
result,
|
|
1165
|
-
self.config.metadata_mapping,
|
|
1166
|
-
discovery_config=getattr(self.config, "discover_metadata", {}),
|
|
1167
|
-
)
|
|
1144
|
+
properties["geometry"] = properties.get("area") or DEFAULT_GEOMETRY
|
|
1168
1145
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
**{ecmwf_format(k): v for k, v in parsed_properties.items()},
|
|
1173
|
-
}
|
|
1146
|
+
start, end = ecmwf_temporal_to_eodag(properties)
|
|
1147
|
+
properties["startTimeFromAscendingNode"] = start
|
|
1148
|
+
properties["completionTimeFromAscendingNode"] = end
|
|
1174
1149
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1150
|
+
else:
|
|
1151
|
+
# use all available query_params to parse properties
|
|
1152
|
+
result_data: dict[str, Any] = {
|
|
1153
|
+
**results.product_type_def_params,
|
|
1154
|
+
**sorted_unpaginated_qp,
|
|
1155
|
+
**{"qs": sorted_unpaginated_qp},
|
|
1156
|
+
}
|
|
1177
1157
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1158
|
+
# update result with product_type_def_params and search args if not None (and not auth)
|
|
1159
|
+
kwargs.pop("auth", None)
|
|
1160
|
+
result_data.update(results.product_type_def_params)
|
|
1161
|
+
result_data = {
|
|
1162
|
+
**result_data,
|
|
1163
|
+
**{k: v for k, v in kwargs.items() if v is not None},
|
|
1164
|
+
}
|
|
1180
1165
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1166
|
+
properties = properties_from_json(
|
|
1167
|
+
result_data,
|
|
1168
|
+
self.config.metadata_mapping,
|
|
1169
|
+
discovery_config=getattr(self.config, "discover_metadata", {}),
|
|
1170
|
+
)
|
|
1183
1171
|
|
|
1184
|
-
|
|
1185
|
-
product_id += f"_{slugify(start)}"
|
|
1186
|
-
if end != NOT_AVAILABLE:
|
|
1187
|
-
product_id += f"_{slugify(end)}"
|
|
1172
|
+
query_hash = hashlib.sha1(str(result_data).encode("UTF-8")).hexdigest()
|
|
1188
1173
|
|
|
1189
|
-
|
|
1174
|
+
properties["title"] = properties["id"] = (
|
|
1175
|
+
(product_type or kwargs.get("dataset", self.provider)).upper()
|
|
1176
|
+
+ "_ORDERABLE_"
|
|
1177
|
+
+ query_hash
|
|
1178
|
+
)
|
|
1190
1179
|
|
|
1191
|
-
|
|
1180
|
+
qs = geojson.dumps(sorted_unpaginated_qp)
|
|
1192
1181
|
|
|
1193
1182
|
# used by server mode to generate downloadlink href
|
|
1183
|
+
# TODO: to remove once the legacy server is removed
|
|
1194
1184
|
properties["_dc_qs"] = quote_plus(qs)
|
|
1195
1185
|
|
|
1196
1186
|
product = EOProduct(
|
|
1197
1187
|
provider=self.provider,
|
|
1198
|
-
|
|
1199
|
-
|
|
1188
|
+
properties={ecmwf_format(k): v for k, v in properties.items()},
|
|
1189
|
+
**kwargs,
|
|
1200
1190
|
)
|
|
1201
1191
|
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1192
|
+
# backup original register_downloader to register_downloader_only
|
|
1193
|
+
product.register_downloader_only = product.register_downloader
|
|
1194
|
+
# patched register_downloader that will also update properties
|
|
1195
|
+
product.register_downloader = MethodType(patched_register_downloader, product)
|
|
1196
|
+
|
|
1197
|
+
return [product]
|
|
1205
1198
|
|
|
1206
1199
|
def count_hits(
|
|
1207
1200
|
self, count_url: Optional[str] = None, result_type: Optional[str] = None
|
|
@@ -1215,6 +1208,83 @@ class ECMWFSearch(PostJsonSearch):
|
|
|
1215
1208
|
return 1
|
|
1216
1209
|
|
|
1217
1210
|
|
|
1211
|
+
def _check_id(product: EOProduct) -> EOProduct:
|
|
1212
|
+
"""Check if the id is the one of an existing job.
|
|
1213
|
+
|
|
1214
|
+
If the job exists, poll it, otherwise, raise an error.
|
|
1215
|
+
|
|
1216
|
+
:param product: The product to check the id for
|
|
1217
|
+
:raises: :class:`~eodag.utils.exceptions.ValidationError`
|
|
1218
|
+
"""
|
|
1219
|
+
if not (product_id := product.search_kwargs.get("id")):
|
|
1220
|
+
return product
|
|
1221
|
+
|
|
1222
|
+
on_response_mm = getattr(product.downloader.config, "order_on_response", {}).get(
|
|
1223
|
+
"metadata_mapping", {}
|
|
1224
|
+
)
|
|
1225
|
+
if not on_response_mm:
|
|
1226
|
+
return product
|
|
1227
|
+
|
|
1228
|
+
logger.debug(f"Update product properties using given orderId {product_id}")
|
|
1229
|
+
on_response_mm_jsonpath = mtd_cfg_as_conversion_and_querypath(
|
|
1230
|
+
on_response_mm,
|
|
1231
|
+
)
|
|
1232
|
+
properties_update = properties_from_json(
|
|
1233
|
+
{}, {**on_response_mm_jsonpath, **{"orderId": (None, product_id)}}
|
|
1234
|
+
)
|
|
1235
|
+
product.properties.update(
|
|
1236
|
+
{k: v for k, v in properties_update.items() if v != NOT_AVAILABLE}
|
|
1237
|
+
)
|
|
1238
|
+
|
|
1239
|
+
auth = product.downloader_auth.authenticate() if product.downloader_auth else None
|
|
1240
|
+
|
|
1241
|
+
# try to poll the job corresponding to the given id
|
|
1242
|
+
try:
|
|
1243
|
+
product.downloader._order_status(product=product, auth=auth) # type: ignore
|
|
1244
|
+
# when a NotAvailableError is catched, it means the product is not ready and still needs to be polled
|
|
1245
|
+
except NotAvailableError:
|
|
1246
|
+
product.properties["storageStatus"] = STAGING_STATUS
|
|
1247
|
+
except Exception as e:
|
|
1248
|
+
if (
|
|
1249
|
+
isinstance(e, DownloadError) or isinstance(e, ValidationError)
|
|
1250
|
+
) and "order status could not be checked" in e.args[0]:
|
|
1251
|
+
raise ValidationError(
|
|
1252
|
+
f"Item {product_id} does not exist with {product.provider}."
|
|
1253
|
+
) from e
|
|
1254
|
+
raise ValidationError(e.args[0]) from e
|
|
1255
|
+
|
|
1256
|
+
# update product id
|
|
1257
|
+
product.properties["id"] = product_id
|
|
1258
|
+
# update product type if needed
|
|
1259
|
+
if product.product_type is None:
|
|
1260
|
+
product.product_type = product.properties.get("ecmwf:dataset")
|
|
1261
|
+
# update product title
|
|
1262
|
+
product.properties["title"] = (
|
|
1263
|
+
(product.product_type or product.provider).upper() + "_" + product_id
|
|
1264
|
+
)
|
|
1265
|
+
# use NOT_AVAILABLE as fallback product_type to avoid using guess_product_type
|
|
1266
|
+
if product.product_type is None:
|
|
1267
|
+
product.product_type = NOT_AVAILABLE
|
|
1268
|
+
|
|
1269
|
+
return product
|
|
1270
|
+
|
|
1271
|
+
|
|
1272
|
+
def patched_register_downloader(self, downloader, authenticator):
|
|
1273
|
+
"""Register product donwloader and update properties if searched by id.
|
|
1274
|
+
|
|
1275
|
+
:param self: product to which information should be added
|
|
1276
|
+
:param downloader: The download method that it can use
|
|
1277
|
+
:class:`~eodag.plugins.download.base.Download` or
|
|
1278
|
+
:class:`~eodag.plugins.api.base.Api`
|
|
1279
|
+
:param authenticator: The authentication method needed to perform the download
|
|
1280
|
+
:class:`~eodag.plugins.authentication.base.Authentication`
|
|
1281
|
+
"""
|
|
1282
|
+
# register downloader
|
|
1283
|
+
self.register_downloader_only(downloader, authenticator)
|
|
1284
|
+
# and also update properties
|
|
1285
|
+
_check_id(self)
|
|
1286
|
+
|
|
1287
|
+
|
|
1218
1288
|
class MeteoblueSearch(ECMWFSearch):
|
|
1219
1289
|
"""MeteoblueSearch search plugin.
|
|
1220
1290
|
|
|
@@ -1283,6 +1353,97 @@ class MeteoblueSearch(ECMWFSearch):
|
|
|
1283
1353
|
"""
|
|
1284
1354
|
return QueryStringSearch.build_query_string(self, product_type, query_dict)
|
|
1285
1355
|
|
|
1356
|
+
def normalize_results(self, results, **kwargs):
|
|
1357
|
+
"""Build :class:`~eodag.api.product._product.EOProduct` from provider result
|
|
1358
|
+
|
|
1359
|
+
:param results: Raw provider result as single dict in list
|
|
1360
|
+
:param kwargs: Search arguments
|
|
1361
|
+
:returns: list of single :class:`~eodag.api.product._product.EOProduct`
|
|
1362
|
+
"""
|
|
1363
|
+
|
|
1364
|
+
product_type = kwargs.get("productType")
|
|
1365
|
+
|
|
1366
|
+
result = results[0]
|
|
1367
|
+
|
|
1368
|
+
# datacube query string got from previous search
|
|
1369
|
+
_dc_qs = kwargs.pop("_dc_qs", None)
|
|
1370
|
+
if _dc_qs is not None:
|
|
1371
|
+
qs = unquote_plus(unquote_plus(_dc_qs))
|
|
1372
|
+
sorted_unpaginated_query_params = geojson.loads(qs)
|
|
1373
|
+
else:
|
|
1374
|
+
next_page_query_obj = orjson.loads(
|
|
1375
|
+
self.config.pagination["next_page_query_obj"].format()
|
|
1376
|
+
)
|
|
1377
|
+
unpaginated_query_params = {
|
|
1378
|
+
k: v
|
|
1379
|
+
for k, v in results.query_params.items()
|
|
1380
|
+
if (k, v) not in next_page_query_obj.items()
|
|
1381
|
+
}
|
|
1382
|
+
# query hash, will be used to build a product id
|
|
1383
|
+
sorted_unpaginated_query_params = dict_items_recursive_sort(
|
|
1384
|
+
unpaginated_query_params
|
|
1385
|
+
)
|
|
1386
|
+
|
|
1387
|
+
# use all available query_params to parse properties
|
|
1388
|
+
result = dict(
|
|
1389
|
+
result,
|
|
1390
|
+
**sorted_unpaginated_query_params,
|
|
1391
|
+
qs=sorted_unpaginated_query_params,
|
|
1392
|
+
)
|
|
1393
|
+
|
|
1394
|
+
qs = geojson.dumps(sorted_unpaginated_query_params)
|
|
1395
|
+
|
|
1396
|
+
query_hash = hashlib.sha1(str(qs).encode("UTF-8")).hexdigest()
|
|
1397
|
+
|
|
1398
|
+
# update result with product_type_def_params and search args if not None (and not auth)
|
|
1399
|
+
kwargs.pop("auth", None)
|
|
1400
|
+
result.update(results.product_type_def_params)
|
|
1401
|
+
result = dict(result, **{k: v for k, v in kwargs.items() if v is not None})
|
|
1402
|
+
|
|
1403
|
+
# parse properties
|
|
1404
|
+
parsed_properties = properties_from_json(
|
|
1405
|
+
result,
|
|
1406
|
+
self.config.metadata_mapping,
|
|
1407
|
+
discovery_config=getattr(self.config, "discover_metadata", {}),
|
|
1408
|
+
)
|
|
1409
|
+
|
|
1410
|
+
properties = {
|
|
1411
|
+
# use product_type_config as default properties
|
|
1412
|
+
**getattr(self.config, "product_type_config", {}),
|
|
1413
|
+
**{ecmwf_format(k): v for k, v in parsed_properties.items()},
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
def slugify(date_str: str) -> str:
|
|
1417
|
+
return date_str.split("T")[0].replace("-", "")
|
|
1418
|
+
|
|
1419
|
+
# build product id
|
|
1420
|
+
product_id = (product_type or self.provider).upper()
|
|
1421
|
+
|
|
1422
|
+
start = properties.get(START, NOT_AVAILABLE)
|
|
1423
|
+
end = properties.get(END, NOT_AVAILABLE)
|
|
1424
|
+
|
|
1425
|
+
if start != NOT_AVAILABLE:
|
|
1426
|
+
product_id += f"_{slugify(start)}"
|
|
1427
|
+
if end != NOT_AVAILABLE:
|
|
1428
|
+
product_id += f"_{slugify(end)}"
|
|
1429
|
+
|
|
1430
|
+
product_id += f"_{query_hash}"
|
|
1431
|
+
|
|
1432
|
+
properties["id"] = properties["title"] = product_id
|
|
1433
|
+
|
|
1434
|
+
# used by server mode to generate downloadlink href
|
|
1435
|
+
properties["_dc_qs"] = quote_plus(qs)
|
|
1436
|
+
|
|
1437
|
+
product = EOProduct(
|
|
1438
|
+
provider=self.provider,
|
|
1439
|
+
productType=product_type,
|
|
1440
|
+
properties=properties,
|
|
1441
|
+
)
|
|
1442
|
+
|
|
1443
|
+
return [
|
|
1444
|
+
product,
|
|
1445
|
+
]
|
|
1446
|
+
|
|
1286
1447
|
|
|
1287
1448
|
class WekeoECMWFSearch(ECMWFSearch):
|
|
1288
1449
|
"""
|
|
@@ -1319,6 +1480,10 @@ class WekeoECMWFSearch(ECMWFSearch):
|
|
|
1319
1480
|
:returns: list of single :class:`~eodag.api.product._product.EOProduct`
|
|
1320
1481
|
"""
|
|
1321
1482
|
|
|
1483
|
+
if kwargs.get("id") and "ORDERABLE" not in kwargs["id"]:
|
|
1484
|
+
# id is order id (only letters and numbers) -> use parent normalize results
|
|
1485
|
+
return super().normalize_results(results, **kwargs)
|
|
1486
|
+
|
|
1322
1487
|
# formating of orderLink requires access to the productType value.
|
|
1323
1488
|
results.data = [
|
|
1324
1489
|
{**result, **results.product_type_def_params} for result in results
|
|
@@ -1329,12 +1494,28 @@ class WekeoECMWFSearch(ECMWFSearch):
|
|
|
1329
1494
|
if not normalized:
|
|
1330
1495
|
return normalized
|
|
1331
1496
|
|
|
1332
|
-
|
|
1497
|
+
# remove unwanted query params
|
|
1498
|
+
excluded_query_params = getattr(self.config, "remove_from_query", [])
|
|
1499
|
+
filtered_query_params = {
|
|
1500
|
+
k: v
|
|
1501
|
+
for k, v in results.query_params.items()
|
|
1502
|
+
if k not in excluded_query_params
|
|
1503
|
+
}
|
|
1333
1504
|
for product in normalized:
|
|
1334
1505
|
properties = {**product.properties, **results.query_params}
|
|
1335
|
-
properties["_dc_qs"] =
|
|
1506
|
+
properties["_dc_qs"] = quote_plus(orjson.dumps(filtered_query_params))
|
|
1336
1507
|
product.properties = {ecmwf_format(k): v for k, v in properties.items()}
|
|
1337
1508
|
|
|
1509
|
+
# update product and title the same way as in parent class
|
|
1510
|
+
splitted_id = product.properties.get("title", "").split("-")
|
|
1511
|
+
dataset = "_".join(splitted_id[:-1])
|
|
1512
|
+
query_hash = splitted_id[-1]
|
|
1513
|
+
product.properties["title"] = product.properties["id"] = (
|
|
1514
|
+
(product.product_type or dataset or self.provider).upper()
|
|
1515
|
+
+ "_ORDERABLE_"
|
|
1516
|
+
+ query_hash
|
|
1517
|
+
)
|
|
1518
|
+
|
|
1338
1519
|
return normalized
|
|
1339
1520
|
|
|
1340
1521
|
def do_search(self, *args: Any, **kwargs: Any) -> list[dict[str, Any]]:
|
|
@@ -1344,4 +1525,9 @@ class WekeoECMWFSearch(ECMWFSearch):
|
|
|
1344
1525
|
:param kwargs: keyword arguments to be used in the search
|
|
1345
1526
|
:return: list containing the results from the provider in json format
|
|
1346
1527
|
"""
|
|
1347
|
-
|
|
1528
|
+
if "id" in kwargs and "ORDERABLE" not in kwargs["id"]:
|
|
1529
|
+
# id is order id (only letters and numbers) -> use parent normalize results.
|
|
1530
|
+
# No real search. We fake it all, then check order status using given id
|
|
1531
|
+
return [{}]
|
|
1532
|
+
else:
|
|
1533
|
+
return QueryStringSearch.do_search(self, *args, **kwargs)
|