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.
- eodag/api/core.py +440 -321
- eodag/api/product/__init__.py +5 -1
- eodag/api/product/_assets.py +57 -2
- eodag/api/product/_product.py +89 -68
- eodag/api/product/metadata_mapping.py +181 -66
- eodag/api/search_result.py +48 -1
- eodag/cli.py +20 -6
- eodag/config.py +95 -6
- eodag/plugins/apis/base.py +8 -165
- eodag/plugins/apis/ecmwf.py +36 -24
- eodag/plugins/apis/usgs.py +40 -24
- eodag/plugins/authentication/aws_auth.py +2 -2
- eodag/plugins/authentication/header.py +31 -6
- eodag/plugins/authentication/keycloak.py +13 -84
- eodag/plugins/authentication/oauth.py +3 -3
- eodag/plugins/authentication/openid_connect.py +256 -46
- eodag/plugins/authentication/qsauth.py +3 -0
- eodag/plugins/authentication/sas_auth.py +8 -1
- eodag/plugins/authentication/token.py +92 -46
- eodag/plugins/authentication/token_exchange.py +120 -0
- eodag/plugins/download/aws.py +86 -91
- eodag/plugins/download/base.py +72 -40
- eodag/plugins/download/http.py +607 -264
- eodag/plugins/download/s3rest.py +28 -15
- eodag/plugins/manager.py +74 -57
- eodag/plugins/search/__init__.py +36 -0
- eodag/plugins/search/base.py +225 -18
- eodag/plugins/search/build_search_result.py +389 -32
- eodag/plugins/search/cop_marine.py +378 -0
- eodag/plugins/search/creodias_s3.py +15 -14
- eodag/plugins/search/csw.py +5 -7
- eodag/plugins/search/data_request_search.py +44 -20
- eodag/plugins/search/qssearch.py +508 -203
- eodag/plugins/search/static_stac_search.py +99 -36
- eodag/resources/constraints/climate-dt.json +13 -0
- eodag/resources/constraints/extremes-dt.json +8 -0
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +1897 -34
- eodag/resources/providers.yml +3539 -3277
- eodag/resources/stac.yml +48 -54
- eodag/resources/stac_api.yml +71 -25
- eodag/resources/stac_provider.yml +5 -0
- eodag/resources/user_conf_template.yml +51 -3
- eodag/rest/__init__.py +6 -0
- eodag/rest/cache.py +70 -0
- eodag/rest/config.py +68 -0
- eodag/rest/constants.py +27 -0
- eodag/rest/core.py +757 -0
- eodag/rest/server.py +397 -258
- eodag/rest/stac.py +438 -307
- eodag/rest/types/collections_search.py +44 -0
- eodag/rest/types/eodag_search.py +232 -43
- eodag/rest/types/{stac_queryables.py → queryables.py} +81 -43
- eodag/rest/types/stac_search.py +277 -0
- eodag/rest/utils/__init__.py +216 -0
- eodag/rest/utils/cql_evaluate.py +119 -0
- eodag/rest/utils/rfc3339.py +65 -0
- eodag/types/__init__.py +99 -9
- eodag/types/bbox.py +15 -14
- eodag/types/download_args.py +31 -0
- eodag/types/search_args.py +58 -7
- eodag/types/whoosh.py +81 -0
- eodag/utils/__init__.py +72 -9
- eodag/utils/constraints.py +37 -37
- eodag/utils/exceptions.py +23 -17
- eodag/utils/repr.py +113 -0
- eodag/utils/requests.py +138 -0
- eodag/utils/rest.py +104 -0
- eodag/utils/stac_reader.py +100 -16
- {eodag-2.12.1.dist-info → eodag-3.0.0b2.dist-info}/METADATA +65 -44
- eodag-3.0.0b2.dist-info/RECORD +110 -0
- {eodag-2.12.1.dist-info → eodag-3.0.0b2.dist-info}/WHEEL +1 -1
- {eodag-2.12.1.dist-info → eodag-3.0.0b2.dist-info}/entry_points.txt +6 -5
- eodag/plugins/apis/cds.py +0 -540
- eodag/rest/utils.py +0 -1133
- eodag-2.12.1.dist-info/RECORD +0 -94
- {eodag-2.12.1.dist-info → eodag-3.0.0b2.dist-info}/LICENSE +0 -0
- {eodag-2.12.1.dist-info → eodag-3.0.0b2.dist-info}/top_level.txt +0 -0
eodag/api/product/__init__.py
CHANGED
|
@@ -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
|
|
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
|
eodag/api/product/_assets.py
CHANGED
|
@@ -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__} ({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>: 
|
|
105
|
+
{{
|
|
106
|
+
{"'roles': '<span style='color: black'>"+str(v['roles'])+"</span>', "
|
|
107
|
+
if v.get("roles") else ""}
|
|
108
|
+
{"'type': '"+str(v['type'])+"', "
|
|
109
|
+
if v.get("type") else ""}
|
|
110
|
+
{"'title': '<span style='color: black'>"+str(v['title'])+"</span>', "
|
|
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:
|
|
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__} - {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>"""
|
eodag/api/product/_product.py
CHANGED
|
@@ -21,17 +21,27 @@ import base64
|
|
|
21
21
|
import logging
|
|
22
22
|
import os
|
|
23
23
|
import re
|
|
24
|
-
import
|
|
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
|
|
29
|
+
from requests.auth import AuthBase
|
|
30
|
+
from shapely import geometry
|
|
30
31
|
from shapely.errors import ShapelyError
|
|
31
32
|
|
|
32
|
-
|
|
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
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
473
|
-
|
|
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["id"]": self.properties.get('id', None),
|
|
545
|
+
"properties["startTimeFromAscendingNode"]": self.properties.get(
|
|
546
|
+
'startTimeFromAscendingNode', None
|
|
547
|
+
),
|
|
548
|
+
"properties["completionTimeFromAscendingNode"]": self.properties.get(
|
|
549
|
+
'completionTimeFromAscendingNode', None
|
|
550
|
+
),
|
|
551
|
+
}, brackets=False)}
|
|
552
|
+
<details><summary style='color: grey; margin-top: 10px;'>properties: ({
|
|
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: ({
|
|
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["thumbnail"]'>{thumbnail_html}</td>
|
|
561
|
+
</tr>
|
|
562
|
+
</table>"""
|