eodag 3.10.0__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.
Files changed (68) hide show
  1. eodag/api/core.py +378 -419
  2. eodag/api/product/__init__.py +3 -3
  3. eodag/api/product/_product.py +68 -40
  4. eodag/api/product/drivers/__init__.py +3 -5
  5. eodag/api/product/drivers/base.py +1 -18
  6. eodag/api/product/metadata_mapping.py +151 -215
  7. eodag/api/search_result.py +13 -7
  8. eodag/cli.py +72 -395
  9. eodag/config.py +46 -50
  10. eodag/plugins/apis/base.py +2 -2
  11. eodag/plugins/apis/ecmwf.py +20 -21
  12. eodag/plugins/apis/usgs.py +37 -33
  13. eodag/plugins/authentication/base.py +1 -3
  14. eodag/plugins/crunch/filter_date.py +3 -3
  15. eodag/plugins/crunch/filter_latest_intersect.py +2 -2
  16. eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
  17. eodag/plugins/download/aws.py +45 -41
  18. eodag/plugins/download/base.py +13 -14
  19. eodag/plugins/download/http.py +65 -65
  20. eodag/plugins/manager.py +28 -29
  21. eodag/plugins/search/__init__.py +3 -4
  22. eodag/plugins/search/base.py +128 -77
  23. eodag/plugins/search/build_search_result.py +105 -107
  24. eodag/plugins/search/cop_marine.py +44 -47
  25. eodag/plugins/search/csw.py +33 -33
  26. eodag/plugins/search/qssearch.py +335 -354
  27. eodag/plugins/search/stac_list_assets.py +1 -1
  28. eodag/plugins/search/static_stac_search.py +31 -31
  29. eodag/resources/{product_types.yml → collections.yml} +2353 -2429
  30. eodag/resources/ext_collections.json +1 -1
  31. eodag/resources/providers.yml +2427 -2719
  32. eodag/resources/stac_provider.yml +46 -90
  33. eodag/types/queryables.py +55 -91
  34. eodag/types/search_args.py +1 -1
  35. eodag/utils/__init__.py +94 -21
  36. eodag/utils/exceptions.py +6 -6
  37. eodag/utils/free_text_search.py +3 -3
  38. {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/METADATA +10 -87
  39. eodag-4.0.0a1.dist-info/RECORD +92 -0
  40. {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/entry_points.txt +0 -4
  41. eodag/plugins/authentication/oauth.py +0 -60
  42. eodag/plugins/download/creodias_s3.py +0 -71
  43. eodag/plugins/download/s3rest.py +0 -351
  44. eodag/plugins/search/data_request_search.py +0 -565
  45. eodag/resources/stac.yml +0 -294
  46. eodag/resources/stac_api.yml +0 -2105
  47. eodag/rest/__init__.py +0 -24
  48. eodag/rest/cache.py +0 -70
  49. eodag/rest/config.py +0 -67
  50. eodag/rest/constants.py +0 -26
  51. eodag/rest/core.py +0 -764
  52. eodag/rest/errors.py +0 -210
  53. eodag/rest/server.py +0 -604
  54. eodag/rest/server.wsgi +0 -6
  55. eodag/rest/stac.py +0 -1032
  56. eodag/rest/templates/README +0 -1
  57. eodag/rest/types/__init__.py +0 -18
  58. eodag/rest/types/collections_search.py +0 -44
  59. eodag/rest/types/eodag_search.py +0 -386
  60. eodag/rest/types/queryables.py +0 -174
  61. eodag/rest/types/stac_search.py +0 -272
  62. eodag/rest/utils/__init__.py +0 -207
  63. eodag/rest/utils/cql_evaluate.py +0 -119
  64. eodag/rest/utils/rfc3339.py +0 -64
  65. eodag-3.10.0.dist-info/RECORD +0 -116
  66. {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/WHEEL +0 -0
  67. {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/licenses/LICENSE +0 -0
  68. {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/top_level.txt +0 -0
@@ -1,565 +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 logging
21
- import time
22
- from datetime import datetime, timedelta, timezone
23
- from typing import TYPE_CHECKING, Any, Optional, cast
24
-
25
- import requests
26
-
27
- from eodag import EOProduct
28
- from eodag.api.product.metadata_mapping import (
29
- format_query_params,
30
- mtd_cfg_as_conversion_and_querypath,
31
- properties_from_json,
32
- )
33
- from eodag.plugins.search import PreparedSearch
34
- from eodag.plugins.search.base import Search
35
- from eodag.types.queryables import Queryables
36
- from eodag.utils import (
37
- DEFAULT_ITEMS_PER_PAGE,
38
- DEFAULT_MISSION_START_DATE,
39
- DEFAULT_PAGE,
40
- DEFAULT_SEARCH_TIMEOUT,
41
- GENERIC_PRODUCT_TYPE,
42
- HTTP_REQ_TIMEOUT,
43
- USER_AGENT,
44
- _deprecated,
45
- deepcopy,
46
- string_to_jsonpath,
47
- )
48
- from eodag.utils.exceptions import (
49
- NotAvailableError,
50
- RequestError,
51
- TimeOutError,
52
- ValidationError,
53
- )
54
-
55
- if TYPE_CHECKING:
56
- from eodag.config import PluginConfig
57
-
58
- logger = logging.getLogger("eodag.search.data_request_search")
59
-
60
-
61
- @_deprecated(
62
- reason="Plugin that was used in previous wekeo provider configuration, but not used anymore",
63
- version="3.7.1",
64
- )
65
- class DataRequestSearch(Search):
66
- """
67
- Plugin to execute search requests composed of several steps:
68
-
69
- #. do a data request which defines which data shall be searched
70
- #. check the status of the request job
71
- #. if finished - fetch the result of the job
72
-
73
- :param provider: provider name
74
- :param config: Search plugin configuration:
75
-
76
- * :attr:`~eodag.config.PluginConfig.api_endpoint` (``str``) (**mandatory**): The endpoint of the
77
- provider's search interface
78
- * :attr:`~eodag.config.PluginConfig.results_entry` (``str``) (**mandatory**): The name of
79
- the key in the provider search result that gives access to the result entries
80
- * :attr:`~eodag.config.PluginConfig.data_request_url` (``str``) (**mandatory**): url
81
- to which the data request shall be sent
82
- * :attr:`~eodag.config.PluginConfig.status_url` (``str``) (**mandatory**): url to fetch
83
- the status of the data request
84
- * :attr:`~eodag.config.PluginConfig.result_url` (``str``) (**mandatory**): url to fetch
85
- the search result when the data request is done
86
- * :attr:`~eodag.config.PluginConfig.need_auth` (``bool``): if authentication is needed for
87
- the search request; default: ``False``
88
- * :attr:`~eodag.config.PluginConfig.auth_error_code` (``int``): which error code is returned in case of an
89
- authentication error; only used if ``need_auth=true``
90
- * :attr:`~eodag.config.PluginConfig.ssl_verify` (``bool``): if the ssl certificates should be
91
- verified in requests; default: ``True``
92
- * :attr:`~eodag.config.PluginConfig.timeout` (``int``): time to wait until request timeout in seconds;
93
- default: ``5``
94
- * :attr:`~eodag.config.PluginConfig.dates_required` (``bool``): if date parameters are mandatory
95
- in the request; default: ``True``
96
- * :attr:`~eodag.config.PluginConfig.pagination` (:class:`~eodag.config.PluginConfig.Pagination`)
97
- (**mandatory**): The configuration of how the pagination is done on the provider. It is a tree with the
98
- following nodes:
99
-
100
- * :attr:`~eodag.config.PluginConfig.Pagination.total_items_nb_key_path` (``str``): An XPath or JsonPath
101
- leading to the total number of results satisfying a request. This is used for providers which provides the
102
- total results metadata along with the result of the query and don't have an endpoint for querying
103
- the number of items satisfying a request, or for providers for which the count endpoint
104
- returns a json or xml document
105
- * :attr:`~eodag.config.PluginConfig.Pagination.max_items_per_page` (``int``): The maximum
106
- number of items per page that the provider can handle; default: ``50``
107
- * :attr:`~eodag.config.PluginConfig.Pagination.start_page` (``int``): number of the
108
- first page; default: ``1``
109
-
110
- * :attr:`~eodag.config.PluginConfig.discover_product_types`
111
- (:class:`~eodag.config.PluginConfig.DiscoverProductTypes`): configuration for product type discovery based on
112
- information from the provider; It contains the keys:
113
-
114
- * :attr:`~eodag.config.PluginConfig.DiscoverProductTypes.fetch_url` (``str``) (**mandatory**): url from which
115
- the product types can be fetched
116
- * :attr:`~eodag.config.PluginConfig.DiscoverProductTypes.result_type` (``str``): type of the provider result;
117
- currently only ``json`` is supported (other types could be used in an extension of this plugin)
118
- * :attr:`~eodag.config.PluginConfig.DiscoverProductTypes.results_entry` (``str``) (**mandatory**): json path
119
- to the list of product types
120
- * :attr:`~eodag.config.PluginConfig.DiscoverProductTypes.generic_product_type_id` (``str``): mapping for the
121
- product type id
122
- * :attr:`~eodag.config.PluginConfig.DiscoverProductTypes.generic_product_type_parsable_metadata`
123
- (``dict[str, str]``): mapping for product type metadata (e.g. ``abstract``, ``licence``) which can be parsed
124
- from the provider result
125
- * :attr:`~eodag.config.PluginConfig.DiscoverProductTypes.generic_product_type_parsable_properties`
126
- (``dict[str, str]``): mapping for product type properties which can be parsed from the result and are not
127
- product type metadata
128
- * :attr:`~eodag.config.PluginConfig.DiscoverProductTypes.single_collection_fetch_url` (``str``): url to fetch
129
- data for a single collection; used if product type metadata is not available from the endpoint given in
130
- :attr:`~eodag.config.PluginConfig.DiscoverProductTypes.fetch_url`
131
- * :attr:`~eodag.config.PluginConfig.DiscoverProductTypes.single_collection_fetch_qs` (``str``): query string
132
- to be added to the :attr:`~eodag.config.PluginConfig.DiscoverProductTypes.fetch_url` to filter for a
133
- collection
134
- * :attr:`~eodag.config.PluginConfig.DiscoverProductTypes.single_product_type_parsable_metadata`
135
- (``dict[str, str]``): mapping for product type metadata returned by the endpoint given in
136
- :attr:`~eodag.config.PluginConfig.DiscoverProductTypes.single_collection_fetch_url`.
137
-
138
- * :attr:`~eodag.config.PluginConfig.constraints_file_url` (``str``): url to fetch the constraints for a specific
139
- product type, can be an http url or a path to a file; the constraints are used to build queryables
140
- * :attr:`~eodag.config.PluginConfig.constraints_entry` (``str``): key in the json result where the constraints
141
- can be found; if not given, it is assumed that the constraints are on top level of the result, i.e.
142
- the result is an array of constraints
143
- * :attr:`~eodag.config.PluginConfig.metadata_mapping` (``dict[str, Any]``): The search plugins of this kind can
144
- detect when a metadata mapping is "query-able", and get the semantics of how to format the query string
145
- parameter that enables to make a query on the corresponding metadata. To make a metadata query-able,
146
- just configure it in the metadata mapping to be a list of 2 items, the first one being the
147
- specification of the query string search formatting. The later is a string following the
148
- specification of Python string formatting, with a special behaviour added to it. For example,
149
- an entry in the metadata mapping of this kind::
150
-
151
- completionTimeFromAscendingNode:
152
- - 'f=acquisition.endViewingDate:lte:{completionTimeFromAscendingNode#timestamp}'
153
- - '$.properties.acquisition.endViewingDate'
154
-
155
- means that the search url will have a query string parameter named ``f`` with a value of
156
- ``acquisition.endViewingDate:lte:1543922280.0`` if the search was done with the value
157
- of ``completionTimeFromAscendingNode`` being ``2018-12-04T12:18:00``. What happened is that
158
- ``{completionTimeFromAscendingNode#timestamp}`` was replaced with the timestamp of the value
159
- of ``completionTimeFromAscendingNode``. This example shows all there is to know about the
160
- semantics of the query string formatting introduced by this plugin: any eodag search parameter
161
- can be referenced in the query string with an additional optional conversion function that
162
- is separated from it by a ``#`` (see :func:`~eodag.api.product.metadata_mapping.format_metadata` for further
163
- details on the available converters). Note that for the values in the
164
- :attr:`~eodag.config.PluginConfig.free_text_search_operations` configuration parameter follow the same rule.
165
- If the metadata_mapping is not a list but only a string, this means that the parameters is not queryable but
166
- it is included in the result obtained from the provider. The string indicates how the provider result should
167
- be mapped to the eodag parameter.
168
-
169
- """
170
-
171
- data_request_id: Optional[str]
172
-
173
- def __init__(self, provider: str, config: PluginConfig) -> None:
174
- super().__init__(provider, config)
175
- self.config.__dict__.setdefault("result_type", "json")
176
- self.config.__dict__.setdefault("results_entry", "content")
177
- self.config.__dict__.setdefault("pagination", {})
178
- self.config.__dict__.setdefault("free_text_search_operations", {})
179
- for product_type in self.config.products.keys():
180
- if "metadata_mapping" in self.config.products[product_type].keys():
181
- self.config.products[product_type][
182
- "metadata_mapping"
183
- ] = mtd_cfg_as_conversion_and_querypath(
184
- self.config.products[product_type]["metadata_mapping"]
185
- )
186
- # Complete and ready to use product type specific metadata-mapping
187
- product_type_metadata_mapping = deepcopy(self.config.metadata_mapping)
188
-
189
- # update config using provider product type definition metadata_mapping
190
- # from another product
191
- other_product_for_mapping = self.config.products[product_type].get(
192
- "metadata_mapping_from_product", ""
193
- )
194
- if other_product_for_mapping:
195
- other_product_type_def_params = self.get_product_type_def_params(
196
- other_product_for_mapping,
197
- )
198
- product_type_metadata_mapping.update(
199
- other_product_type_def_params.get("metadata_mapping", {})
200
- )
201
- # from current product
202
- product_type_metadata_mapping.update(
203
- self.config.products[product_type]["metadata_mapping"]
204
- )
205
-
206
- self.config.products[product_type][
207
- "metadata_mapping"
208
- ] = product_type_metadata_mapping
209
-
210
- if (
211
- self.config.result_type == "json"
212
- and "next_page_url_key_path" in self.config.pagination
213
- ):
214
- self.config.pagination["next_page_url_key_path"] = string_to_jsonpath(
215
- self.config.pagination.get("next_page_url_key_path")
216
- )
217
- self.download_info: dict[str, Any] = {}
218
- self.data_request_id = None
219
-
220
- def discover_product_types(self, **kwargs: Any) -> Optional[dict[str, Any]]:
221
- """Fetch product types is disabled for `DataRequestSearch`
222
-
223
- :returns: empty dict
224
- """
225
- return None
226
-
227
- def clear(self) -> None:
228
- """Clear search context"""
229
- super().clear()
230
- self.data_request_id = None
231
-
232
- def query(
233
- self,
234
- prep: PreparedSearch = PreparedSearch(),
235
- **kwargs: Any,
236
- ) -> tuple[list[EOProduct], Optional[int]]:
237
- """
238
- performs the search for a provider where several steps are required to fetch the data
239
- """
240
- if kwargs.get("sort_by"):
241
- raise ValidationError(f"{self.provider} does not support sorting feature")
242
-
243
- product_type = kwargs.get("productType")
244
-
245
- if product_type is None:
246
- raise ValidationError("Required productType is missing")
247
-
248
- # replace "product_type" to "providerProductType" in search args if exists
249
- # for compatibility with DataRequestSearch method
250
- if kwargs.get("product_type"):
251
- kwargs["providerProductType"] = kwargs.pop("product_type", None)
252
- provider_product_type = cast(str, self._map_product_type(product_type or ""))
253
- keywords = {k: v for k, v in kwargs.items() if k != "auth" and v is not None}
254
-
255
- if provider_product_type and provider_product_type != GENERIC_PRODUCT_TYPE:
256
- keywords["productType"] = provider_product_type
257
- else:
258
- keywords["productType"] = product_type
259
-
260
- # provider product type specific conf
261
- self.product_type_def_params = self.get_product_type_def_params(
262
- product_type, format_variables=kwargs
263
- )
264
-
265
- # update config using provider product type definition metadata_mapping
266
- # from another product
267
- other_product_for_mapping = self.product_type_def_params.get(
268
- "metadata_mapping_from_product", ""
269
- )
270
- if other_product_for_mapping:
271
- other_product_type_def_params = self.get_product_type_def_params(
272
- other_product_for_mapping, format_variables=kwargs
273
- )
274
- self.config.metadata_mapping.update(
275
- other_product_type_def_params.get("metadata_mapping", {})
276
- )
277
- # from current product
278
- self.config.metadata_mapping.update(
279
- self.product_type_def_params.get("metadata_mapping", {})
280
- )
281
-
282
- # if product_type_def_params is set, remove product_type as it may conflict with this conf
283
- if self.product_type_def_params:
284
- keywords.pop("productType", None)
285
- keywords.update(
286
- {
287
- k: v
288
- for k, v in self.product_type_def_params.items()
289
- if k not in keywords.keys()
290
- and k in self.config.metadata_mapping.keys()
291
- and isinstance(self.config.metadata_mapping[k], list)
292
- }
293
- )
294
-
295
- # update dates if needed
296
- if getattr(self.config, "dates_required", True) and "id" not in keywords:
297
- if not keywords.get("startTimeFromAscendingNode"):
298
- keywords["startTimeFromAscendingNode"] = getattr(
299
- self.config, "product_type_config", {}
300
- ).get("missionStartDate", DEFAULT_MISSION_START_DATE)
301
- if not keywords.get("completionTimeFromAscendingNode"):
302
- keywords["completionTimeFromAscendingNode"] = getattr(
303
- self.config, "product_type_config", {}
304
- ).get("missionEndDate", datetime.now(timezone.utc).isoformat())
305
-
306
- # ask for data_request_id if not set (it must exist when iterating over pages)
307
- if not self.data_request_id:
308
- data_request_id = self._create_data_request(
309
- provider_product_type, product_type, **keywords
310
- )
311
- self.data_request_id = data_request_id
312
- request_finished = False
313
- else:
314
- data_request_id = self.data_request_id
315
- request_finished = True
316
-
317
- # loop to check search job status
318
- search_timeout = int(getattr(self.config, "timeout", DEFAULT_SEARCH_TIMEOUT))
319
- logger.info(
320
- f"checking status of request job {data_request_id} (timeout={search_timeout}s)"
321
- )
322
- check_beginning = datetime.now()
323
- while not request_finished:
324
- request_finished = self._check_request_status(data_request_id)
325
- if not request_finished and datetime.now() >= check_beginning + timedelta(
326
- seconds=search_timeout
327
- ):
328
- self._cancel_request(data_request_id)
329
- raise NotAvailableError(
330
- f"Timeout reached when checking search job status for {self.provider}"
331
- )
332
- elif not request_finished:
333
- time.sleep(1)
334
-
335
- logger.info("search job for product_type %s finished", provider_product_type)
336
- result = self._get_result_data(
337
- data_request_id,
338
- kwargs.get("items_per_page", DEFAULT_ITEMS_PER_PAGE),
339
- kwargs.get("page", DEFAULT_PAGE),
340
- )
341
- # if exists, add the geometry from search args in the content of the response for each product
342
- if keywords.get("geometry"):
343
- for product_content in result["content"]:
344
- if product_content["extraInformation"] is None:
345
- product_content["extraInformation"] = {
346
- "footprint": keywords["geometry"]
347
- }
348
- elif not product_content["extraInformation"].get("footprint"):
349
- product_content["extraInformation"]["footprint"] = keywords[
350
- "geometry"
351
- ]
352
- logger.info("result retrieved from search job")
353
- if self._check_uses_custom_filters(product_type):
354
- result = self._apply_additional_filters(
355
- result, self.config.products[product_type]["custom_filters"]
356
- )
357
- return self._convert_result_data(
358
- result, data_request_id, product_type or "", **kwargs
359
- )
360
-
361
- def _create_data_request(
362
- self, product_type: str, eodag_product_type: str, **kwargs: Any
363
- ) -> str:
364
- headers = getattr(self.auth, "headers", USER_AGENT)
365
- ssl_verify = getattr(self.config.ssl_verify, "ssl_verify", True)
366
- try:
367
- url = self.config.data_request_url
368
- try:
369
- request_body = format_query_params(
370
- eodag_product_type, self.config, kwargs
371
- )
372
- except ValidationError as err:
373
- not_queryable_search_param = Queryables.get_queryable_from_alias(
374
- str(err.message).split(":")[-1].strip()
375
- )
376
- raise ValidationError(
377
- f"Search parameters which are not queryable are disallowed for {product_type} on "
378
- f"{self.provider}: please remove '{not_queryable_search_param}' from your search parameters",
379
- {not_queryable_search_param},
380
- ) from err
381
- logger.debug(
382
- f"Sending search job request to {url} with {str(request_body)}"
383
- )
384
- request_job = requests.post(
385
- url,
386
- json=request_body,
387
- headers=headers,
388
- timeout=HTTP_REQ_TIMEOUT,
389
- verify=ssl_verify,
390
- )
391
- request_job.raise_for_status()
392
- except requests.exceptions.Timeout as exc:
393
- raise TimeOutError(exc, timeout=HTTP_REQ_TIMEOUT) from exc
394
- except requests.RequestException as e:
395
- raise RequestError.from_error(
396
- e, f"search job for product_type {product_type} could not be created"
397
- ) from e
398
- else:
399
- logger.info("search job for product_type %s created", product_type)
400
- return request_job.json()["jobId"]
401
-
402
- def _cancel_request(self, data_request_id: str) -> None:
403
- logger.info("deleting request job %s", data_request_id)
404
- delete_url = f"{self.config.data_request_url}/{data_request_id}"
405
- headers = getattr(self.auth, "headers", USER_AGENT)
406
- try:
407
- delete_resp = requests.delete(
408
- delete_url, headers=headers, timeout=HTTP_REQ_TIMEOUT
409
- )
410
- delete_resp.raise_for_status()
411
- except requests.exceptions.Timeout as exc:
412
- raise TimeOutError(exc, timeout=HTTP_REQ_TIMEOUT) from exc
413
- except requests.RequestException as e:
414
- raise RequestError.from_error(e, "_cancel_request failed") from e
415
-
416
- def _check_request_status(self, data_request_id: str) -> bool:
417
- logger.debug("checking status of request job %s", data_request_id)
418
- status_url = self.config.status_url + data_request_id
419
- headers = getattr(self.auth, "headers", USER_AGENT)
420
- ssl_verify = getattr(self.config, "ssl_verify", True)
421
-
422
- try:
423
- status_resp = requests.get(
424
- status_url,
425
- headers=headers,
426
- timeout=HTTP_REQ_TIMEOUT,
427
- verify=ssl_verify,
428
- )
429
- status_resp.raise_for_status()
430
- except requests.exceptions.Timeout as exc:
431
- raise TimeOutError(exc, timeout=HTTP_REQ_TIMEOUT) from exc
432
- except requests.RequestException as e:
433
- raise RequestError.from_error(e, "_check_request_status failed") from e
434
- else:
435
- status_data = status_resp.json()
436
- if "status_code" in status_data and status_data["status_code"] in [
437
- 403,
438
- 404,
439
- ]:
440
- logger.error(f"_check_request_status failed: {status_data}")
441
- error = RequestError("authentication token expired during request")
442
- error.status_code = status_data["status_code"]
443
- raise error
444
- if status_data["status"] == "failed":
445
- logger.error(f"_check_request_status failed: {status_data}")
446
- raise RequestError(
447
- f"data request job has failed, message: {status_data['message']}"
448
- )
449
- return status_data["status"] == "completed"
450
-
451
- def _get_result_data(
452
- self, data_request_id: str, items_per_page: int, page: int
453
- ) -> dict[str, Any]:
454
- page = page - 1 + self.config.pagination.get("start_page", 1)
455
- url = self.config.result_url.format(
456
- jobId=data_request_id, items_per_page=items_per_page, page=page
457
- )
458
- ssl_verify = getattr(self.config, "ssl_verify", True)
459
- headers = getattr(self.auth, "headers", USER_AGENT)
460
- try:
461
- return requests.get(
462
- url, headers=headers, timeout=HTTP_REQ_TIMEOUT, verify=ssl_verify
463
- ).json()
464
- except requests.exceptions.Timeout as exc:
465
- raise TimeOutError(exc, timeout=HTTP_REQ_TIMEOUT) from exc
466
- except requests.RequestException:
467
- logger.error(f"Result could not be retrieved for {url}")
468
- return {}
469
-
470
- def _convert_result_data(
471
- self,
472
- result_data: dict[str, Any],
473
- data_request_id: str,
474
- product_type: str,
475
- **kwargs: Any,
476
- ) -> tuple[list[EOProduct], int]:
477
- """Build EOProducts from provider results"""
478
- results_entry = self.config.results_entry
479
- results = result_data[results_entry]
480
- logger.debug(
481
- "Adapting %s plugin results to eodag product representation" % len(results)
482
- )
483
- products: list[EOProduct] = []
484
- for result in results:
485
- product = EOProduct(
486
- self.provider,
487
- properties_from_json(
488
- result,
489
- self.get_metadata_mapping(kwargs.get("productType")),
490
- discovery_config=getattr(self.config, "discover_metadata", {}),
491
- ),
492
- **kwargs,
493
- )
494
- # use product_type_config as default properties
495
- product.properties = dict(
496
- getattr(self.config, "product_type_config", {}), **product.properties
497
- )
498
- products.append(product)
499
- # postprocess filtering needed when provider does not natively offer filtering by id
500
- if "id" in kwargs:
501
- products = [
502
- p for p in products if product.properties["id"] == kwargs["id"]
503
- ] or products
504
- total_items_nb_key_path = string_to_jsonpath(
505
- self.config.pagination["total_items_nb_key_path"]
506
- )
507
- found_total_items_nb_paths = total_items_nb_key_path.find(results)
508
- if found_total_items_nb_paths and not isinstance(
509
- found_total_items_nb_paths, int
510
- ):
511
- total_items_nb = found_total_items_nb_paths[0].value
512
- else:
513
- total_items_nb = 0
514
- for p in products:
515
- # add the request id to the order link property (required to create data order)
516
- p.properties["orderLink"] = p.properties["orderLink"].replace(
517
- "requestJobId", str(data_request_id)
518
- )
519
- if self.config.products[product_type].get("storeDownloadUrl", False):
520
- # store download information to retrieve it later in case search by id
521
- # is not possible
522
- self.download_info[p.properties["id"]] = {
523
- "requestJobId": data_request_id,
524
- "orderLink": p.properties["orderLink"],
525
- "downloadLink": p.properties["downloadLink"],
526
- "provider": self.provider,
527
- }
528
- return products, total_items_nb
529
-
530
- def _check_uses_custom_filters(self, product_type: str) -> bool:
531
- if (
532
- product_type in self.config.products
533
- and "custom_filters" in self.config.products[product_type]
534
- ):
535
- return True
536
- return False
537
-
538
- def _apply_additional_filters(
539
- self, result: dict[str, Any], custom_filters: dict[str, str]
540
- ) -> dict[str, Any]:
541
- filtered_result = []
542
- results_entry = self.config.results_entry
543
- results = result[results_entry]
544
- path = string_to_jsonpath(custom_filters["filter_attribute"])
545
- indexes = custom_filters["indexes"].split("-")
546
- for record in results:
547
- found_paths = path.find(record)
548
- if not found_paths or isinstance(found_paths, int):
549
- continue
550
- filter_param = found_paths[0].value
551
- filter_value = filter_param[int(indexes[0]) : int(indexes[1])]
552
- filter_clause = "'" + filter_value + "' " + custom_filters["filter_clause"]
553
- if eval(filter_clause):
554
- filtered_result.append(record)
555
- result[results_entry] = filtered_result
556
- return result
557
-
558
- def _map_product_type(self, product_type: Optional[str]) -> Optional[str]:
559
- """Map the eodag product type to the provider product type"""
560
- if product_type is None:
561
- return None
562
- logger.debug("Mapping eodag product type to provider product type")
563
- return self.config.products.get(product_type, {}).get(
564
- "productType", GENERIC_PRODUCT_TYPE
565
- )