eodag 2.12.0__py3-none-any.whl → 3.0.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 (93) hide show
  1. eodag/__init__.py +6 -8
  2. eodag/api/core.py +654 -538
  3. eodag/api/product/__init__.py +12 -2
  4. eodag/api/product/_assets.py +59 -16
  5. eodag/api/product/_product.py +100 -93
  6. eodag/api/product/drivers/__init__.py +7 -2
  7. eodag/api/product/drivers/base.py +0 -3
  8. eodag/api/product/metadata_mapping.py +192 -96
  9. eodag/api/search_result.py +69 -10
  10. eodag/cli.py +55 -25
  11. eodag/config.py +391 -116
  12. eodag/plugins/apis/base.py +11 -165
  13. eodag/plugins/apis/ecmwf.py +36 -25
  14. eodag/plugins/apis/usgs.py +80 -35
  15. eodag/plugins/authentication/aws_auth.py +13 -4
  16. eodag/plugins/authentication/base.py +10 -1
  17. eodag/plugins/authentication/generic.py +2 -2
  18. eodag/plugins/authentication/header.py +31 -6
  19. eodag/plugins/authentication/keycloak.py +17 -84
  20. eodag/plugins/authentication/oauth.py +3 -3
  21. eodag/plugins/authentication/openid_connect.py +268 -49
  22. eodag/plugins/authentication/qsauth.py +4 -1
  23. eodag/plugins/authentication/sas_auth.py +9 -2
  24. eodag/plugins/authentication/token.py +98 -47
  25. eodag/plugins/authentication/token_exchange.py +122 -0
  26. eodag/plugins/crunch/base.py +3 -1
  27. eodag/plugins/crunch/filter_date.py +3 -9
  28. eodag/plugins/crunch/filter_latest_intersect.py +0 -3
  29. eodag/plugins/crunch/filter_latest_tpl_name.py +1 -4
  30. eodag/plugins/crunch/filter_overlap.py +4 -8
  31. eodag/plugins/crunch/filter_property.py +5 -11
  32. eodag/plugins/download/aws.py +149 -185
  33. eodag/plugins/download/base.py +88 -97
  34. eodag/plugins/download/creodias_s3.py +1 -1
  35. eodag/plugins/download/http.py +638 -310
  36. eodag/plugins/download/s3rest.py +47 -45
  37. eodag/plugins/manager.py +228 -88
  38. eodag/plugins/search/__init__.py +36 -0
  39. eodag/plugins/search/base.py +239 -30
  40. eodag/plugins/search/build_search_result.py +382 -37
  41. eodag/plugins/search/cop_marine.py +441 -0
  42. eodag/plugins/search/creodias_s3.py +25 -20
  43. eodag/plugins/search/csw.py +5 -7
  44. eodag/plugins/search/data_request_search.py +61 -30
  45. eodag/plugins/search/qssearch.py +713 -255
  46. eodag/plugins/search/static_stac_search.py +106 -40
  47. eodag/resources/ext_product_types.json +1 -1
  48. eodag/resources/product_types.yml +1921 -34
  49. eodag/resources/providers.yml +4091 -3655
  50. eodag/resources/stac.yml +50 -216
  51. eodag/resources/stac_api.yml +71 -25
  52. eodag/resources/stac_provider.yml +5 -0
  53. eodag/resources/user_conf_template.yml +89 -32
  54. eodag/rest/__init__.py +6 -0
  55. eodag/rest/cache.py +70 -0
  56. eodag/rest/config.py +68 -0
  57. eodag/rest/constants.py +26 -0
  58. eodag/rest/core.py +735 -0
  59. eodag/rest/errors.py +178 -0
  60. eodag/rest/server.py +264 -431
  61. eodag/rest/stac.py +442 -836
  62. eodag/rest/types/collections_search.py +44 -0
  63. eodag/rest/types/eodag_search.py +238 -47
  64. eodag/rest/types/queryables.py +164 -0
  65. eodag/rest/types/stac_search.py +273 -0
  66. eodag/rest/utils/__init__.py +216 -0
  67. eodag/rest/utils/cql_evaluate.py +119 -0
  68. eodag/rest/utils/rfc3339.py +64 -0
  69. eodag/types/__init__.py +106 -10
  70. eodag/types/bbox.py +15 -14
  71. eodag/types/download_args.py +40 -0
  72. eodag/types/search_args.py +57 -7
  73. eodag/types/whoosh.py +79 -0
  74. eodag/utils/__init__.py +110 -91
  75. eodag/utils/constraints.py +37 -45
  76. eodag/utils/exceptions.py +39 -22
  77. eodag/utils/import_system.py +0 -4
  78. eodag/utils/logging.py +37 -80
  79. eodag/utils/notebook.py +4 -4
  80. eodag/utils/repr.py +113 -0
  81. eodag/utils/requests.py +128 -0
  82. eodag/utils/rest.py +100 -0
  83. eodag/utils/stac_reader.py +93 -21
  84. {eodag-2.12.0.dist-info → eodag-3.0.0.dist-info}/METADATA +88 -53
  85. eodag-3.0.0.dist-info/RECORD +109 -0
  86. {eodag-2.12.0.dist-info → eodag-3.0.0.dist-info}/WHEEL +1 -1
  87. {eodag-2.12.0.dist-info → eodag-3.0.0.dist-info}/entry_points.txt +7 -5
  88. eodag/plugins/apis/cds.py +0 -540
  89. eodag/rest/types/stac_queryables.py +0 -134
  90. eodag/rest/utils.py +0 -1133
  91. eodag-2.12.0.dist-info/RECORD +0 -94
  92. {eodag-2.12.0.dist-info → eodag-3.0.0.dist-info}/LICENSE +0 -0
  93. {eodag-2.12.0.dist-info → eodag-3.0.0.dist-info}/top_level.txt +0 -0
