eodag 3.0.1__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 (87) hide show
  1. eodag/api/core.py +174 -138
  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 +117 -90
  10. eodag/api/search_result.py +13 -23
  11. eodag/cli.py +26 -5
  12. eodag/config.py +86 -92
  13. eodag/plugins/apis/base.py +1 -1
  14. eodag/plugins/apis/ecmwf.py +42 -22
  15. eodag/plugins/apis/usgs.py +17 -16
  16. eodag/plugins/authentication/aws_auth.py +16 -13
  17. eodag/plugins/authentication/base.py +5 -3
  18. eodag/plugins/authentication/header.py +3 -3
  19. eodag/plugins/authentication/keycloak.py +4 -4
  20. eodag/plugins/authentication/oauth.py +7 -3
  21. eodag/plugins/authentication/openid_connect.py +22 -16
  22. eodag/plugins/authentication/sas_auth.py +4 -4
  23. eodag/plugins/authentication/token.py +41 -10
  24. eodag/plugins/authentication/token_exchange.py +1 -1
  25. eodag/plugins/base.py +4 -4
  26. eodag/plugins/crunch/base.py +4 -4
  27. eodag/plugins/crunch/filter_date.py +4 -4
  28. eodag/plugins/crunch/filter_latest_intersect.py +6 -6
  29. eodag/plugins/crunch/filter_latest_tpl_name.py +7 -7
  30. eodag/plugins/crunch/filter_overlap.py +4 -4
  31. eodag/plugins/crunch/filter_property.py +6 -7
  32. eodag/plugins/download/aws.py +146 -87
  33. eodag/plugins/download/base.py +38 -56
  34. eodag/plugins/download/creodias_s3.py +29 -0
  35. eodag/plugins/download/http.py +173 -183
  36. eodag/plugins/download/s3rest.py +10 -11
  37. eodag/plugins/manager.py +10 -20
  38. eodag/plugins/search/__init__.py +6 -5
  39. eodag/plugins/search/base.py +90 -46
  40. eodag/plugins/search/build_search_result.py +1048 -361
  41. eodag/plugins/search/cop_marine.py +22 -12
  42. eodag/plugins/search/creodias_s3.py +9 -73
  43. eodag/plugins/search/csw.py +11 -11
  44. eodag/plugins/search/data_request_search.py +19 -18
  45. eodag/plugins/search/qssearch.py +99 -258
  46. eodag/plugins/search/stac_list_assets.py +85 -0
  47. eodag/plugins/search/static_stac_search.py +4 -4
  48. eodag/resources/ext_product_types.json +1 -1
  49. eodag/resources/product_types.yml +1134 -325
  50. eodag/resources/providers.yml +906 -2006
  51. eodag/resources/stac_api.yml +2 -2
  52. eodag/resources/user_conf_template.yml +10 -9
  53. eodag/rest/cache.py +2 -2
  54. eodag/rest/config.py +3 -3
  55. eodag/rest/core.py +112 -82
  56. eodag/rest/errors.py +5 -5
  57. eodag/rest/server.py +33 -14
  58. eodag/rest/stac.py +41 -38
  59. eodag/rest/types/collections_search.py +3 -3
  60. eodag/rest/types/eodag_search.py +29 -23
  61. eodag/rest/types/queryables.py +42 -31
  62. eodag/rest/types/stac_search.py +15 -25
  63. eodag/rest/utils/__init__.py +14 -21
  64. eodag/rest/utils/cql_evaluate.py +6 -6
  65. eodag/rest/utils/rfc3339.py +2 -2
  66. eodag/types/__init__.py +141 -32
  67. eodag/types/bbox.py +2 -2
  68. eodag/types/download_args.py +3 -3
  69. eodag/types/queryables.py +183 -72
  70. eodag/types/search_args.py +4 -4
  71. eodag/types/whoosh.py +127 -3
  72. eodag/utils/__init__.py +153 -51
  73. eodag/utils/exceptions.py +28 -21
  74. eodag/utils/import_system.py +2 -2
  75. eodag/utils/repr.py +65 -6
  76. eodag/utils/requests.py +13 -13
  77. eodag/utils/rest.py +2 -2
  78. eodag/utils/s3.py +231 -0
  79. eodag/utils/stac_reader.py +10 -10
  80. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/METADATA +77 -76
  81. eodag-3.1.0.dist-info/RECORD +113 -0
  82. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
  83. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +4 -2
  84. eodag/utils/constraints.py +0 -244
  85. eodag-3.0.1.dist-info/RECORD +0 -109
  86. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/LICENSE +0 -0
  87. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/top_level.txt +0 -0
