eodag 2.12.0__py3-none-any.whl → 3.0.0b1__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 +434 -319
- eodag/api/product/__init__.py +5 -1
- eodag/api/product/_assets.py +7 -2
- eodag/api/product/_product.py +46 -68
- eodag/api/product/metadata_mapping.py +181 -66
- eodag/api/search_result.py +21 -1
- eodag/cli.py +20 -6
- eodag/config.py +95 -6
- eodag/plugins/apis/base.py +8 -162
- eodag/plugins/apis/ecmwf.py +36 -24
- eodag/plugins/apis/usgs.py +40 -24
- eodag/plugins/authentication/aws_auth.py +2 -2
- eodag/plugins/authentication/header.py +31 -6
- eodag/plugins/authentication/keycloak.py +13 -84
- eodag/plugins/authentication/oauth.py +3 -3
- eodag/plugins/authentication/openid_connect.py +256 -46
- eodag/plugins/authentication/qsauth.py +3 -0
- eodag/plugins/authentication/sas_auth.py +8 -1
- eodag/plugins/authentication/token.py +92 -46
- eodag/plugins/authentication/token_exchange.py +120 -0
- eodag/plugins/download/aws.py +86 -91
- eodag/plugins/download/base.py +72 -40
- eodag/plugins/download/http.py +607 -264
- eodag/plugins/download/s3rest.py +28 -15
- eodag/plugins/manager.py +73 -57
- eodag/plugins/search/__init__.py +36 -0
- eodag/plugins/search/base.py +225 -18
- eodag/plugins/search/build_search_result.py +389 -32
- eodag/plugins/search/cop_marine.py +378 -0
- eodag/plugins/search/creodias_s3.py +15 -14
- eodag/plugins/search/csw.py +5 -7
- eodag/plugins/search/data_request_search.py +44 -20
- eodag/plugins/search/qssearch.py +508 -203
- eodag/plugins/search/static_stac_search.py +99 -36
- eodag/resources/constraints/climate-dt.json +13 -0
- eodag/resources/constraints/extremes-dt.json +8 -0
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +1897 -34
- eodag/resources/providers.yml +3539 -3277
- eodag/resources/stac.yml +48 -54
- eodag/resources/stac_api.yml +71 -25
- eodag/resources/stac_provider.yml +5 -0
- eodag/resources/user_conf_template.yml +51 -3
- eodag/rest/__init__.py +6 -0
- eodag/rest/cache.py +70 -0
- eodag/rest/config.py +68 -0
- eodag/rest/constants.py +27 -0
- eodag/rest/core.py +757 -0
- eodag/rest/server.py +397 -258
- eodag/rest/stac.py +438 -307
- eodag/rest/types/collections_search.py +44 -0
- eodag/rest/types/eodag_search.py +232 -43
- eodag/rest/types/{stac_queryables.py → queryables.py} +81 -43
- eodag/rest/types/stac_search.py +277 -0
- eodag/rest/utils/__init__.py +216 -0
- eodag/rest/utils/cql_evaluate.py +119 -0
- eodag/rest/utils/rfc3339.py +65 -0
- eodag/types/__init__.py +99 -9
- eodag/types/bbox.py +15 -14
- eodag/types/download_args.py +31 -0
- eodag/types/search_args.py +58 -7
- eodag/types/whoosh.py +81 -0
- eodag/utils/__init__.py +72 -9
- eodag/utils/constraints.py +37 -37
- eodag/utils/exceptions.py +23 -17
- eodag/utils/requests.py +138 -0
- eodag/utils/rest.py +104 -0
- eodag/utils/stac_reader.py +100 -16
- {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/METADATA +64 -44
- eodag-3.0.0b1.dist-info/RECORD +109 -0
- {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/WHEEL +1 -1
- {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/entry_points.txt +6 -5
- eodag/plugins/apis/cds.py +0 -540
- eodag/rest/utils.py +0 -1133
- eodag-2.12.0.dist-info/RECORD +0 -94
- {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/LICENSE +0 -0
- {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/top_level.txt +0 -0
eodag/config.py
CHANGED
|
@@ -27,6 +27,7 @@ from typing import (
|
|
|
27
27
|
ItemsView,
|
|
28
28
|
Iterator,
|
|
29
29
|
List,
|
|
30
|
+
Literal,
|
|
30
31
|
Optional,
|
|
31
32
|
Tuple,
|
|
32
33
|
TypedDict,
|
|
@@ -40,13 +41,16 @@ import requests
|
|
|
40
41
|
import yaml
|
|
41
42
|
import yaml.constructor
|
|
42
43
|
import yaml.parser
|
|
44
|
+
from annotated_types import Gt
|
|
43
45
|
from jsonpath_ng import JSONPath
|
|
44
46
|
from pkg_resources import resource_filename
|
|
45
47
|
from requests.auth import AuthBase
|
|
48
|
+
from typing_extensions import Doc
|
|
46
49
|
|
|
47
50
|
from eodag.utils import (
|
|
48
51
|
HTTP_REQ_TIMEOUT,
|
|
49
52
|
USER_AGENT,
|
|
53
|
+
Annotated,
|
|
50
54
|
cached_yaml_load,
|
|
51
55
|
cached_yaml_load_all,
|
|
52
56
|
cast_scalar_value,
|
|
@@ -125,7 +129,11 @@ class ProviderConfig(yaml.YAMLObject):
|
|
|
125
129
|
"""
|
|
126
130
|
|
|
127
131
|
name: str
|
|
132
|
+
group: str
|
|
128
133
|
priority: int = 0 # Set default priority to 0
|
|
134
|
+
roles: List[str]
|
|
135
|
+
description: str
|
|
136
|
+
url: str
|
|
129
137
|
api: PluginConfig
|
|
130
138
|
search: PluginConfig
|
|
131
139
|
products: Dict[str, Any]
|
|
@@ -138,7 +146,7 @@ class ProviderConfig(yaml.YAMLObject):
|
|
|
138
146
|
yaml_tag = "!provider"
|
|
139
147
|
|
|
140
148
|
@classmethod
|
|
141
|
-
def from_yaml(cls, loader: yaml.Loader, node: Any) -> ProviderConfig:
|
|
149
|
+
def from_yaml(cls, loader: yaml.Loader, node: Any) -> Iterator[ProviderConfig]:
|
|
142
150
|
"""Build a :class:`~eodag.config.ProviderConfig` from Yaml"""
|
|
143
151
|
cls.validate(tuple(node_key.value for node_key, _ in node.value))
|
|
144
152
|
for node_key, node_value in node.value:
|
|
@@ -223,19 +231,77 @@ class PluginConfig(yaml.YAMLObject):
|
|
|
223
231
|
next_page_url_key_path: Union[str, JSONPath]
|
|
224
232
|
next_page_query_obj_key_path: Union[str, JSONPath]
|
|
225
233
|
next_page_merge_key_path: Union[str, JSONPath]
|
|
234
|
+
count_tpl: str
|
|
226
235
|
next_page_url_tpl: str
|
|
227
236
|
next_page_query_obj: str
|
|
228
237
|
count_endpoint: str
|
|
229
238
|
start_page: int
|
|
230
239
|
|
|
240
|
+
class Sort(TypedDict):
|
|
241
|
+
"""Configuration for sort during search"""
|
|
242
|
+
|
|
243
|
+
sort_by_default: List[Tuple[str, str]]
|
|
244
|
+
sort_by_tpl: str
|
|
245
|
+
sort_param_mapping: Dict[str, str]
|
|
246
|
+
sort_order_mapping: Dict[Literal["ascending", "descending"], str]
|
|
247
|
+
max_sort_params: Annotated[int, Gt(0)]
|
|
248
|
+
|
|
249
|
+
class OrderOnResponse(TypedDict):
|
|
250
|
+
"""Configuration for order on-response during download"""
|
|
251
|
+
|
|
252
|
+
metadata_mapping: Dict[str, Union[str, List[str]]]
|
|
253
|
+
|
|
254
|
+
class OrderStatusSuccess(TypedDict):
|
|
255
|
+
"""
|
|
256
|
+
Configuration to identify order status success during download
|
|
257
|
+
|
|
258
|
+
Order status response matching the following parameters are considered success
|
|
259
|
+
At least one is required
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
status: Annotated[str, Doc("Variable in the order status response json body")]
|
|
263
|
+
message: Annotated[str, Doc("Variable in the order status response json body")]
|
|
264
|
+
http_code: Annotated[int, Doc("HTTP code of the order status response")]
|
|
265
|
+
|
|
266
|
+
class OrderStatusOrdered(TypedDict):
|
|
267
|
+
"""
|
|
268
|
+
Configuration to identify order status ordered during download
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
http_code: Annotated[int, Doc("HTTP code of the order status response")]
|
|
272
|
+
|
|
273
|
+
class OrderStatusRequest(TypedDict):
|
|
274
|
+
"""
|
|
275
|
+
Order status request configuration
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
method: Annotated[str, Doc("Request HTTP method")]
|
|
279
|
+
headers: Annotated[Dict[str, Any], Doc("Request hearders")]
|
|
280
|
+
|
|
231
281
|
class OrderStatusOnSuccess(TypedDict):
|
|
232
|
-
"""Configuration for order on-success during download"""
|
|
282
|
+
"""Configuration for order status on-success during download"""
|
|
233
283
|
|
|
234
|
-
need_search: bool
|
|
284
|
+
need_search: Annotated[bool, Doc("If a new search is needed on success")]
|
|
235
285
|
result_type: str
|
|
236
286
|
results_entry: str
|
|
237
287
|
metadata_mapping: Dict[str, Union[str, List[str]]]
|
|
238
288
|
|
|
289
|
+
class OrderStatus(TypedDict):
|
|
290
|
+
"""Configuration for order status during download"""
|
|
291
|
+
|
|
292
|
+
request: PluginConfig.OrderStatusRequest
|
|
293
|
+
metadata_mapping: Annotated[
|
|
294
|
+
Dict[str, Union[str, List[str]]],
|
|
295
|
+
Doc("Metadata-mapping used to parse order status response"),
|
|
296
|
+
]
|
|
297
|
+
success: PluginConfig.OrderStatusSuccess
|
|
298
|
+
error: Annotated[
|
|
299
|
+
Dict[str, Any],
|
|
300
|
+
Doc("Part of the order status response that tells there is an error"),
|
|
301
|
+
]
|
|
302
|
+
ordered: PluginConfig.OrderStatusOrdered
|
|
303
|
+
on_success: PluginConfig.OrderStatusOnSuccess
|
|
304
|
+
|
|
239
305
|
name: str
|
|
240
306
|
type: str
|
|
241
307
|
|
|
@@ -251,12 +317,15 @@ class PluginConfig(yaml.YAMLObject):
|
|
|
251
317
|
result_type: str
|
|
252
318
|
results_entry: str
|
|
253
319
|
pagination: PluginConfig.Pagination
|
|
320
|
+
sort: PluginConfig.Sort
|
|
254
321
|
query_params_key: str
|
|
255
|
-
discover_metadata: Dict[str, str]
|
|
322
|
+
discover_metadata: Dict[str, Union[str, bool]]
|
|
256
323
|
discover_product_types: Dict[str, Any]
|
|
257
324
|
discover_queryables: Dict[str, Any]
|
|
258
325
|
metadata_mapping: Dict[str, Union[str, List[str]]]
|
|
259
326
|
free_params: Dict[Any, Any]
|
|
327
|
+
constraints_file_url: str
|
|
328
|
+
remove_from_queryables: List[str]
|
|
260
329
|
free_text_search_operations: Dict[str, Any] # ODataV4Search
|
|
261
330
|
metadata_pre_mapping: Dict[str, Any] # ODataV4Search
|
|
262
331
|
data_request_url: str # DataRequestSearch
|
|
@@ -268,16 +337,30 @@ class PluginConfig(yaml.YAMLObject):
|
|
|
268
337
|
max_connections: int # StaticStacSearch
|
|
269
338
|
timeout: float # StaticStacSearch
|
|
270
339
|
s3_bucket: str # CreodiasS3Search
|
|
340
|
+
end_date_excluded: bool # BuildSearchResult
|
|
341
|
+
remove_from_query: List[str] # BuildSearchResult
|
|
342
|
+
ssl_verify: bool
|
|
271
343
|
|
|
272
344
|
# download -------------------------------------------------------------------------
|
|
273
345
|
base_uri: str
|
|
274
346
|
outputs_prefix: str
|
|
275
347
|
extract: bool
|
|
348
|
+
outputs_extension: str
|
|
276
349
|
order_enabled: bool # HTTPDownload
|
|
277
350
|
order_method: str # HTTPDownload
|
|
278
351
|
order_headers: Dict[str, str] # HTTPDownload
|
|
279
|
-
|
|
352
|
+
|
|
353
|
+
order_on_response: PluginConfig.OrderOnResponse
|
|
354
|
+
order_status: PluginConfig.OrderStatus
|
|
355
|
+
no_auth_download: Annotated[
|
|
356
|
+
bool,
|
|
357
|
+
Doc(
|
|
358
|
+
"Do not authenticate the download request but only the order and order status ones."
|
|
359
|
+
),
|
|
360
|
+
]
|
|
280
361
|
bucket_path_level: int # S3RestDownload
|
|
362
|
+
requester_pays: bool # AwsDownload
|
|
363
|
+
flatten_top_dirs: bool
|
|
281
364
|
|
|
282
365
|
# auth -----------------------------------------------------------------------------
|
|
283
366
|
credentials: Dict[str, str]
|
|
@@ -299,7 +382,13 @@ class PluginConfig(yaml.YAMLObject):
|
|
|
299
382
|
token_exchange_post_data_method: str # OIDCAuthorizationCodeFlowAuth
|
|
300
383
|
token_uri: str # OIDCAuthorizationCodeFlowAuth
|
|
301
384
|
token_key: str # OIDCAuthorizationCodeFlowAuth
|
|
385
|
+
req_data: Dict[str, Any] # TokenAuth
|
|
302
386
|
signed_url_key: str # SASAuth
|
|
387
|
+
refresh_uri: str # TokenAuth
|
|
388
|
+
refresh_token_key: str # TokenAuth
|
|
389
|
+
subject: Dict[str, Any] # TokenExchangeAuth
|
|
390
|
+
subject_issuer: str # TokenExchangeAuth
|
|
391
|
+
audience: str # TokenExchangeAuth
|
|
303
392
|
|
|
304
393
|
yaml_loader = yaml.Loader
|
|
305
394
|
yaml_dumper = yaml.SafeDumper
|
|
@@ -362,7 +451,7 @@ def load_config(config_path: str) -> Dict[str, ProviderConfig]:
|
|
|
362
451
|
:returns: The default provider's configuration
|
|
363
452
|
:rtype: dict
|
|
364
453
|
"""
|
|
365
|
-
logger.debug(
|
|
454
|
+
logger.debug("Loading configuration from %s", config_path)
|
|
366
455
|
config: Dict[str, ProviderConfig] = {}
|
|
367
456
|
try:
|
|
368
457
|
# Providers configs are stored in this file as separated yaml documents
|
eodag/plugins/apis/base.py
CHANGED
|
@@ -17,34 +17,16 @@
|
|
|
17
17
|
# limitations under the License.
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
import
|
|
21
|
-
from
|
|
20
|
+
from eodag.plugins.download.base import Download
|
|
21
|
+
from eodag.plugins.search.base import Search
|
|
22
22
|
|
|
23
|
-
from pydantic.fields import Field, FieldInfo
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
from eodag.api.product import EOProduct
|
|
27
|
-
from eodag.api.search_result import SearchResult
|
|
28
|
-
from eodag.config import PluginConfig
|
|
29
|
-
from eodag.utils import DownloadedCallback, ProgressCallback
|
|
30
|
-
|
|
31
|
-
from eodag.plugins.base import PluginTopic
|
|
32
|
-
from eodag.utils import (
|
|
33
|
-
DEFAULT_DOWNLOAD_TIMEOUT,
|
|
34
|
-
DEFAULT_DOWNLOAD_WAIT,
|
|
35
|
-
DEFAULT_ITEMS_PER_PAGE,
|
|
36
|
-
DEFAULT_PAGE,
|
|
37
|
-
Annotated,
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
logger = logging.getLogger("eodag.apis.base")
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class Api(PluginTopic):
|
|
24
|
+
class Api(Search, Download):
|
|
44
25
|
"""Plugins API Base plugin
|
|
45
26
|
|
|
46
|
-
An Api plugin
|
|
27
|
+
An Api plugin inherit the methods from Search and Download plugins.
|
|
47
28
|
|
|
29
|
+
There are three methods that it must implement:
|
|
48
30
|
- ``query``: search for products
|
|
49
31
|
- ``download``: download a single :class:`~eodag.api.product._product.EOProduct`
|
|
50
32
|
- ``download_all``: download multiple products from a :class:`~eodag.api.search_result.SearchResult`
|
|
@@ -62,148 +44,12 @@ class Api(PluginTopic):
|
|
|
62
44
|
(e.g. 'file:///tmp/product_folder' on Linux or
|
|
63
45
|
'file:///C:/Users/username/AppData/LOcal/Temp' on Windows)
|
|
64
46
|
- save a *record* file in the directory ``outputs_prefix/.downloaded`` whose name
|
|
65
|
-
is built on the MD5 hash of the product's ``
|
|
66
|
-
(``hashlib.md5(
|
|
67
|
-
the product's ``remote_location`` attribute itself.
|
|
47
|
+
is built on the MD5 hash of the product's ``product_type`` and ``properties['id']``
|
|
48
|
+
attributes (``hashlib.md5((product.product_type+"-"+product.properties['id']).encode("utf-8")).hexdigest()``)
|
|
49
|
+
and whose content is the product's ``remote_location`` attribute itself.
|
|
68
50
|
- not try to download a product whose ``location`` attribute already points to an
|
|
69
51
|
existing file/directory
|
|
70
52
|
- not try to download a product if its *record* file exists as long as the expected
|
|
71
53
|
product's file/directory. If the *record* file only is found, it must be deleted
|
|
72
54
|
(it certainly indicates that the download didn't complete)
|
|
73
55
|
"""
|
|
74
|
-
|
|
75
|
-
def clear(self) -> None:
|
|
76
|
-
"""Method used to clear a search context between two searches."""
|
|
77
|
-
pass
|
|
78
|
-
|
|
79
|
-
def query(
|
|
80
|
-
self,
|
|
81
|
-
product_type: Optional[str] = None,
|
|
82
|
-
items_per_page: int = DEFAULT_ITEMS_PER_PAGE,
|
|
83
|
-
page: int = DEFAULT_PAGE,
|
|
84
|
-
count: bool = True,
|
|
85
|
-
**kwargs: Any,
|
|
86
|
-
) -> Tuple[List[EOProduct], Optional[int]]:
|
|
87
|
-
"""Implementation of how the products must be searched goes here.
|
|
88
|
-
|
|
89
|
-
This method must return a tuple with (1) a list of EOProduct instances (see eodag.api.product module)
|
|
90
|
-
which will be processed by a Download plugin (2) and the total number of products matching
|
|
91
|
-
the search criteria. If ``count`` is False, the second element returned must be ``None``.
|
|
92
|
-
"""
|
|
93
|
-
raise NotImplementedError("A Api plugin must implement a method named query")
|
|
94
|
-
|
|
95
|
-
def discover_product_types(self) -> Optional[Dict[str, Any]]:
|
|
96
|
-
"""Fetch product types list from provider using `discover_product_types` conf"""
|
|
97
|
-
return None
|
|
98
|
-
|
|
99
|
-
def discover_queryables(
|
|
100
|
-
self, **kwargs: Any
|
|
101
|
-
) -> Optional[Dict[str, Annotated[Any, FieldInfo]]]:
|
|
102
|
-
"""Fetch queryables list from provider using `discover_queryables` conf
|
|
103
|
-
|
|
104
|
-
:param kwargs: additional filters for queryables (`productType` and other search
|
|
105
|
-
arguments)
|
|
106
|
-
:type kwargs: Any
|
|
107
|
-
:returns: fetched queryable parameters dict
|
|
108
|
-
:rtype: Optional[Dict[str, Annotated[Any, FieldInfo]]]
|
|
109
|
-
"""
|
|
110
|
-
return None
|
|
111
|
-
|
|
112
|
-
def get_defaults_as_queryables(
|
|
113
|
-
self, product_type: str
|
|
114
|
-
) -> Dict[str, Annotated[Any, FieldInfo]]:
|
|
115
|
-
"""
|
|
116
|
-
Return given product type defaut settings as queryables
|
|
117
|
-
|
|
118
|
-
:param product_type: given product type
|
|
119
|
-
:type product_type: str
|
|
120
|
-
:returns: queryable parameters dict
|
|
121
|
-
:rtype: Dict[str, Annotated[Any, FieldInfo]]
|
|
122
|
-
"""
|
|
123
|
-
defaults = self.config.products.get(product_type, {})
|
|
124
|
-
queryables = {}
|
|
125
|
-
for parameter, value in defaults.items():
|
|
126
|
-
queryables[parameter] = Annotated[type(value), Field(default=value)]
|
|
127
|
-
return queryables
|
|
128
|
-
|
|
129
|
-
def download(
|
|
130
|
-
self,
|
|
131
|
-
product: EOProduct,
|
|
132
|
-
auth: Optional[PluginConfig] = None,
|
|
133
|
-
progress_callback: Optional[ProgressCallback] = None,
|
|
134
|
-
wait: int = DEFAULT_DOWNLOAD_WAIT,
|
|
135
|
-
timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
136
|
-
**kwargs: Any,
|
|
137
|
-
) -> Optional[str]:
|
|
138
|
-
"""
|
|
139
|
-
Base download method. Not available, it must be defined for each plugin.
|
|
140
|
-
|
|
141
|
-
:param product: The EO product to download
|
|
142
|
-
:type product: :class:`~eodag.api.product._product.EOProduct`
|
|
143
|
-
:param auth: (optional) The configuration of a plugin of type Authentication
|
|
144
|
-
:type auth: :class:`~eodag.config.PluginConfig`
|
|
145
|
-
:param progress_callback: (optional) A progress callback
|
|
146
|
-
:type progress_callback: :class:`~eodag.utils.ProgressCallback`
|
|
147
|
-
:param wait: (optional) If download fails, wait time in minutes between two download tries
|
|
148
|
-
:type wait: int
|
|
149
|
-
:param timeout: (optional) If download fails, maximum time in minutes before stop retrying
|
|
150
|
-
to download
|
|
151
|
-
:type timeout: int
|
|
152
|
-
:param kwargs: `outputs_prefix` (str), `extract` (bool), `delete_archive` (bool)
|
|
153
|
-
and `dl_url_params` (dict) can be provided as additional kwargs
|
|
154
|
-
and will override any other values defined in a configuration
|
|
155
|
-
file or with environment variables.
|
|
156
|
-
:type kwargs: Union[str, bool, dict]
|
|
157
|
-
:returns: The absolute path to the downloaded product in the local filesystem
|
|
158
|
-
(e.g. '/tmp/product.zip' on Linux or
|
|
159
|
-
'C:\\Users\\username\\AppData\\Local\\Temp\\product.zip' on Windows)
|
|
160
|
-
:rtype: str
|
|
161
|
-
"""
|
|
162
|
-
raise NotImplementedError(
|
|
163
|
-
"An Api plugin must implement a method named download"
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
def download_all(
|
|
167
|
-
self,
|
|
168
|
-
products: SearchResult,
|
|
169
|
-
auth: Optional[PluginConfig] = None,
|
|
170
|
-
downloaded_callback: Optional[DownloadedCallback] = None,
|
|
171
|
-
progress_callback: Optional[ProgressCallback] = None,
|
|
172
|
-
wait: int = DEFAULT_DOWNLOAD_WAIT,
|
|
173
|
-
timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
174
|
-
**kwargs: Any,
|
|
175
|
-
) -> List[str]:
|
|
176
|
-
"""
|
|
177
|
-
Base download_all method.
|
|
178
|
-
|
|
179
|
-
:param products: Products to download
|
|
180
|
-
:type products: :class:`~eodag.api.search_result.SearchResult`
|
|
181
|
-
:param auth: (optional) The configuration of a plugin of type Authentication
|
|
182
|
-
:type auth: :class:`~eodag.config.PluginConfig`
|
|
183
|
-
:param downloaded_callback: (optional) A method or a callable object which takes
|
|
184
|
-
as parameter the ``product``. You can use the base class
|
|
185
|
-
:class:`~eodag.api.product.DownloadedCallback` and override
|
|
186
|
-
its ``__call__`` method. Will be called each time a product
|
|
187
|
-
finishes downloading
|
|
188
|
-
:type downloaded_callback: Callable[[:class:`~eodag.api.product._product.EOProduct`], None]
|
|
189
|
-
or None
|
|
190
|
-
:param progress_callback: (optional) A progress callback
|
|
191
|
-
:type progress_callback: :class:`~eodag.utils.ProgressCallback`
|
|
192
|
-
:param wait: (optional) If download fails, wait time in minutes between two download tries
|
|
193
|
-
:type wait: int
|
|
194
|
-
:param timeout: (optional) If download fails, maximum time in minutes before stop retrying
|
|
195
|
-
to download
|
|
196
|
-
:type timeout: int
|
|
197
|
-
:param kwargs: `outputs_prefix` (str), `extract` (bool), `delete_archive` (bool)
|
|
198
|
-
and `dl_url_params` (dict) can be provided as additional kwargs
|
|
199
|
-
and will override any other values defined in a configuration
|
|
200
|
-
file or with environment variables.
|
|
201
|
-
:type kwargs: Union[str, bool, dict]
|
|
202
|
-
:returns: List of absolute paths to the downloaded products in the local
|
|
203
|
-
filesystem (e.g. ``['/tmp/product.zip']`` on Linux or
|
|
204
|
-
``['C:\\Users\\username\\AppData\\Local\\Temp\\product.zip']`` on Windows)
|
|
205
|
-
:rtype: list
|
|
206
|
-
"""
|
|
207
|
-
raise NotImplementedError(
|
|
208
|
-
"A Api plugin must implement a method named download_all"
|
|
209
|
-
)
|
eodag/plugins/apis/ecmwf.py
CHANGED
|
@@ -18,42 +18,47 @@
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
20
|
import logging
|
|
21
|
-
|
|
22
|
-
from
|
|
21
|
+
import os
|
|
22
|
+
from datetime import datetime, timezone
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
23
24
|
|
|
24
25
|
import geojson
|
|
25
26
|
from ecmwfapi import ECMWFDataServer, ECMWFService
|
|
26
27
|
from ecmwfapi.api import APIException, Connection, get_apikey_values
|
|
27
28
|
|
|
28
29
|
from eodag.plugins.apis.base import Api
|
|
29
|
-
from eodag.plugins.
|
|
30
|
+
from eodag.plugins.search import PreparedSearch
|
|
30
31
|
from eodag.plugins.search.base import Search
|
|
31
32
|
from eodag.plugins.search.build_search_result import BuildPostSearchResult
|
|
32
|
-
from eodag.rest.stac import DEFAULT_MISSION_START_DATE
|
|
33
33
|
from eodag.utils import (
|
|
34
34
|
DEFAULT_DOWNLOAD_TIMEOUT,
|
|
35
35
|
DEFAULT_DOWNLOAD_WAIT,
|
|
36
|
-
|
|
37
|
-
DEFAULT_PAGE,
|
|
36
|
+
DEFAULT_MISSION_START_DATE,
|
|
38
37
|
get_geometry_from_various,
|
|
39
38
|
path_to_uri,
|
|
39
|
+
sanitize,
|
|
40
40
|
urlsplit,
|
|
41
41
|
)
|
|
42
42
|
from eodag.utils.exceptions import AuthenticationError, DownloadError
|
|
43
43
|
from eodag.utils.logging import get_logging_verbose
|
|
44
44
|
|
|
45
45
|
if TYPE_CHECKING:
|
|
46
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
47
|
+
|
|
48
|
+
from requests.auth import AuthBase
|
|
49
|
+
|
|
46
50
|
from eodag.api.product import EOProduct
|
|
47
51
|
from eodag.api.search_result import SearchResult
|
|
48
52
|
from eodag.config import PluginConfig
|
|
49
|
-
from eodag.
|
|
53
|
+
from eodag.types.download_args import DownloadConf
|
|
54
|
+
from eodag.utils import DownloadedCallback, ProgressCallback, Unpack
|
|
50
55
|
|
|
51
56
|
logger = logging.getLogger("eodag.apis.ecmwf")
|
|
52
57
|
|
|
53
58
|
ECMWF_MARS_KNOWN_FORMATS = {"grib": "grib", "netcdf": "nc"}
|
|
54
59
|
|
|
55
60
|
|
|
56
|
-
class EcmwfApi(
|
|
61
|
+
class EcmwfApi(Api, BuildPostSearchResult):
|
|
57
62
|
"""A plugin that enables to build download-request and download data on ECMWF MARS.
|
|
58
63
|
|
|
59
64
|
Builds a single ready-to-download :class:`~eodag.api.product._product.EOProduct`
|
|
@@ -84,10 +89,7 @@ class EcmwfApi(Download, Api, BuildPostSearchResult):
|
|
|
84
89
|
|
|
85
90
|
def query(
|
|
86
91
|
self,
|
|
87
|
-
|
|
88
|
-
items_per_page: int = DEFAULT_ITEMS_PER_PAGE,
|
|
89
|
-
page: int = DEFAULT_PAGE,
|
|
90
|
-
count: bool = True,
|
|
92
|
+
prep: PreparedSearch = PreparedSearch(),
|
|
91
93
|
**kwargs: Any,
|
|
92
94
|
) -> Tuple[List[EOProduct], Optional[int]]:
|
|
93
95
|
"""Build ready-to-download SearchResult"""
|
|
@@ -112,7 +114,7 @@ class EcmwfApi(Download, Api, BuildPostSearchResult):
|
|
|
112
114
|
if "completionTimeFromAscendingNode" not in kwargs:
|
|
113
115
|
kwargs["completionTimeFromAscendingNode"] = getattr(
|
|
114
116
|
self.config, "product_type_config", {}
|
|
115
|
-
).get("missionEndDate", None) or datetime.
|
|
117
|
+
).get("missionEndDate", None) or datetime.now(timezone.utc).isoformat(
|
|
116
118
|
timespec="seconds"
|
|
117
119
|
)
|
|
118
120
|
|
|
@@ -120,9 +122,7 @@ class EcmwfApi(Download, Api, BuildPostSearchResult):
|
|
|
120
122
|
if "geometry" in kwargs:
|
|
121
123
|
kwargs["geometry"] = get_geometry_from_various(geometry=kwargs["geometry"])
|
|
122
124
|
|
|
123
|
-
return BuildPostSearchResult.query(
|
|
124
|
-
self, items_per_page=items_per_page, page=page, count=count, **kwargs
|
|
125
|
-
)
|
|
125
|
+
return BuildPostSearchResult.query(self, prep, **kwargs)
|
|
126
126
|
|
|
127
127
|
def authenticate(self) -> Dict[str, Optional[str]]:
|
|
128
128
|
"""Check credentials and returns information needed for auth
|
|
@@ -156,21 +156,23 @@ class EcmwfApi(Download, Api, BuildPostSearchResult):
|
|
|
156
156
|
def download(
|
|
157
157
|
self,
|
|
158
158
|
product: EOProduct,
|
|
159
|
-
auth: Optional[
|
|
159
|
+
auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
|
|
160
160
|
progress_callback: Optional[ProgressCallback] = None,
|
|
161
161
|
wait: int = DEFAULT_DOWNLOAD_WAIT,
|
|
162
162
|
timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
163
|
-
**kwargs:
|
|
163
|
+
**kwargs: Unpack[DownloadConf],
|
|
164
164
|
) -> Optional[str]:
|
|
165
165
|
"""Download data from ECMWF MARS"""
|
|
166
166
|
product_format = product.properties.get("format", "grib")
|
|
167
167
|
product_extension = ECMWF_MARS_KNOWN_FORMATS.get(product_format, product_format)
|
|
168
|
+
kwargs["outputs_extension"] = kwargs.get(
|
|
169
|
+
"outputs_extension", f".{product_extension}"
|
|
170
|
+
)
|
|
168
171
|
|
|
169
172
|
# Prepare download
|
|
170
173
|
fs_path, record_filename = self._prepare_download(
|
|
171
174
|
product,
|
|
172
175
|
progress_callback=progress_callback,
|
|
173
|
-
outputs_extension=f".{product_extension}",
|
|
174
176
|
**kwargs,
|
|
175
177
|
)
|
|
176
178
|
|
|
@@ -179,6 +181,13 @@ class EcmwfApi(Download, Api, BuildPostSearchResult):
|
|
|
179
181
|
product.location = path_to_uri(fs_path)
|
|
180
182
|
return fs_path
|
|
181
183
|
|
|
184
|
+
new_fs_path = os.path.join(
|
|
185
|
+
os.path.dirname(fs_path), sanitize(product.properties["title"])
|
|
186
|
+
)
|
|
187
|
+
if not os.path.isdir(new_fs_path):
|
|
188
|
+
os.makedirs(new_fs_path)
|
|
189
|
+
fs_path = os.path.join(new_fs_path, os.path.basename(fs_path))
|
|
190
|
+
|
|
182
191
|
# get download request dict from product.location/downloadLink url query string
|
|
183
192
|
# separate url & parameters
|
|
184
193
|
download_request = geojson.loads(urlsplit(product.location).query)
|
|
@@ -222,13 +231,12 @@ class EcmwfApi(Download, Api, BuildPostSearchResult):
|
|
|
222
231
|
fh.write(product.properties["downloadLink"])
|
|
223
232
|
logger.debug("Download recorded in %s", record_filename)
|
|
224
233
|
|
|
225
|
-
# do not try to extract
|
|
234
|
+
# do not try to extract a directory
|
|
226
235
|
kwargs["extract"] = False
|
|
227
236
|
|
|
228
237
|
product_path = self._finalize(
|
|
229
|
-
|
|
238
|
+
new_fs_path,
|
|
230
239
|
progress_callback=progress_callback,
|
|
231
|
-
outputs_extension=f".{product_extension}",
|
|
232
240
|
**kwargs,
|
|
233
241
|
)
|
|
234
242
|
product.location = path_to_uri(product_path)
|
|
@@ -237,12 +245,12 @@ class EcmwfApi(Download, Api, BuildPostSearchResult):
|
|
|
237
245
|
def download_all(
|
|
238
246
|
self,
|
|
239
247
|
products: SearchResult,
|
|
240
|
-
auth: Optional[
|
|
248
|
+
auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
|
|
241
249
|
downloaded_callback: Optional[DownloadedCallback] = None,
|
|
242
250
|
progress_callback: Optional[ProgressCallback] = None,
|
|
243
251
|
wait: int = DEFAULT_DOWNLOAD_WAIT,
|
|
244
252
|
timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
245
|
-
**kwargs:
|
|
253
|
+
**kwargs: Unpack[DownloadConf],
|
|
246
254
|
) -> List[str]:
|
|
247
255
|
"""
|
|
248
256
|
Download all using parent (base plugin) method
|
|
@@ -256,3 +264,7 @@ class EcmwfApi(Download, Api, BuildPostSearchResult):
|
|
|
256
264
|
timeout=timeout,
|
|
257
265
|
**kwargs,
|
|
258
266
|
)
|
|
267
|
+
|
|
268
|
+
def clear(self) -> None:
|
|
269
|
+
"""Clear search context"""
|
|
270
|
+
pass
|