@@ -15,8 +15,18 @@
15
15
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
+ #
18
19
  """EODAG product package"""
19
20
  try:
20
- from eodag_cube.api.product import EOProduct # noqa
21
+ # import from eodag-cube if installed
22
+ from eodag_cube.api.product import ( # pyright: ignore[reportMissingImports]
23
+ Asset,
24
+ AssetsDict,
25
+ EOProduct,
26
+ )
21
27
  except ImportError:
22
- from ._product import EOProduct # noqa
28
+ from ._assets import Asset, AssetsDict # type: ignore[assignment]
29
+ from ._product import EOProduct # type: ignore[assignment]
30
+
31
+ # exportable content
32
+ __all__ = ["Asset", "AssetsDict", "EOProduct"]
@@ -19,12 +19,15 @@ 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
22
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
23
23
 
24
24
  from eodag.utils.exceptions import NotAvailableError
25
+ from eodag.utils.repr import dict_to_html_table
25
26
 
26
27
  if TYPE_CHECKING:
27
28
  from eodag.api.product import EOProduct
29
+ from eodag.types.download_args import DownloadConf
30
+ from eodag.utils import Unpack
28
31
 
29
32
 
30
33
  class AssetsDict(UserDict):
@@ -32,11 +35,8 @@ class AssetsDict(UserDict):
32
35
  :class:`~eodag.api.product._product.EOProduct` resulting from a search.
33
36
 
34
37
  :param product: Product resulting from a search
35
- :type product: :class:`~eodag.api.product._product.EOProduct`
36
38
  :param args: (optional) Arguments used to init the dictionary
37
- :type args: Any
38
39
  :param kwargs: (optional) Additional named-arguments used to init the dictionary
39
- :type kwargs: Any
40
40
  """
41
41
 
42
42
  product: EOProduct
@@ -53,17 +53,15 @@ class AssetsDict(UserDict):
53
53
 
54
54
  :returns: The representation of a :class:`~eodag.api.product._assets.AssetsDict`
55
55
  as a Python dict
56
- :rtype: dict
57
56
  """
58
57
  return {k: v.as_dict() for k, v in self.data.items()}
59
58
 
