eodag 2.12.0__py3-none-any.whl → 3.0.0b1__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 (77) hide show
  1. eodag/api/core.py +434 -319
  2. eodag/api/product/__init__.py +5 -1
  3. eodag/api/product/_assets.py +7 -2
  4. eodag/api/product/_product.py +46 -68
  5. eodag/api/product/metadata_mapping.py +181 -66
  6. eodag/api/search_result.py +21 -1
  7. eodag/cli.py +20 -6
  8. eodag/config.py +95 -6
  9. eodag/plugins/apis/base.py +8 -162
  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 +73 -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/requests.py +138 -0
  67. eodag/utils/rest.py +104 -0
  68. eodag/utils/stac_reader.py +100 -16
  69. {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/METADATA +64 -44
  70. eodag-3.0.0b1.dist-info/RECORD +109 -0
  71. {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/WHEEL +1 -1
  72. {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/entry_points.txt +6 -5
  73. eodag/plugins/apis/cds.py +0 -540
  74. eodag/rest/utils.py +0 -1133
  75. eodag-2.12.0.dist-info/RECORD +0 -94
  76. {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/LICENSE +0 -0
  77. {eodag-2.12.0.dist-info → eodag-3.0.0b1.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,14 @@ 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
25
 
26
26
  if TYPE_CHECKING:
27
27
  from eodag.api.product import EOProduct
28
+ from eodag.types.download_args import DownloadConf
29
+ from eodag.utils import Unpack
28
30
 
29
31
 
30
32
  class AssetsDict(UserDict):
@@ -98,6 +100,9 @@ class Asset(UserDict):
98
100
  """
99
101
 
100
102
  product: EOProduct
103
+ size: int
104
+ filename: Optional[str]
105
+ rel_path: str
101
106
 
102
107
  def __init__(self, product: EOProduct, key: str, *args: Any, **kwargs: Any) -> None:
103
108
  self.product = product
@@ -113,7 +118,7 @@ class Asset(UserDict):
113
118
  """
114
119
  return self.data
115
120
 
116
- def download(self, **kwargs: Any) -> str:
121
+ def download(self, **kwargs: Unpack[DownloadConf]) -> str:
117
122
  """Downloads a single asset
118
123
 
119
124
  :param kwargs: (optional) Additional named-arguments passed to `plugin.download()`
@@ -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,
@@ -49,6 +59,8 @@ if TYPE_CHECKING:
49
59
  from eodag.plugins.apis.base import Api
50
60
  from eodag.plugins.authentication.base import Authentication
51
61
  from eodag.plugins.download.base import Download
62
+ from eodag.types.download_args import DownloadConf
63
+ from eodag.utils import Unpack
52
64
 
53
65
  try:
54
66
  from shapely.errors import GEOSException
@@ -133,40 +145,15 @@ class EOProduct:
133
145
  raise MisconfiguredError(
134
146
  f"No geometry available to build EOProduct(id={properties.get('id', None)}, provider={provider})"
135
147
  )
136
- elif properties["geometry"] == NOT_AVAILABLE:
137
- product_geometry = properties.pop("defaultGeometry")
148
+ elif not properties["geometry"] or properties["geometry"] == NOT_AVAILABLE:
149
+ product_geometry = properties.pop("defaultGeometry", DEFAULT_GEOMETRY)
138
150
  else:
139
151
  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)
152
+
153
+ self.geometry = self.search_intersection = get_geometry_from_various(
154
+ geometry=product_geometry
155
+ )
156
+
170
157
  self.search_kwargs = kwargs
171
158
  if self.search_kwargs.get("geometry") is not None:
172
159
  searched_geom = get_geometry_from_various(
@@ -273,31 +260,23 @@ class EOProduct:
273
260
  # resolve locations and properties if needed with downloader configuration
274
261
  location_attrs = ("location", "remote_location")
275
262
  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
- )
263
+ if "%(" in getattr(self, location_attr):
264
+ try:
265
+ setattr(
266
+ self,
267
+ location_attr,
268
+ getattr(self, location_attr) % vars(self.downloader.config),
269
+ )
270
+ except ValueError as e:
271
+ logger.debug(
272
+ f"Could not resolve product.{location_attr} ({getattr(self, location_attr)})"
273
+ f" in register_downloader: {str(e)}"
274
+ )
288
275
 
289
276
  for k, v in self.properties.items():
290
- if isinstance(v, str):
277
+ if isinstance(v, str) and "%(" in v:
291
278
  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)
279
+ self.properties[k] = v % vars(self.downloader.config)
301
280
  except (TypeError, ValueError) as e:
302
281
  logger.debug(
303
282
  f"Could not resolve {k} property ({v}) in register_downloader: {str(e)}"
@@ -308,7 +287,7 @@ class EOProduct:
308
287
  progress_callback: Optional[ProgressCallback] = None,
309
288
  wait: int = DEFAULT_DOWNLOAD_WAIT,
310
289
  timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
311
- **kwargs: Any,
290
+ **kwargs: Unpack[DownloadConf],
312
291
  ) -> str:
313
292
  """Download the EO product using the provided download plugin and the
314
293
  authenticator if necessary.
@@ -351,13 +330,6 @@ class EOProduct:
351
330
  else self.downloader_auth
352
331
  )
353
332
 
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
333
  progress_callback, close_progress_callback = self._init_progress_bar(
362
334
  progress_callback
363
335
  )
@@ -469,9 +441,13 @@ class EOProduct:
469
441
  if base_dir is not None:
470
442
  quicklooks_base_dir = os.path.abspath(os.path.realpath(base_dir))
471
443
  else:
472
- quicklooks_base_dir = os.path.join(
473
- self.downloader.config.outputs_prefix, "quicklooks"
444
+ tempdir = tempfile.gettempdir()
445
+ outputs_prefix = (
446
+ getattr(self.downloader.config, "outputs_prefix", tempdir)
447
+ if self.downloader
448
+ else tempdir
474
449
  )
450
+ quicklooks_base_dir = os.path.join(outputs_prefix, "quicklooks")
475
451
  if not os.path.isdir(quicklooks_base_dir):
476
452
  os.makedirs(quicklooks_base_dir)
477
453
  quicklook_file = os.path.join(
@@ -497,6 +473,8 @@ class EOProduct:
497
473
  if self.downloader_auth is not None
498
474
  else None
499
475
  )
476
+ if not isinstance(auth, AuthBase):
477
+ auth = None
500
478
  with requests.get(
501
479
  self.properties["quicklook"],
502
480
  stream=True,