@@ -19,7 +19,7 @@ from __future__ import annotations
19
19
 
20
20
  import re
21
21
  from collections import UserDict
22
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
22
+ from typing import TYPE_CHECKING, Any, Optional
23
23
 
24
24
  from eodag.utils.exceptions import NotAvailableError
25
25
  from eodag.utils.repr import dict_to_html_table
@@ -31,12 +31,27 @@ if TYPE_CHECKING:
31
31
 
32
32
 
33
33
  class AssetsDict(UserDict):
34
- """A UserDict object listing assets contained in a
35
- :class:`~eodag.api.product._product.EOProduct` resulting from a search.
34
+ """A UserDict object which values are :class:`~eodag.api.product._assets.Asset`
35
+ contained in a :class:`~eodag.api.product._product.EOProduct` resulting from a
36
+ search.
36
37
 
37
38
  :param product: Product resulting from a search
38
39
  :param args: (optional) Arguments used to init the dictionary
39
40
  :param kwargs: (optional) Additional named-arguments used to init the dictionary
41
+
42
+ Example
43
+ -------
44
+
45
+ >>> from eodag.api.product import EOProduct
46
+ >>> product = EOProduct(
47
+ ... provider="foo",
48
+ ... properties={"id": "bar", "geometry": "POINT (0 0)"}
49
+ ... )
50
+ >>> type(product.assets)
51
+ <class 'eodag.api.product._assets.AssetsDict'>
52
+ >>> product.assets.update({"foo": {"href": "http://somewhere/something"}})
53
+ >>> product.assets
54
+ {'foo': {'href': 'http://somewhere/something'}}
40
55
  """
41
56
 
42
57
  product: EOProduct
@@ -45,10 +60,10 @@ class AssetsDict(UserDict):
45
60
  self.product = product
46
61
  super(AssetsDict, self).__init__(*args, **kwargs)
47
62
 
48
- def __setitem__(self, key: str, value: Dict[str, Any]) -> None:
63
+ def __setitem__(self, key: str, value: dict[str, Any]) -> None:
49
64
  super().__setitem__(key, Asset(self.product, key, value))
50
65
 
51
- def as_dict(self) -> Dict[str, Any]:
66
+ def as_dict(self) -> dict[str, Any]:
52
67
  """Builds a representation of AssetsDict to enable its serialization
53
68
 
54
69
  :returns: The representation of a :class:`~eodag.api.product._assets.AssetsDict`
@@ -56,7 +71,7 @@ class AssetsDict(UserDict):
56
71
  """
57
72
  return {k: v.as_dict() for k, v in self.data.items()}
58
73
 
59
- def get_values(self, asset_filter: str = "") -> List[Asset]:
74
+ def get_values(self, asset_filter: str = "") -> list[Asset]:
60
75
  """
61
76
  retrieves the assets matching the given filter
62
77
 
@@ -98,12 +113,12 @@ class AssetsDict(UserDict):
98
113
  <details><summary style='color: grey;'>
99
114
  <span style='color: black'>'{k}'</span>:&ensp;
100
115
  {{
101
- {"'roles': '<span style='color: black'>"+str(v['roles'])+"</span>',&ensp;"
102
- if v.get("roles") else ""}
103
- {"'type': '"+str(v['type'])+"',&ensp;"
104
- if v.get("type") else ""}
105
- {"'title': '<span style='color: black'>"+str(v['title'])+"</span>',&ensp;"
106
- if v.get("title") else ""}
116
+ {"'roles': '<span style='color: black'>" + str(v['roles']) + "</span>',&ensp;"
117
+ if v.get("roles") else ""}
118
+ {"'type': '" + str(v['type']) + "',&ensp;"
119
+ if v.get("type") else ""}
120
+ {"'title': '<span style='color: black'>" + str(v['title']) + "</span>',&ensp;"
121
+ if v.get("title") else ""}
107
122
  ...
108
123
  }}