60
59
  def get_values(self, asset_filter: str = "") -> List[Asset]:
61
60
  """
62
61
  retrieves the assets matching the given filter
63
- :param asset_filter: filter with which the assets should be matched
64
- :type asset_filter: str
62
+
63
+ :param asset_filter: regex filter with which the assets should be matched
65
64
  :return: list of assets
66
- :rtype: List[Asset]
67
65
  """
68
66
  if asset_filter:
69
67
  filter_regex = re.compile(asset_filter)
@@ -82,22 +80,58 @@ class AssetsDict(UserDict):
82
80
  else:
83
81
  return [a for a in self.values() if "href" in a]
84
82
 
83
+ def _repr_html_(self, embeded=False):
84
+ thead = (
85
+ f"""<thead><tr><td style='text-align: left; color: grey;'>
86
+ {type(self).__name__}&ensp;({len(self)})
87
+ </td></tr></thead>
88
+ """
89
+ if not embeded
90
+ else ""
91
+ )
92
+ tr_style = "style='background-color: transparent;'" if embeded else ""
93
+ return (
94
+ f"<table>{thead}"
95
+ + "".join(
96
+ [
97
+ f"""<tr {tr_style}><td style='text-align: left;'>
98
+ <details><summary style='color: grey;'>
99
+ <span style='color: black'>'{k}'</span>:&ensp;
100
+ {{
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 ""}
107
+ ...
108
+ }}
109
+ </summary>
110
+ {dict_to_html_table(v, depth=1)}
111
+ </details>
112
+ </td></tr>
113
+ """
114
+ for k, v in self.items()
115
+ ]
116
+ )
117
+ + "</table>"
118
+ )
119
+
85
120
 
86
121
  class Asset(UserDict):
87
122
  """A UserDict object containg one of the assets of a
88
123
  :class:`~eodag.api.product._product.EOProduct` resulting from a search.
89
124
 
90
125
  :param product: Product resulting from a search
91
- :type product: :class:`~eodag.api.product._product.EOProduct`
92
126
  :param key: asset key
93
- :type key: str
94
127
  :param args: (optional) Arguments used to init the dictionary
95
- :type args: Any
96
128
  :param kwargs: (optional) Additional named-arguments used to init the dictionary
97
- :type kwargs: Any
98
129
  """
99
130
 
100
131
  product: EOProduct
132
+ size: int
133
+ filename: Optional[str]
134
+ rel_path: str
101
135
 
102
136
  def __init__(self, product: EOProduct, key: str, *args: Any, **kwargs: Any) -> None:
103
137
  self.product = product
@@ -109,16 +143,25 @@ class Asset(UserDict):
109
143
 
110
144
  :returns: The representation of a :class:`~eodag.api.product._assets.Asset` as a
111
145
  Python dict
112
- :rtype: dict
113
146
  """
114
147
  return self.data
115
148
 
116
- def download(self, **kwargs: Any) -> str:
149
+ def download(self, **kwargs: Unpack[DownloadConf]) -> str:
117
150
  """Downloads a single asset
118
151
 
119
152
  :param kwargs: (optional) Additional named-arguments passed to `plugin.download()`
120
- :type kwargs: Any
121
153
  :returns: The absolute path to the downloaded product on the local filesystem
