eodag 2.12.1__py3-none-any.whl → 3.0.0b2__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 (78) hide show
  1. eodag/api/core.py +440 -321
  2. eodag/api/product/__init__.py +5 -1
  3. eodag/api/product/_assets.py +57 -2
  4. eodag/api/product/_product.py +89 -68
  5. eodag/api/product/metadata_mapping.py +181 -66
  6. eodag/api/search_result.py +48 -1
  7. eodag/cli.py +20 -6
  8. eodag/config.py +95 -6
  9. eodag/plugins/apis/base.py +8 -165
  10. eodag/plugins/apis/ecmwf.py +36 -24
  11. eodag/plugins/apis/usgs.py +40 -24
  12. eodag/plugins/authentication/aws_auth.py +2 -2
  13. eodag/plugins/authentication/header.py +31 -6
  14. eodag/plugins/authentication/keycloak.py +13 -84
  15. eodag/plugins/authentication/oauth.py +3 -3
  16. eodag/plugins/authentication/openid_connect.py +256 -46
  17. eodag/plugins/authentication/qsauth.py +3 -0
  18. eodag/plugins/authentication/sas_auth.py +8 -1
  19. eodag/plugins/authentication/token.py +92 -46
  20. eodag/plugins/authentication/token_exchange.py +120 -0
  21. eodag/plugins/download/aws.py +86 -91
  22. eodag/plugins/download/base.py +72 -40
  23. eodag/plugins/download/http.py +607 -264
  24. eodag/plugins/download/s3rest.py +28 -15
  25. eodag/plugins/manager.py +74 -57
  26. eodag/plugins/search/__init__.py +36 -0
  27. eodag/plugins/search/base.py +225 -18
  28. eodag/plugins/search/build_search_result.py +389 -32
  29. eodag/plugins/search/cop_marine.py +378 -0
  30. eodag/plugins/search/creodias_s3.py +15 -14
  31. eodag/plugins/search/csw.py +5 -7
  32. eodag/plugins/search/data_request_search.py +44 -20
  33. eodag/plugins/search/qssearch.py +508 -203
  34. eodag/plugins/search/static_stac_search.py +99 -36
  35. eodag/resources/constraints/climate-dt.json +13 -0
  36. eodag/resources/constraints/extremes-dt.json +8 -0
  37. eodag/resources/ext_product_types.json +1 -1
  38. eodag/resources/product_types.yml +1897 -34
  39. eodag/resources/providers.yml +3539 -3277
  40. eodag/resources/stac.yml +48 -54
  41. eodag/resources/stac_api.yml +71 -25
  42. eodag/resources/stac_provider.yml +5 -0
  43. eodag/resources/user_conf_template.yml +51 -3
  44. eodag/rest/__init__.py +6 -0
  45. eodag/rest/cache.py +70 -0
  46. eodag/rest/config.py +68 -0
  47. eodag/rest/constants.py +27 -0
  48. eodag/rest/core.py +757 -0
  49. eodag/rest/server.py +397 -258
  50. eodag/rest/stac.py +438 -307
  51. eodag/rest/types/collections_search.py +44 -0
  52. eodag/rest/types/eodag_search.py +232 -43
  53. eodag/rest/types/{stac_queryables.py → queryables.py} +81 -43
  54. eodag/rest/types/stac_search.py +277 -0
  55. eodag/rest/utils/__init__.py +216 -0
  56. eodag/rest/utils/cql_evaluate.py +119 -0
  57. eodag/rest/utils/rfc3339.py +65 -0
  58. eodag/types/__init__.py +99 -9
  59. eodag/types/bbox.py +15 -14
  60. eodag/types/download_args.py +31 -0
  61. eodag/types/search_args.py +58 -7
  62. eodag/types/whoosh.py +81 -0
  63. eodag/utils/__init__.py +72 -9
  64. eodag/utils/constraints.py +37 -37
  65. eodag/utils/exceptions.py +23 -17
  66. eodag/utils/repr.py +113 -0
  67. eodag/utils/requests.py +138 -0
  68. eodag/utils/rest.py +104 -0
  69. eodag/utils/stac_reader.py +100 -16
  70. {eodag-2.12.1.dist-info → eodag-3.0.0b2.dist-info}/METADATA +65 -44
  71. eodag-3.0.0b2.dist-info/RECORD +110 -0
  72. {eodag-2.12.1.dist-info → eodag-3.0.0b2.dist-info}/WHEEL +1 -1
  73. {eodag-2.12.1.dist-info → eodag-3.0.0b2.dist-info}/entry_points.txt +6 -5
  74. eodag/plugins/apis/cds.py +0 -540
  75. eodag/rest/utils.py +0 -1133
  76. eodag-2.12.1.dist-info/RECORD +0 -94
  77. {eodag-2.12.1.dist-info → eodag-3.0.0b2.dist-info}/LICENSE +0 -0
  78. {eodag-2.12.1.dist-info → eodag-3.0.0b2.dist-info}/top_level.txt +0 -0