109
124
  </summary>
@@ -119,13 +134,27 @@ class AssetsDict(UserDict):
119
134
 
120
135
 
121
136
  class Asset(UserDict):
122
- """A UserDict object containg one of the assets of a
123
- :class:`~eodag.api.product._product.EOProduct` resulting from a search.
137
+ """A UserDict object containg one of the
138
+ :attr:`~eodag.api.product._product.EOProduct.assets` resulting from a search.
124
139
 
125
140
  :param product: Product resulting from a search
126
141
  :param key: asset key
127
142
  :param args: (optional) Arguments used to init the dictionary
128
143
  :param kwargs: (optional) Additional named-arguments used to init the dictionary
144
+
145
+ Example
146
+ -------
147
+
148
+ >>> from eodag.api.product import EOProduct
149
+ >>> product = EOProduct(
150
+ ... provider="foo",
151
+ ... properties={"id": "bar", "geometry": "POINT (0 0)"}
152
+ ... )
153
+ >>> product.assets.update({"foo": {"href": "http://somewhere/something"}})
154
+ >>> type(product.assets["foo"])
155
+ <class 'eodag.api.product._assets.Asset'>
156
+ >>> product.assets["foo"]
157
+ {'href': 'http://somewhere/something'}
129
158
  """
130
159
 
131
160
  product: EOProduct
@@ -138,7 +167,7 @@ class Asset(UserDict):
138
167
  self.key = key
139
168
  super(Asset, self).__init__(*args, **kwargs)
140
169
 
141
- def as_dict(self) -> Dict[str, Any]:
170
+ def as_dict(self) -> dict[str, Any]:
142
171
  """Builds a representation of Asset to enable its serialization
143
172
 
144
173
  :returns: The representation of a :class:`~eodag.api.product._assets.Asset` as a
@@ -22,7 +22,7 @@ import logging
22
22
  import os
23
23
  import re
24
24
  import tempfile
25
- from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union
25
+ from typing import TYPE_CHECKING, Any, Optional, Union
26
26
 
27
27
  import requests
28
28
  from requests import RequestException
@@ -38,7 +38,7 @@ try:
38
38
  except ImportError:
39
39
  from eodag.api.product._assets import AssetsDict
40
40
 