122
- :rtype: str
123
154
  """
124
155
  return self.product.download(asset=self.key, **kwargs)
156
+
157
+ def _repr_html_(self):
158
+ thead = f"""<thead><tr><td style='text-align: left; color: grey;'>
159
+ {type(self).__name__}&ensp;-&ensp;{self.key}
160
+ </td></tr></thead>
161
+ """
162
+ return f"""<table>{thead}
163
+ <tr><td style='text-align: left;'>
164
+ {dict_to_html_table(self)}
165
+ </details>
166
+ </td></tr>
167
+ </table>"""
@@ -21,17 +21,29 @@ import base64
21
21
  import logging
22
22
  import os
23
23
  import re
24
- import urllib.parse
24
+ import tempfile
25
25
  from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union
26
26
 
27
27
  import requests
28
28
  from requests import RequestException
29
- from shapely import geometry, wkb, wkt
29
+ from requests.auth import AuthBase
30
+ from shapely import geometry
30
31
  from shapely.errors import ShapelyError
31
32
 
32
- from eodag.api.product._assets import AssetsDict
33
+ try:
34
+ # import from eodag-cube if installed
35
+ from eodag_cube.api.product import ( # pyright: ignore[reportMissingImports]
36
+ AssetsDict,
37
+ )
38
+ except ImportError:
39
+ from eodag.api.product._assets import AssetsDict
40
+
33
41
  from eodag.api.product.drivers import DRIVERS, NoDriver
34
- from eodag.api.product.metadata_mapping import NOT_AVAILABLE, NOT_MAPPED
42
+ from eodag.api.product.metadata_mapping import (
43
+ DEFAULT_GEOMETRY,
44
+ NOT_AVAILABLE,
45
+ NOT_MAPPED,
46
+ )
35
47
  from eodag.utils import (
36
48
  DEFAULT_DOWNLOAD_TIMEOUT,
37
49
  DEFAULT_DOWNLOAD_WAIT,
@@ -41,6 +53,7 @@ from eodag.utils import (
41
53
  get_geometry_from_various,
42
54
  )
43
55
  from eodag.utils.exceptions import DownloadError, MisconfiguredError
56
+ from eodag.utils.repr import dict_to_html_table
44
57
 
45
58
  if TYPE_CHECKING:
46
59
  from shapely.geometry.base import BaseGeometry
@@ -49,6 +62,8 @@ if TYPE_CHECKING:
49
62
  from eodag.plugins.apis.base import Api
50
63
  from eodag.plugins.authentication.base import Authentication
51
64
  from eodag.plugins.download.base import Download
65
+ from eodag.types.download_args import DownloadConf
66
+ from eodag.utils import Unpack
52
67
 
53
68
  try:
54
69
  from shapely.errors import GEOSException
@@ -73,9 +88,7 @@ class EOProduct:
73
88
  parameters that led to its creation.
74
89
 
75
90
  :param provider: The provider from which the product originates
76
- :type provider: str
77
91
  :param properties: The metadata of the product
78
- :type properties: dict
79
92
  :ivar product_type: The product type
80
93
  :vartype product_type: str
81
94
  :ivar location: The path to the product, either remote or local if downloaded
@@ -133,40 +146,15 @@ class EOProduct:
133
146
  raise MisconfiguredError(
134
147
  f"No geometry available to build EOProduct(id={properties.get('id', None)}, provider={provider})"
135
148
  )
136
- elif properties["geometry"] == NOT_AVAILABLE:
137
- product_geometry = properties.pop("defaultGeometry")
149
+ elif not properties["geometry"] or properties["geometry"] == NOT_AVAILABLE:
150
+ product_geometry = properties.pop("defaultGeometry", DEFAULT_GEOMETRY)
138
151
  else:
139
152
  product_geometry = properties["geometry"]
140
- # Let's try 'latmin lonmin latmax lonmax'
141
- if isinstance(product_geometry, str):
142
- bbox_pattern = re.compile(
143
- r"^(-?\d+\.?\d*) (-?\d+\.?\d*) (-?\d+\.?\d*) (-?\d+\.?\d*)$"
144
- )
145
- found_bbox = bbox_pattern.match(product_geometry)
146
- if found_bbox:
147
- coords = found_bbox.groups()
148
- if len(coords) == 4:
149
- product_geometry = geometry.box(
150
- float(coords[1]),
151
- float(coords[0]),
152
- float(coords[3]),
153
- float(coords[2]),
154
- )
155
- # Best effort to understand provider specific geometry (the default is to
156
- # assume an object implementing the Geo Interface: see
157
- # https://gist.github.com/2217756)
158
- if isinstance(product_geometry, str):
159
- try:
160
- product_geometry = wkt.loads(product_geometry)
161
- except (ShapelyError, GEOSException):
162
- try:
163
- product_geometry = wkb.loads(product_geometry)
164
- # Also catching TypeError because product_geometry can be a
165
- # string and not a bytes string
166
- except (ShapelyError, GEOSException, TypeError):
167
- # Giv up!
168
- raise
169
- self.geometry = self.search_intersection = geometry.shape(product_geometry)
153
+
154
+ self.geometry = self.search_intersection = get_geometry_from_various(
155
+ geometry=product_geometry
156
+ )
157
+
170
158
  self.search_kwargs = kwargs
171
159
  if self.search_kwargs.get("geometry") is not None:
172
160
  searched_geom = get_geometry_from_various(
@@ -192,7 +180,6 @@ class EOProduct:
192
180
 
193
181
  :returns: The representation of a :class:`~eodag.api.product._product.EOProduct` as a
194
182
  Python dict
195
- :rtype: dict
196
183
  """