@@ -15,8 +15,12 @@
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
+ #
19
+ # type: ignore
18
20
  """EODAG product package"""
19
21
  try:
20
- from eodag_cube.api.product import EOProduct # noqa
22
+ # import from eodag-cube if installed
23
+ from eodag_cube.api.product import Asset, AssetsDict, EOProduct # noqa
21
24
  except ImportError:
25
+ from ._assets import Asset, AssetsDict # noqa
22
26
  from ._product import EOProduct # noqa
@@ -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):
@@ -82,6 +85,43 @@ class AssetsDict(UserDict):
82
85
  else:
83
86
  return [a for a in self.values() if "href" in a]
84
87
 
88
+ def _repr_html_(self, embeded=False):
89
+ thead = (
90
+ f"""<thead><tr><td style='text-align: left; color: grey;'>
91
+ {type(self).__name__}&ensp;({len(self)})
92
+ </td></tr></thead>
93
+ """
94
+ if not embeded
95
+ else ""
96
+ )
97
+ tr_style = "style='background-color: transparent;'" if embeded else ""
98
+ return (
99
+ f"<table>{thead}"
100
+ + "".join(
101
+ [
102
+ f"""<tr {tr_style}><td style='text-align: left;'>
103
+ <details><summary style='color: grey;'>
104
+ <span style='color: black'>'{k}'</span>:&ensp;
105
+ {{
106
+ {"'roles': '<span style='color: black'>"+str(v['roles'])+"</span>',&ensp;"
107
+ if v.get("roles") else ""}
108
+ {"'type': '"+str(v['type'])+"',&ensp;"
109
+ if v.get("type") else ""}
110
+ {"'title': '<span style='color: black'>"+str(v['title'])+"</span>',&ensp;"
111
+ if v.get("title") else ""}
112
+ ...
113
+ }}
114
+ </summary>
115
+ {dict_to_html_table(v, depth=1)}
116
+ </details>
117
+ </td></tr>
118
+ """
119
+ for k, v in self.items()
120
+ ]
121
+ )
122
+ + "</table>"
123
+ )
124
+
85
125
 
86
126
  class Asset(UserDict):
87
127
  """A UserDict object containg one of the assets of a
@@ -98,6 +138,9 @@ class Asset(UserDict):
98
138
  """
99
139
 
100
140
  product: EOProduct
141
+ size: int
142
+ filename: Optional[str]
143
+ rel_path: str
101
144
 
102
145
  def __init__(self, product: EOProduct, key: str, *args: Any, **kwargs: Any) -> None:
103
146
  self.product = product
@@ -113,7 +156,7 @@ class Asset(UserDict):
113
156
  """
114
157
  return self.data
115
158
 
116
- def download(self, **kwargs: Any) -> str:
159
+ def download(self, **kwargs: Unpack[DownloadConf]) -> str:
117
160
  """Downloads a single asset
