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.
Files changed (94) hide show
  1. eodag/api/core.py +347 -247
  2. eodag/api/product/_assets.py +44 -15
  3. eodag/api/product/_product.py +58 -47
  4. eodag/api/product/drivers/__init__.py +81 -4
  5. eodag/api/product/drivers/base.py +65 -4
  6. eodag/api/product/drivers/generic.py +65 -0
  7. eodag/api/product/drivers/sentinel1.py +97 -0
  8. eodag/api/product/drivers/sentinel2.py +95 -0
  9. eodag/api/product/metadata_mapping.py +129 -93
  10. eodag/api/search_result.py +28 -12
  11. eodag/cli.py +61 -24
  12. eodag/config.py +457 -167
  13. eodag/plugins/apis/base.py +10 -4
  14. eodag/plugins/apis/ecmwf.py +53 -23
  15. eodag/plugins/apis/usgs.py +41 -17
  16. eodag/plugins/authentication/aws_auth.py +30 -18
  17. eodag/plugins/authentication/base.py +14 -3
  18. eodag/plugins/authentication/generic.py +14 -3
  19. eodag/plugins/authentication/header.py +14 -6
  20. eodag/plugins/authentication/keycloak.py +44 -25
  21. eodag/plugins/authentication/oauth.py +18 -4
  22. eodag/plugins/authentication/openid_connect.py +192 -171
  23. eodag/plugins/authentication/qsauth.py +12 -4
  24. eodag/plugins/authentication/sas_auth.py +22 -5
  25. eodag/plugins/authentication/token.py +95 -17
  26. eodag/plugins/authentication/token_exchange.py +19 -19
  27. eodag/plugins/base.py +4 -4
  28. eodag/plugins/crunch/base.py +8 -5
  29. eodag/plugins/crunch/filter_date.py +9 -6
  30. eodag/plugins/crunch/filter_latest_intersect.py +9 -8
  31. eodag/plugins/crunch/filter_latest_tpl_name.py +8 -8
  32. eodag/plugins/crunch/filter_overlap.py +9 -11
  33. eodag/plugins/crunch/filter_property.py +10 -10
  34. eodag/plugins/download/aws.py +181 -105
  35. eodag/plugins/download/base.py +49 -67
  36. eodag/plugins/download/creodias_s3.py +40 -2
  37. eodag/plugins/download/http.py +247 -223
  38. eodag/plugins/download/s3rest.py +29 -28
  39. eodag/plugins/manager.py +176 -41
  40. eodag/plugins/search/__init__.py +6 -5
  41. eodag/plugins/search/base.py +123 -60
  42. eodag/plugins/search/build_search_result.py +1046 -355
  43. eodag/plugins/search/cop_marine.py +132 -39
  44. eodag/plugins/search/creodias_s3.py +19 -68
  45. eodag/plugins/search/csw.py +48 -8
  46. eodag/plugins/search/data_request_search.py +124 -23
  47. eodag/plugins/search/qssearch.py +531 -310
  48. eodag/plugins/search/stac_list_assets.py +85 -0
  49. eodag/plugins/search/static_stac_search.py +23 -24
  50. eodag/resources/ext_product_types.json +1 -1
  51. eodag/resources/product_types.yml +1295 -355
  52. eodag/resources/providers.yml +1819 -3010
  53. eodag/resources/stac.yml +3 -163
  54. eodag/resources/stac_api.yml +2 -2
  55. eodag/resources/user_conf_template.yml +115 -99
  56. eodag/rest/cache.py +2 -2
  57. eodag/rest/config.py +3 -4
  58. eodag/rest/constants.py +0 -1
  59. eodag/rest/core.py +157 -117
  60. eodag/rest/errors.py +181 -0
  61. eodag/rest/server.py +57 -339
  62. eodag/rest/stac.py +133 -581
  63. eodag/rest/types/collections_search.py +3 -3
  64. eodag/rest/types/eodag_search.py +41 -30
  65. eodag/rest/types/queryables.py +42 -32
  66. eodag/rest/types/stac_search.py +15 -16
  67. eodag/rest/utils/__init__.py +14 -21
  68. eodag/rest/utils/cql_evaluate.py +6 -6
  69. eodag/rest/utils/rfc3339.py +2 -2
  70. eodag/types/__init__.py +153 -32
  71. eodag/types/bbox.py +2 -2
  72. eodag/types/download_args.py +4 -4
  73. eodag/types/queryables.py +183 -73
  74. eodag/types/search_args.py +6 -6
  75. eodag/types/whoosh.py +127 -3
  76. eodag/utils/__init__.py +228 -106
  77. eodag/utils/exceptions.py +47 -26
  78. eodag/utils/import_system.py +2 -2
  79. eodag/utils/logging.py +37 -77
  80. eodag/utils/repr.py +65 -6
  81. eodag/utils/requests.py +13 -15
  82. eodag/utils/rest.py +2 -2
  83. eodag/utils/s3.py +231 -0
  84. eodag/utils/stac_reader.py +11 -11
  85. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/METADATA +81 -81
  86. eodag-3.1.0.dist-info/RECORD +113 -0
  87. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
  88. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +5 -2
  89. eodag/resources/constraints/climate-dt.json +0 -13
  90. eodag/resources/constraints/extremes-dt.json +0 -8
  91. eodag/utils/constraints.py +0 -244
  92. eodag-3.0.0b3.dist-info/RECORD +0 -110
  93. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/LICENSE +0 -0
  94. {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, Dict, List, Optional, Tuple, cast
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: Dict[str, Any]):
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
- ) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:
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
- str,
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 UnsupportedProductType(product_type)
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: Dict[str, Any],
154
- collection_dict: Dict[str, Any],
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, obj["Key"], s3_url, dataset_item, collection_dict
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: Dict[str, Any],
169
- collection_dict: Dict[str, Any],
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].split(".")[0]
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
- if "start_datetime" in dataset_item:
184
- properties["startTimeFromAscendingNode"] = dataset_item[
185
- "start_datetime"
186
- ]
187
- properties["completionTimeFromAscendingNode"] = dataset_item[
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{8}", item_key)
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{6}", item_key)
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
- ) -> Tuple[List[EOProduct], Optional[int]]:
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: List[EOProduct] = []
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{8}", item_key)
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{6}", item_key)
355
- item_start = _get_date_from_yyyymmdd(item_dates[0], item_key)
356
- if not item_start: # identified pattern was not a valid datetime
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 not item_dates or (start_date <= item_start <= end_date):
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, List
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 AssetsDict, EOProduct # type: ignore
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 guess_file_type
32
- from eodag.utils.exceptions import AuthenticationError, MisconfiguredError, RequestError
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
- _update_assets(self, downloader.config, authenticator)
47
+ update_assets_from_s3(
48
+ self, authenticator, getattr(downloader.config, "s3_endpoint", None)
49
+ )
52
50
  except BotoCoreError as e:
53
- raise RequestError(f"could not update assets: {str(e)}") from e
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
- Search on creodias and adapt results to s3
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
- ) -> List[EOProduct]:
72
+ ) -> list[EOProduct]:
122
73
  """Build EOProducts from provider results"""
123
74
 
124
75
  products = super(CreodiasS3Search, self).normalize_results(results, **kwargs)
@@ -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, Dict, List, Optional, Tuple, Union
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
- ) -> Tuple[List[EOProduct], Optional[int]]:
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: List[EOProduct] = []
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: Dict[str, Any],
232
+ product_type_def: dict[str, Any],
193
233
  product_type: str,
194
- params: Dict[str, Any],
195
- ) -> Union[List[OgcExpression], List[List[OgcExpression]]]:
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: List[OgcExpression] = []
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 = (