197
184
  search_intersection = None
198
185
  if self.search_intersection is not None:
@@ -224,9 +211,7 @@ class EOProduct:
224
211
 
225
212
  :param feature: The representation of a :class:`~eodag.api.product._product.EOProduct`
226
213
  as a Python dict
227
- :type feature: dict
228
214
  :returns: An instance of :class:`~eodag.api.product._product.EOProduct`
229
- :rtype: :class:`~eodag.api.product._product.EOProduct`
230
215
  """
231
216
  properties = feature["properties"]
232
217
  properties["geometry"] = feature["geometry"]
@@ -260,11 +245,9 @@ class EOProduct:
260
245
  """Give to the product the information needed to download itself.
261
246
 
262
247
  :param downloader: The download method that it can use
263
- :type downloader: Concrete subclass of
264
248
  :class:`~eodag.plugins.download.base.Download` or
265
249
  :class:`~eodag.plugins.api.base.Api`
266
250
  :param authenticator: The authentication method needed to perform the download
267
- :type authenticator: Concrete subclass of
268
251
  :class:`~eodag.plugins.authentication.base.Authentication`
269
252
  """
270
253
  self.downloader = downloader
@@ -273,31 +256,23 @@ class EOProduct:
273
256
  # resolve locations and properties if needed with downloader configuration
274
257
  location_attrs = ("location", "remote_location")
275
258
  for location_attr in location_attrs:
276
- try:
277
- setattr(
278
- self,
279
- location_attr,
280
- urllib.parse.unquote(getattr(self, location_attr))
281
- % vars(self.downloader.config),
282
- )
283
- except ValueError as e:
284
- logger.debug(
285
- f"Could not resolve product.{location_attr} ({getattr(self, location_attr)})"
286
- f" in register_downloader: {str(e)}"
287
- )
259
+ if "%(" in getattr(self, location_attr):
260
+ try:
261
+ setattr(
262
+ self,
263
+ location_attr,
264
+ getattr(self, location_attr) % vars(self.downloader.config),
265
+ )
266
+ except ValueError as e:
267
+ logger.debug(
268
+ f"Could not resolve product.{location_attr} ({getattr(self, location_attr)})"
269
+ f" in register_downloader: {str(e)}"
270
+ )
288
271
 
289
272
  for k, v in self.properties.items():
290
- if isinstance(v, str):
273
+ if isinstance(v, str) and "%(" in v:
291
274
  try:
292
- if "%" in v:
293
- parsed = urllib.parse.urlparse(v)
294
- prop = urllib.parse.unquote(parsed.path) % vars(
295
- self.downloader.config
296
- )
297
- parsed = parsed._replace(path=urllib.parse.quote(prop))
298
- self.properties[k] = urllib.parse.urlunparse(parsed)
299
- else:
300
- self.properties[k] = v % vars(self.downloader.config)
275
+ self.properties[k] = v % vars(self.downloader.config)
301
276
  except (TypeError, ValueError) as e:
302
277
  logger.debug(
303
278
  f"Could not resolve {k} property ({v}) in register_downloader: {str(e)}"
@@ -308,7 +283,7 @@ class EOProduct:
308
283
  progress_callback: Optional[ProgressCallback] = None,
309
284
  wait: int = DEFAULT_DOWNLOAD_WAIT,
310
285
  timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
311
- **kwargs: Any,
286
+ **kwargs: Unpack[DownloadConf],
312
287
  ) -> str:
313
288
  """Download the EO product using the provided download plugin and the