118
161
 
119
162
  :param kwargs: (optional) Additional named-arguments passed to `plugin.download()`
@@ -122,3 +165,15 @@ class Asset(UserDict):
122
165
  :rtype: str
123
166
  """
124
167
  return self.product.download(asset=self.key, **kwargs)
168
+
169
+ def _repr_html_(self):
170
+ thead = f"""<thead><tr><td style='text-align: left; color: grey;'>
171
+ {type(self).__name__}&ensp;-&ensp;{self.key}
172
+ </td></tr></thead>
173
+ """
174
+ return f"""<table>{thead}
175
+ <tr><td style='text-align: left;'>
176
+ {dict_to_html_table(self)}
177
+ </details>
178
+ </td></tr>
179
+ </table>"""
@@ -21,17 +21,27 @@ 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 AssetsDict # type: ignore # noqa
36
+ except ImportError:
37
+ from eodag.api.product._assets import AssetsDict # type: ignore # noqa
38
+
33
39
  from eodag.api.product.drivers import DRIVERS, NoDriver
34
- from eodag.api.product.metadata_mapping import NOT_AVAILABLE, NOT_MAPPED
40
+ from eodag.api.product.metadata_mapping import (
41
+ DEFAULT_GEOMETRY,
42
+ NOT_AVAILABLE,
43
+ NOT_MAPPED,
44
+ )
35
45
  from eodag.utils import (
36
46
  DEFAULT_DOWNLOAD_TIMEOUT,
37
47
  DEFAULT_DOWNLOAD_WAIT,
@@ -41,6 +51,7 @@ from eodag.utils import (
41
51
  get_geometry_from_various,
42
52
  )
43
53
  from eodag.utils.exceptions import DownloadError, MisconfiguredError
54
+ from eodag.utils.repr import dict_to_html_table
44
55
 
45
56
  if TYPE_CHECKING:
46
57
  from shapely.geometry.base import BaseGeometry
@@ -49,6 +60,8 @@ if TYPE_CHECKING:
49
60
  from eodag.plugins.apis.base import Api
50
61
  from eodag.plugins.authentication.base import Authentication
51
62
  from eodag.plugins.download.base import Download
63
+ from eodag.types.download_args import DownloadConf
64
+ from eodag.utils import Unpack
52
65
 
53
66
  try:
54
67
  from shapely.errors import GEOSException
@@ -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(
@@ -273,31 +261,23 @@ class EOProduct:
273
261
  # resolve locations and properties if needed with downloader configuration
274
262
  location_attrs = ("location", "remote_location")
275
263
  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
- )
264
+ if "%(" in getattr(self, location_attr):
265
+ try:
266
+ setattr(
267
+ self,
268
+ location_attr,
269
+ getattr(self, location_attr) % vars(self.downloader.config),
270
+ )
271
+ except ValueError as e:
272
+ logger.debug(
273
+ f"Could not resolve product.{location_attr} ({getattr(self, location_attr)})"
274
+ f" in register_downloader: {str(e)}"
275
+ )
288
276
 
289
277
  for k, v in self.properties.items():
290
- if isinstance(v, str):
278
+ if isinstance(v, str) and "%(" in v:
291
279
  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)
280
+ self.properties[k] = v % vars(self.downloader.config)
301
281
  except (TypeError, ValueError) as e:
302
282
  logger.debug(
303
283
  f"Could not resolve {k} property ({v}) in register_downloader: {str(e)}"
@@ -308,7 +288,7 @@ class EOProduct:
308
288
  progress_callback: Optional[ProgressCallback] = None,
309
289
  wait: int = DEFAULT_DOWNLOAD_WAIT,
310
290
  timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
311
- **kwargs: Any,
291
+ **kwargs: Unpack[DownloadConf],
312
292
  ) -> str:
313
293
  """Download the EO product using the provided download plugin and the