41
- from eodag.api.product.drivers import DRIVERS, NoDriver
41
+ from eodag.api.product.drivers import DRIVERS, LEGACY_DRIVERS, NoDriver
42
42
  from eodag.api.product.metadata_mapping import (
43
43
  DEFAULT_GEOMETRY,
44
44
  NOT_AVAILABLE,
@@ -89,20 +89,6 @@ class EOProduct:
89
89
 
90
90
  :param provider: The provider from which the product originates
91
91
  :param properties: The metadata of the product
92
- :ivar product_type: The product type
93
- :vartype product_type: str
94
- :ivar location: The path to the product, either remote or local if downloaded
95
- :vartype location: str
96
- :ivar remote_location: The remote path to the product
97
- :vartype remote_location: str
98
- :ivar search_kwargs: The search kwargs used by eodag to search for the product
99
- :vartype search_kwargs: Any
100
- :ivar geometry: The geometry of the product
101
- :vartype geometry: :class:`shapely.geometry.base.BaseGeometry`
102
- :ivar search_intersection: The intersection between the product's geometry
103
- and the search area.
104
- :vartype search_intersection: :class:`shapely.geometry.base.BaseGeometry` or None
105
-
106
92
 
107
93
  .. note::
108
94
  The geojson spec `enforces <https://github.com/geojson/draft-geojson/pull/6>`_
@@ -112,18 +98,31 @@ class EOProduct:
112
98
  mentioned CRS.
113
99
  """
114
100
 
101
+ #: The provider from which the product originates
115
102
  provider: str
116
- properties: Dict[str, Any]
103
+ #: The metadata of the product
104
+ properties: dict[str, Any]
105
+ #: The product type
117
106
  product_type: Optional[str]
118
- location: str
119
- remote_location: str
120
- search_kwargs: Any
107
+ #: The geometry of the product
121
108
  geometry: BaseGeometry
109
+ #: The intersection between the product's geometry and the search area.
122
110
  search_intersection: Optional[BaseGeometry]
111
+ #: The path to the product, either remote or local if downloaded
112
+ location: str
113
+ #: The remote path to the product
114
+ remote_location: str
115
+ #: Assets of the product
123
116
  assets: AssetsDict
117
+ #: Driver enables additional methods to be called on the EOProduct
118
+ driver: DatasetDriver
119
+ #: Product data filename, stored during download
120
+ filename: str
121
+ #: Product search keyword arguments, stored during search
122
+ search_kwargs: Any
124
123
 
125
124
  def __init__(
126
- self, provider: str, properties: Dict[str, Any], **kwargs: Any
125
+ self, provider: str, properties: dict[str, Any], **kwargs: Any
127
126
  ) -> None:
128
127
  self.provider = provider
129
128
  self.product_type = kwargs.get("productType")
@@ -174,7 +173,7 @@ class EOProduct:
174
173
  self.downloader: Optional[Union[Api, Download]] = None
175
174
  self.downloader_auth: Optional[Authentication] = None
176
175
 
177
- def as_dict(self) -> Dict[str, Any]:
176
+ def as_dict(self) -> dict[str, Any]:
178
177
  """Builds a representation of EOProduct as a dictionary to enable its geojson
179
178
  serialization
180
179
 
@@ -185,7 +184,7 @@ class EOProduct:
185
184
  if self.search_intersection is not None:
186
185
  search_intersection = geometry.mapping(self.search_intersection)
187
186
 
188
- geojson_repr: Dict[str, Any] = {
187
+ geojson_repr: dict[str, Any] = {
189
188
  "type": "Feature",
190
189
  "geometry": geometry.mapping(self.geometry),
191
190
  "id": self.properties["id"],
@@ -205,7 +204,7 @@ class EOProduct:
205
204
  return geojson_repr
206
205
 
207
206
  @classmethod
208
- def from_geojson(cls, feature: Dict[str, Any]) -> EOProduct:
207
+ def from_geojson(cls, feature: dict[str, Any]) -> EOProduct:
209
208
  """Builds an :class:`~eodag.api.product._product.EOProduct` object from its
210
209
  representation as geojson
211
210
 
@@ -281,8 +280,8 @@ class EOProduct:
281
280
  def download(
282
281
  self,
283
282
  progress_callback: Optional[ProgressCallback] = None,
284
- wait: int = DEFAULT_DOWNLOAD_WAIT,
285
- timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
283
+ wait: float = DEFAULT_DOWNLOAD_WAIT,
284
+ timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
286
285
  **kwargs: Unpack[DownloadConf],
287
286
  ) -> str:
288
287
  """Download the EO product using the provided download plugin and the
@@ -355,7 +354,7 @@ class EOProduct:
355
354
 
356
355
  def _init_progress_bar(
357
356
  self, progress_callback: Optional[ProgressCallback]
358
- ) -> Tuple[ProgressCallback, bool]:
357
+ ) -> tuple[ProgressCallback, bool]:
359
358
  # progress bar init
360
359
  if progress_callback is None:
361
360
  progress_callback = ProgressCallback(position=1)
@@ -462,12 +461,20 @@ class EOProduct:
462
461
  )
463
462
  if not isinstance(auth, AuthBase):
464
463
  auth = None
464
+ # Read the ssl_verify parameter used on the provider config
465
+ # to ensure the same behavior for get_quicklook as other download functions
466
+ ssl_verify = (
467
+ getattr(self.downloader.config, "ssl_verify", True)
468
+ if self.downloader
469
+ else True
470
+ )
465
471
  with requests.get(
466
472
  self.properties["quicklook"],
467
473
  stream=True,
468
474
  auth=auth,
469
475
  headers=USER_AGENT,
470
476
  timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
477
+ verify=ssl_verify,
471
478
  ) as stream:
472
479
  try:
473
480
  stream.raise_for_status()
@@ -497,11 +504,16 @@ class EOProduct:
497
504
  try:
498
505
  for driver_conf in DRIVERS:
499
506
  if all([criteria(self) for criteria in driver_conf["criteria"]]):
500
- return driver_conf["driver"]
507
+ driver = driver_conf["driver"]
508
+ break
509
+ # use legacy driver for deprecated get_data method usage
510
+ for lecacy_conf in LEGACY_DRIVERS:
511
+ if all([criteria(self) for criteria in lecacy_conf["criteria"]]):
512
+ driver.legacy = lecacy_conf["driver"]
513
+ break
514
+ return driver
501
515
  except TypeError:
502
- logger.warning(
503
- "Drivers definition seems out-of-date, please update eodag-cube"
504
- )
516
+ logger.info("No driver matching")
505
517
  pass
506
518
  return NoDriver()
507
519
 
@@ -525,22 +537,21 @@ class EOProduct:
525
537
  <tr style='background-color: transparent;'>
526
538
  <td style='text-align: left; vertical-align: top;'>
527
539
  {dict_to_html_table({
528
- "provider": self.provider,
529
- "product_type": self.product_type,
530
- "properties[&quot;id&quot;]": self.properties.get('id', None),
531
- "properties[&quot;startTimeFromAscendingNode&quot;]": self.properties.get(
532
- 'startTimeFromAscendingNode', None
533
- ),
534
- "properties[&quot;completionTimeFromAscendingNode&quot;]": self.properties.get(
535
- 'completionTimeFromAscendingNode', None
536
- ),
537
- }, brackets=False)}
538
- <details><summary style='color: grey; margin-top: 10px;'>properties:&ensp;({
539
- len(self.properties)
540
- })</summary>{dict_to_html_table(self.properties, depth=1)}</details>
541
- <details><summary style='color: grey; margin-top: 10px;'>assets:&ensp;({
542
- len(self.assets)
543
- })</summary>{self.assets._repr_html_(embeded=True)}</details>
540
+ "provider": self.provider,
541
+ "product_type": self.product_type,
542
+ "properties[&quot;id&quot;]": self.properties.get('id', None),
543
+ "properties[&quot;startTimeFromAscendingNode&quot;]": self.properties.get(
544
+ 'startTimeFromAscendingNode', None
545
+ ),
546
+ "properties[&quot;completionTimeFromAscendingNode&quot;]": self.properties.get(
547
+ 'completionTimeFromAscendingNode', None
548
+ ),
549
+ }, brackets=False)}
550
+ <details><summary style='color: grey; margin-top: 10px;'>properties:&ensp;({len(
551
+ self.properties)})</summary>{
552
+ dict_to_html_table(self.properties, depth=1)}</details>
553
+ <details><summary style='color: grey; margin-top: 10px;'>assets:&ensp;({len(
554
+ self.assets)})</summary>{self.assets._repr_html_(embeded=True)}</details>
544
555
  </td>