314
289
  authenticator if necessary.
@@ -322,20 +297,15 @@ class EOProduct:
322
297
  size as inputs and handle progress bar
323
298
  creation and update to give the user a
324
299
  feedback on the download progress
325
- :type progress_callback: :class:`~eodag.utils.ProgressCallback` or None
326
300
  :param wait: (optional) If download fails, wait time in minutes between
327
301
  two download tries
328
- :type wait: int
329
302
  :param timeout: (optional) If download fails, maximum time in minutes
330
303
  before stop retrying to download
331
- :type timeout: int
332
- :param kwargs: `outputs_prefix` (str), `extract` (bool), `delete_archive` (bool)
304
+ :param kwargs: `output_dir` (str), `extract` (bool), `delete_archive` (bool)
333
305
  and `dl_url_params` (dict) can be provided as additional kwargs
334
306
  and will override any other values defined in a configuration
335
307
  file or with environment variables.
336
- :type kwargs: Union[str, bool, dict]
337
308
  :returns: The absolute path to the downloaded product on the local filesystem
338
- :rtype: str
339
309
  :raises: :class:`~eodag.utils.exceptions.PluginImplementationError`
340
310
  :raises: :class:`RuntimeError`
341
311
  """
@@ -351,13 +321,6 @@ class EOProduct:
351
321
  else self.downloader_auth
352
322
  )
353
323
 
354
- # resolve remote location if needed with downloader configuration
355
- self.remote_location = urllib.parse.unquote(self.remote_location) % vars(
356
- self.downloader.config
357
- )
358
- if not self.location.startswith("file"):
359
- self.location = urllib.parse.unquote(self.location)
360
-
361
324
  progress_callback, close_progress_callback = self._init_progress_bar(
362
325
  progress_callback
363
326
  )
@@ -410,7 +373,7 @@ class EOProduct:
410
373
  def get_quicklook(
411
374
  self,
412
375
  filename: Optional[str] = None,
413
- base_dir: Optional[str] = None,
376
+ output_dir: Optional[str] = None,
414
377
  progress_callback: Optional[ProgressCallback] = None,
415
378
  ) -> str:
416
379
  """Download the quicklook image of a given EOProduct from its provider if it
@@ -418,18 +381,14 @@ class EOProduct:
418
381
 
419
382
  :param filename: (optional) The name to give to the downloaded quicklook. If not
420
383
  given, it defaults to the product's ID (without file extension).
421
- :type filename: str
422
- :param base_dir: (optional) The absolute path of the directory where to store
384
+ :param output_dir: (optional) The absolute path of the directory where to store
423
385
  the quicklooks in the filesystem. If not given, it defaults to the
424
- `quicklooks` directory under this EO product downloader's ``outputs_prefix``
386
+ `quicklooks` directory under this EO product downloader's ``output_dir``
425
387
  config param (e.g. '/tmp/quicklooks/')
426
- :type base_dir: str
427
388
  :param progress_callback: (optional) A method or a callable object which takes
428
389
  a current size and a maximum size as inputs and handle progress bar
429
390
  creation and update to give the user a feedback on the download progress
430
- :type progress_callback: :class:`~eodag.utils.ProgressCallback` or None
431
391
  :returns: The absolute path of the downloaded quicklook