314
294
  authenticator if necessary.
@@ -351,13 +331,6 @@ class EOProduct:
351
331
  else self.downloader_auth
352
332
  )
353
333
 
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
334
  progress_callback, close_progress_callback = self._init_progress_bar(
362
335
  progress_callback
363
336
  )
@@ -469,9 +442,13 @@ class EOProduct:
469
442
  if base_dir is not None:
470
443
  quicklooks_base_dir = os.path.abspath(os.path.realpath(base_dir))
471
444
  else:
472
- quicklooks_base_dir = os.path.join(
473
- self.downloader.config.outputs_prefix, "quicklooks"
445
+ tempdir = tempfile.gettempdir()
446
+ outputs_prefix = (
447
+ getattr(self.downloader.config, "outputs_prefix", tempdir)
448
+ if self.downloader
449
+ else tempdir
474
450
  )
451
+ quicklooks_base_dir = os.path.join(outputs_prefix, "quicklooks")
475
452
  if not os.path.isdir(quicklooks_base_dir):
476
453
  os.makedirs(quicklooks_base_dir)
477
454
  quicklook_file = os.path.join(
@@ -497,6 +474,8 @@ class EOProduct:
497
474
  if self.downloader_auth is not None
498
475
  else None
499
476
  )
477
+ if not isinstance(auth, AuthBase):
478
+ auth = None
500
479
  with requests.get(
501
480
  self.properties["quicklook"],
502
481
  stream=True,
@@ -539,3 +518,45 @@ class EOProduct:
539
518
  )
540
519
  pass
541
520
  return NoDriver()
521
+
522
+ def _repr_html_(self):
523
+ thumbnail = self.properties.get("thumbnail", None)
524
+ thumbnail_html = (
525
+ f"<img src='{thumbnail}' width=100 alt='thumbnail'/>"
526
+ if thumbnail and not thumbnail.startswith("s3")
527
+ else ""
528
+ )
529
+ geom_style = "style='color: grey; text-align: center; min-width:100px; vertical-align: top;'"
530
+ thumbnail_style = (
531
+ "style='padding-top: 1.5em; min-width:100px; vertical-align: top;'"
532
+ )
533
+
534
+ return f"""<table>
535
+ <thead><tr style='background-color: transparent;'><td style='text-align: left; color: grey;'>
536
+ {type(self).__name__}
537
+ </td></tr></thead>
538
+
539
+ <tr style='background-color: transparent;'>
540
+ <td style='text-align: left; vertical-align: top;'>
541
+ {dict_to_html_table({
542
+ "provider": self.provider,
543
+ "product_type": self.product_type,
544
+ "properties[&quot;id&quot;]": self.properties.get('id', None),
545
+ "properties[&quot;startTimeFromAscendingNode&quot;]": self.properties.get(
546
+ 'startTimeFromAscendingNode', None
547
+ ),
548
+ "properties[&quot;completionTimeFromAscendingNode&quot;]": self.properties.get(
549
+ 'completionTimeFromAscendingNode', None
550
+ ),
551
+ }, brackets=False)}
552
+ <details><summary style='color: grey; margin-top: 10px;'>properties:&ensp;({
553
+ len(self.properties)
554
+ })</summary>{dict_to_html_table(self.properties, depth=1)}</details>
555
+ <details><summary style='color: grey; margin-top: 10px;'>assets:&ensp;({
556
+ len(self.assets)
557
+ })</summary>{self.assets._repr_html_(embeded=True)}</details>
558
+ </td>
559
+ <td {geom_style} title='geometry'>geometry<br />{self.geometry._repr_svg_()}</td>
560
+ <td {thumbnail_style} title='properties[&quot;thumbnail&quot;]'>{thumbnail_html}</td>
561
+ </tr>
562
+ </table>"""