545
556
  <td {geom_style} title='geometry'>geometry<br />{self.geometry._repr_svg_()}</td>
546
557
  <td {thumbnail_style} title='properties[&quot;thumbnail&quot;]'>{thumbnail_html}</td>
@@ -16,14 +16,91 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
  """EODAG drivers package"""
19
+ from __future__ import annotations
20
+
21
+ from typing import Callable, TypedDict
22
+
19
23
  from eodag.api.product.drivers.base import DatasetDriver, NoDriver
24
+ from eodag.api.product.drivers.generic import GenericDriver
25
+ from eodag.api.product.drivers.sentinel1 import Sentinel1Driver
26
+ from eodag.api.product.drivers.sentinel2 import Sentinel2Driver
20
27
 
21
28
  try:
22
- from eodag_cube.api.product.drivers import ( # pyright: ignore[reportMissingImports]
23
- DRIVERS,
29
+ # import from eodag-cube if installed
30
+ from eodag_cube.api.product.drivers.generic import ( # pyright: ignore[reportMissingImports]; isort: skip
31
+ GenericDriver as GenericDriver_cube,
32
+ )
33
+ from eodag_cube.api.product.drivers.sentinel2_l1c import ( # pyright: ignore[reportMissingImports]; isort: skip
34
+ Sentinel2L1C as Sentinel2L1C_cube,
35
+ )
36
+ from eodag_cube.api.product.drivers.stac_assets import ( # pyright: ignore[reportMissingImports]; isort: skip
37
+ StacAssets as StacAssets_cube,
24
38
  )
25
39
  except ImportError:
26
- DRIVERS = []
40
+ GenericDriver_cube = NoDriver
41
+ Sentinel2L1C_cube = NoDriver
42
+ StacAssets_cube = NoDriver
43
+
44
+
45
+ class DriverCriteria(TypedDict):
46
+ """Driver criteria definition"""
47
+
48
+ #: Function that returns True if the driver is suitable for the given :class:`~eodag.api.product._product.EOProduct`
49
+ criteria: list[Callable[..., bool]]
50
+ #: driver to use
51
+ driver: DatasetDriver
52
+
53
+
54
+ #: list of drivers and their criteria
55
+ DRIVERS: list[DriverCriteria] = [
56
+ {
57
+ "criteria": [
58
+ lambda prod: True
59
+ if (prod.product_type or "").startswith("S2_MSI_")
60
+ else False
61
+ ],
62
+ "driver": Sentinel2Driver(),
63
+ },
64
+ {
65
+ "criteria": [
66
+ lambda prod: True
67
+ if (prod.product_type or "").startswith("S1_SAR_")
68
+ else False
69
+ ],
70
+ "driver": Sentinel1Driver(),
71
+ },
72
+ {
73
+ "criteria": [lambda prod: True],
74
+ "driver": GenericDriver(),
75
+ },
76
+ ]
77
+
78
+
79
+ #: list of legacy drivers and their criteria
80
+ LEGACY_DRIVERS: list[DriverCriteria] = [
81
+ {
82
+ "criteria": [
83
+ lambda prod: True if len(getattr(prod, "assets", {})) > 0 else False
84
+ ],
85
+ "driver": StacAssets_cube(),
86
+ },
87
+ {
88
+ "criteria": [lambda prod: True if "assets" in prod.properties else False],
89
+ "driver": StacAssets_cube(),
90
+ },
91
+ {
92
+ "criteria": [
93
+ lambda prod: True
94
+ if getattr(prod, "product_type") == "S2_MSI_L1C"
95
+ else False
96
+ ],
97
+ "driver": Sentinel2L1C_cube(),
98
+ },
99
+ {
100
+ "criteria": [lambda prod: True],
101
+ "driver": GenericDriver_cube(),
102
+ },
103
+ ]
27
104
 
28
105
  # exportable content
29
- __all__ = ["DRIVERS", "DatasetDriver", "NoDriver"]
106
+ __all__ = ["DRIVERS", "DatasetDriver", "GenericDriver", "NoDriver", "Sentinel2Driver"]
@@ -17,15 +17,72 @@
17
17
  # limitations under the License.
18
18
  from __future__ import annotations
19
19
 
20
- from typing import TYPE_CHECKING
20
+ import logging
21
+ import re
22
+ from typing import TYPE_CHECKING, Optional, TypedDict
23
+
24
+ from eodag.utils import _deprecated
21
25
 
22
26
  if TYPE_CHECKING:
23
27
  from eodag.api.product import EOProduct
24
28
 
25
29
 
30
+ class AssetPatterns(TypedDict):
31
+ """Asset patterns definition"""
32
+
33
+ #: pattern to match and extract asset key
34
+ pattern: re.Pattern
35
+ #: roles associated to the asset key
36
+ roles: list[str]
37
+
38
+
39
+ logger = logging.getLogger("eodag.driver.base")
40
+
41
+
26
42
  class DatasetDriver(metaclass=type):
27
- """Dataset driver"""
43
+ """Parent class for all dataset drivers.
44
+
45
+ Drivers will provide methods adapted to a given :class:`~eodag.api.product._product.EOProduct` related to predefined
46
+ criteria.
47
+ """
48
+
49
+ #: legacy driver for deprecated :meth:`~eodag_cube.api.product._product.EOProduct.get_data` method usage
50
+ legacy: DatasetDriver
28
51
 
52
+ #: list of patterns to match asset keys and roles
53
+ ASSET_KEYS_PATTERNS_ROLES: list[AssetPatterns] = []
54
+
55
+ #: strip non-alphanumeric characters at the beginning and end of the key
56
+ STRIP_SPECIAL_PATTERN = re.compile(r"^[^A-Z0-9]+|[^A-Z0-9]+$", re.IGNORECASE)
57
+
58
+ def _normalize_key(self, key, eo_product):
59
+ # default cleanup
60
+ norm_key = key.replace(eo_product.properties.get("id", ""), "")
61
+ norm_key = re.sub(self.STRIP_SPECIAL_PATTERN, "", norm_key)
62
+
63
+ return norm_key
64
+
65
+ def guess_asset_key_and_roles(
66
+ self, href: str, eo_product: EOProduct
67
+ ) -> tuple[Optional[str], Optional[list[str]]]:
68
+ """Guess the asset key and roles from the given href.
69
+
70
+ :param href: The asset href
71
+ :param eo_product: The product to which the asset belongs
72
+ :returns: The asset key and roles
73
+ """
74
+ for pattern_dict in self.ASSET_KEYS_PATTERNS_ROLES:
75
+ if matched := pattern_dict["pattern"].match(href):
76
+ extracted_key, roles = (
77
+ "".join([m for m in matched.groups() if m is not None]),
78
+ pattern_dict.get("roles"),
79
+ )
80
+ normalized_key = self._normalize_key(extracted_key, eo_product)
81
+ return normalized_key or extracted_key, roles
82
+ logger.debug(f"No key & roles could be guessed for {href}")
83
+ return None, None
84
+
85
+ @_deprecated(reason="Method used by deprecated get_data", version="3.1.0")
29
86
  def get_data_address(self, eo_product: EOProduct, band: str) -> str:
30
87
  """Retrieve the address of the dataset represented by `eo_product`.
31
88
 
@@ -34,12 +91,16 @@ class DatasetDriver(metaclass=type):
34
91
  :returns: An address for the dataset
35
92
  :raises: :class:`~eodag.utils.exceptions.AddressNotFound`
36
93
  :raises: :class:`~eodag.utils.exceptions.UnsupportedDatasetAddressScheme`
94
+
95
+ .. deprecated:: 3.1.0
96
+ Method used by deprecated :meth:`~eodag_cube.api.product._product.EOProduct.get_data`
37
97
  """
38
98
  raise NotImplementedError
39
99
 
40
100
 
41
101
  class NoDriver(DatasetDriver):
42
- """A default driver that does not implement any of the methods it should implement, used for all product types for
43
- which the :meth:`~eodag.api.product._product.EOProduct.get_data` method is not yet implemented in eodag. Expect a
102
+ """A default :attr:`~eodag.api.product.drivers.base.DatasetDriver.legacy` driver that does not implement any of the
103
+ methods it should implement, used for all product types for which the deprecated
104
+ :meth:`~eodag_cube.api.product._product.EOProduct.get_data` method is not implemented. Expect a
44
105
  :exc:`NotImplementedError` when trying to get the data in that case.
45
106
  """
@@ -0,0 +1,65 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright 2021, CS GROUP - France, http://www.c-s.fr
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 re
22
+
23
+ from eodag.api.product.drivers.base import AssetPatterns, DatasetDriver
24
+
25
+ logger = logging.getLogger("eodag.driver.generic")
26
+
27
+
28
+ class GenericDriver(DatasetDriver):
29
+ """Generic default Driver"""
30
+
31
+ #: list of patterns to match asset keys and roles
32
+ ASSET_KEYS_PATTERNS_ROLES: list[AssetPatterns] = [
33
+ # data
34
+ {
35
+ "pattern": re.compile(
36
+ r"^(?:.*[/\\])?([^/\\]+)(\.jp2|\.tiff?|\.dat|\.nc|\.grib2?)$",
37
+ re.IGNORECASE,
38
+ ),
39
+ "roles": ["data"],
40
+ },
41
+ # metadata
42
+ {
43
+ "pattern": re.compile(
44
+ r"^(?:.*[/\\])?([^/\\]+)(\.xml|\.xsd|\.safe|\.json)$", re.IGNORECASE
45
+ ),
46
+ "roles": ["metadata"],
47
+ },
48
+ # thumbnail
49
+ {
50
+ "pattern": re.compile(
51
+ r"^(?:.*[/\\])?(thumbnail)(\.jpg|\.jpeg|\.png)$", re.IGNORECASE
52
+ ),
53
+ "roles": ["thumbnail"],
54
+ },
55
+ # quicklook
56
+ {
57
+ "pattern": re.compile(
58
+ r"^(?:.*[/\\])?([^/\\]+-ql|preview|quick-?look)(\.jpg|\.jpeg|\.png)$",
59
+ re.IGNORECASE,
60
+ ),
61
+ "roles": ["overview"],
62
+ },
63
+ # default
64
+ {"pattern": re.compile(r"^(?:.*[/\\])?([^/\\]+)$"), "roles": ["auxiliary"]},
65
+ ]
@@ -0,0 +1,97 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright 2025, CS GROUP - France, http://www.c-s.fr
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 re
21
+ from typing import TYPE_CHECKING
22
+
23
+ from eodag.api.product.drivers.base import AssetPatterns, DatasetDriver
24
+
25
+ if TYPE_CHECKING:
26
+ from eodag.api.product._product import EOProduct
27
+
28
+
29
+ class Sentinel1Driver(DatasetDriver):
30
+ """Driver for Sentinel1 products"""
31
+
32
+ #: pattern to match data-role keys
33
+ DATA_PATTERN = re.compile(r"[vh]{2}", re.IGNORECASE)
34
+
35
+ #: list of patterns to replace in asset keys
36
+ REPLACE_PATTERNS = [
37
+ (re.compile(r"s1a?", re.IGNORECASE), ""),
38
+ (re.compile(r"grd", re.IGNORECASE), ""),
39
+ (re.compile(r"slc", re.IGNORECASE), ""),
40
+ (re.compile(r"ocn", re.IGNORECASE), ""),
41
+ (re.compile(r"iw", re.IGNORECASE), ""),
42
+ (re.compile(r"ew", re.IGNORECASE), ""),
43
+ (re.compile(r"wv", re.IGNORECASE), ""),
44
+ (re.compile(r"sm", re.IGNORECASE), ""),
45
+ (re.compile(r"raw([-_]s)?", re.IGNORECASE), ""),
46
+ (re.compile(r"[t?0-9]{3,}", re.IGNORECASE), ""),
47
+ (re.compile(r"-+"), "-"),
48
+ (re.compile(r"-+\."), "."),
49
+ (re.compile(r"_+"), "_"),
50
+ (re.compile(r"_+\."), "."),
51
+ ]
52
+
53
+ #: list of patterns to match asset keys and roles
54
+ ASSET_KEYS_PATTERNS_ROLES: list[AssetPatterns] = [
55
+ # data
56
+ {
57
+ "pattern": re.compile(
58
+ r"^.*?([vh]{2}).*\.(?:jp2|tiff?|dat)$", re.IGNORECASE
59
+ ),
60
+ "roles": ["data"],
61
+ },
62
+ # metadata
63
+ {
64
+ "pattern": re.compile(
65
+ r"^(?:.*[/\\])?([^/\\]+)(\.xml|\.xsd|\.safe|\.json)$", re.IGNORECASE
66
+ ),
67
+ "roles": ["metadata"],
68
+ },
69
+ # thumbnail
70
+ {
71
+ "pattern": re.compile(
72
+ r"^(?:.*[/\\])?(thumbnail)(\.jpe?g|\.png)$", re.IGNORECASE
73
+ ),
74
+ "roles": ["thumbnail"],
75
+ },
76
+ # quicklook
77
+ {
78
+ "pattern": re.compile(
79
+ r"^(?:.*[/\\])?([^/\\]+-ql|preview|quick-?look)(\.jpe?g|\.png)$",
80
+ re.IGNORECASE,
81
+ ),
82
+ "roles": ["overview"],
83
+ },
84
+ # default
85
+ {"pattern": re.compile(r"^(?:.*[/\\])?([^/\\]+)$"), "roles": ["auxiliary"]},
86
+ ]
87
+
88
+ def _normalize_key(self, key: str, eo_product: EOProduct) -> str:
89
+ if self.DATA_PATTERN.fullmatch(key):
90
+ return key.upper()
91
+
92
+ key = super()._normalize_key(key, eo_product)
93
+
94
+ for pattern, replacement in self.REPLACE_PATTERNS:
95
+ key = pattern.sub(replacement, key)
96
+
97
+ return super()._normalize_key(key, eo_product)