432
- :rtype: str
433
392
  """
434
393
 
435
394
  def format_quicklook_address() -> None:
@@ -466,16 +425,20 @@ class EOProduct:
466
425
 
467
426
  format_quicklook_address()
468
427
 
469
- if base_dir is not None:
470
- quicklooks_base_dir = os.path.abspath(os.path.realpath(base_dir))
428
+ if output_dir is not None:
429
+ quicklooks_output_dir = os.path.abspath(os.path.realpath(output_dir))
471
430
  else:
472
- quicklooks_base_dir = os.path.join(
473
- self.downloader.config.outputs_prefix, "quicklooks"
431
+ tempdir = tempfile.gettempdir()
432
+ downloader_output_dir = (
433
+ getattr(self.downloader.config, "output_dir", tempdir)
434
+ if self.downloader
435
+ else tempdir
474
436
  )
475
- if not os.path.isdir(quicklooks_base_dir):
476
- os.makedirs(quicklooks_base_dir)
437
+ quicklooks_output_dir = os.path.join(downloader_output_dir, "quicklooks")
438
+ if not os.path.isdir(quicklooks_output_dir):
439
+ os.makedirs(quicklooks_output_dir)
477
440
  quicklook_file = os.path.join(
478
- quicklooks_base_dir,
441
+ quicklooks_output_dir,
479
442
  filename if filename is not None else self.properties["id"],
480
443
  )
481
444
 
@@ -497,6 +460,8 @@ class EOProduct:
497
460
  if self.downloader_auth is not None
498
461
  else None
499
462
  )
463
+ if not isinstance(auth, AuthBase):
464
+ auth = None
500
465
  with requests.get(
501
466
  self.properties["quicklook"],
502
467
  stream=True,
@@ -539,3 +504,45 @@ class EOProduct:
539
504
  )
540
505
  pass
541
506
  return NoDriver()
507
+
508
+ def _repr_html_(self):
509
+ thumbnail = self.properties.get("thumbnail", None)
510
+ thumbnail_html = (
511
+ f"<img src='{thumbnail}' width=100 alt='thumbnail'/>"
512
+ if thumbnail and not thumbnail.startswith("s3")
513
+ else ""
514
+ )
515
+ geom_style = "style='color: grey; text-align: center; min-width:100px; vertical-align: top;'"
516
+ thumbnail_style = (
517
+ "style='padding-top: 1.5em; min-width:100px; vertical-align: top;'"
518
+ )
519
+
520
+ return f"""<table>
521
+ <thead><tr style='background-color: transparent;'><td style='text-align: left; color: grey;'>
522
+ {type(self).__name__}
523
+ </td></tr></thead>
524
+
525
+ <tr style='background-color: transparent;'>
526
+ <td style='text-align: left; vertical-align: top;'>
527
+ {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>
544
+ </td>
545
+ <td {geom_style} title='geometry'>geometry<br />{self.geometry._repr_svg_()}</td>
546
+ <td {thumbnail_style} title='properties[&quot;thumbnail&quot;]'>{thumbnail_html}</td>
547
+ </tr>
548
+ </table>"""
@@ -16,9 +16,14 @@
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 eodag.api.product.drivers.base import DatasetDriver, NoDriver # noqa
19
+ from eodag.api.product.drivers.base import DatasetDriver, NoDriver
20
20
 
21
21
  try:
22
- from eodag_cube.api.product.drivers import DRIVERS
22
+ from eodag_cube.api.product.drivers import ( # pyright: ignore[reportMissingImports]
23
+ DRIVERS,
24
+ )
23
25
  except ImportError:
24
26
  DRIVERS = []
27
+
28
+ # exportable content
29
+ __all__ = ["DRIVERS", "DatasetDriver", "NoDriver"]
@@ -30,11 +30,8 @@ class DatasetDriver(metaclass=type):
30
30
  """Retrieve the address of the dataset represented by `eo_product`.
31
31
 
32
32
  :param eo_product: The product whom underlying dataset address is to be retrieved
33
- :type eo_product: :class:`~eodag.api.product._product.EOProduct`
34
33
  :param band: The band to retrieve (e.g: 'B01')
35
- :type band: str
36
34
  :returns: An address for the dataset
37
- :rtype: str
38
35
  :raises: :class:`~eodag.utils.exceptions.AddressNotFound`
39
36
  :raises: :class:`~eodag.utils.exceptions.UnsupportedDatasetAddressScheme`
40
37
  """