eodag 3.10.0__py3-none-any.whl → 4.0.0a1__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 +378 -419
- eodag/api/product/__init__.py +3 -3
- eodag/api/product/_product.py +68 -40
- eodag/api/product/drivers/__init__.py +3 -5
- eodag/api/product/drivers/base.py +1 -18
- eodag/api/product/metadata_mapping.py +151 -215
- eodag/api/search_result.py +13 -7
- eodag/cli.py +72 -395
- eodag/config.py +46 -50
- eodag/plugins/apis/base.py +2 -2
- eodag/plugins/apis/ecmwf.py +20 -21
- eodag/plugins/apis/usgs.py +37 -33
- eodag/plugins/authentication/base.py +1 -3
- eodag/plugins/crunch/filter_date.py +3 -3
- eodag/plugins/crunch/filter_latest_intersect.py +2 -2
- eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
- eodag/plugins/download/aws.py +45 -41
- eodag/plugins/download/base.py +13 -14
- eodag/plugins/download/http.py +65 -65
- eodag/plugins/manager.py +28 -29
- eodag/plugins/search/__init__.py +3 -4
- eodag/plugins/search/base.py +128 -77
- eodag/plugins/search/build_search_result.py +105 -107
- eodag/plugins/search/cop_marine.py +44 -47
- eodag/plugins/search/csw.py +33 -33
- eodag/plugins/search/qssearch.py +335 -354
- eodag/plugins/search/stac_list_assets.py +1 -1
- eodag/plugins/search/static_stac_search.py +31 -31
- eodag/resources/{product_types.yml → collections.yml} +2353 -2429
- eodag/resources/ext_collections.json +1 -1
- eodag/resources/providers.yml +2427 -2719
- eodag/resources/stac_provider.yml +46 -90
- eodag/types/queryables.py +55 -91
- eodag/types/search_args.py +1 -1
- eodag/utils/__init__.py +94 -21
- eodag/utils/exceptions.py +6 -6
- eodag/utils/free_text_search.py +3 -3
- {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/METADATA +10 -87
- eodag-4.0.0a1.dist-info/RECORD +92 -0
- {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/entry_points.txt +0 -4
- eodag/plugins/authentication/oauth.py +0 -60
- eodag/plugins/download/creodias_s3.py +0 -71
- eodag/plugins/download/s3rest.py +0 -351
- eodag/plugins/search/data_request_search.py +0 -565
- eodag/resources/stac.yml +0 -294
- eodag/resources/stac_api.yml +0 -2105
- eodag/rest/__init__.py +0 -24
- eodag/rest/cache.py +0 -70
- eodag/rest/config.py +0 -67
- eodag/rest/constants.py +0 -26
- eodag/rest/core.py +0 -764
- eodag/rest/errors.py +0 -210
- eodag/rest/server.py +0 -604
- eodag/rest/server.wsgi +0 -6
- eodag/rest/stac.py +0 -1032
- eodag/rest/templates/README +0 -1
- eodag/rest/types/__init__.py +0 -18
- eodag/rest/types/collections_search.py +0 -44
- eodag/rest/types/eodag_search.py +0 -386
- eodag/rest/types/queryables.py +0 -174
- eodag/rest/types/stac_search.py +0 -272
- eodag/rest/utils/__init__.py +0 -207
- eodag/rest/utils/cql_evaluate.py +0 -119
- eodag/rest/utils/rfc3339.py +0 -64
- eodag-3.10.0.dist-info/RECORD +0 -116
- {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/WHEEL +0 -0
- {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/licenses/LICENSE +0 -0
- {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/top_level.txt +0 -0
eodag/config.py
CHANGED
|
@@ -64,8 +64,8 @@ from eodag.utils.exceptions import ValidationError
|
|
|
64
64
|
|
|
65
65
|
logger = logging.getLogger("eodag.config")
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
"https://cs-si.github.io/eodag/eodag/resources/
|
|
67
|
+
EXT_COLLECTIONS_CONF_URI = (
|
|
68
|
+
"https://cs-si.github.io/eodag/eodag/resources/ext_collections.json"
|
|
69
69
|
)
|
|
70
70
|
AUTH_TOPIC_KEYS = ("auth", "search_auth", "download_auth")
|
|
71
71
|
PLUGINS_TOPICS_KEYS = ("api", "search", "download") + AUTH_TOPIC_KEYS
|
|
@@ -114,7 +114,7 @@ class ProviderConfig(yaml.YAMLObject):
|
|
|
114
114
|
Lower value means lower priority. (Default: 0)
|
|
115
115
|
:param api: (optional) The configuration of a plugin of type Api
|
|
116
116
|
:param search: (optional) The configuration of a plugin of type Search
|
|
117
|
-
:param products: (optional) The
|
|
117
|
+
:param products: (optional) The collections supported by the provider
|
|
118
118
|
:param download: (optional) The configuration of a plugin of type Download
|
|
119
119
|
:param auth: (optional) The configuration of a plugin of type Authentication
|
|
120
120
|
:param search_auth: (optional) The configuration of a plugin of type Authentication for search
|
|
@@ -135,7 +135,7 @@ class ProviderConfig(yaml.YAMLObject):
|
|
|
135
135
|
auth: PluginConfig
|
|
136
136
|
search_auth: PluginConfig
|
|
137
137
|
download_auth: PluginConfig
|
|
138
|
-
|
|
138
|
+
collections_fetched: bool # set in core.update_collections_list
|
|
139
139
|
|
|
140
140
|
yaml_loader = yaml.Loader
|
|
141
141
|
yaml_dumper = yaml.SafeDumper
|
|
@@ -274,17 +274,19 @@ class PluginConfig(yaml.YAMLObject):
|
|
|
274
274
|
metadata_path: str
|
|
275
275
|
#: list search parameters to send as is to the provider
|
|
276
276
|
search_param_unparsed: list[str]
|
|
277
|
+
#: Use as STAC extension prefix if it does not have one already
|
|
278
|
+
metadata_prefix: str
|
|
277
279
|
#: Whether an error must be raised when using a search parameter which is not queryable or not
|
|
278
280
|
raise_mtd_discovery_error: bool
|
|
279
281
|
|
|
280
|
-
class
|
|
281
|
-
"""Configuration for
|
|
282
|
+
class DiscoverCollections(TypedDict, total=False):
|
|
283
|
+
"""Configuration for collections discovery"""
|
|
282
284
|
|
|
283
|
-
#: URL from which the
|
|
285
|
+
#: URL from which the collections can be fetched
|
|
284
286
|
fetch_url: Optional[str]
|
|
285
|
-
#: HTTP method used to fetch
|
|
287
|
+
#: HTTP method used to fetch collections
|
|
286
288
|
fetch_method: str
|
|
287
|
-
#: Request body to fetch
|
|
289
|
+
#: Request body to fetch collections using POST method
|
|
288
290
|
fetch_body: dict[str, Any]
|
|
289
291
|
#: Maximum number of connections for concurrent HTTP requests
|
|
290
292
|
max_connections: int
|
|
@@ -294,32 +296,32 @@ class PluginConfig(yaml.YAMLObject):
|
|
|
294
296
|
start_page: int
|
|
295
297
|
#: Type of the provider result
|
|
296
298
|
result_type: str
|
|
297
|
-
#: JsonPath to the list of
|
|
299
|
+
#: JsonPath to the list of collections
|
|
298
300
|
results_entry: Union[JSONPath, str]
|
|
299
|
-
#: Mapping for the
|
|
300
|
-
|
|
301
|
-
#: Mapping for
|
|
301
|
+
#: Mapping for the collection id
|
|
302
|
+
generic_collection_id: str
|
|
303
|
+
#: Mapping for collection metadata (e.g. ``abstract``, ``licence``) which can be parsed from the provider
|
|
302
304
|
#: result
|
|
303
|
-
|
|
304
|
-
#: Mapping for
|
|
305
|
-
|
|
306
|
-
#: Mapping for
|
|
307
|
-
|
|
305
|
+
generic_collection_parsable_metadata: dict[str, str]
|
|
306
|
+
#: Mapping for collection properties which can be parsed from the result and are not collection metadata
|
|
307
|
+
generic_collection_parsable_properties: dict[str, str]
|
|
308
|
+
#: Mapping for collection properties which cannot be parsed from the result and are not collection metadata
|
|
309
|
+
generic_collection_unparsable_properties: dict[str, str]
|
|
308
310
|
#: URL to fetch data for a single collection
|
|
309
311
|
single_collection_fetch_url: str
|
|
310
312
|
#: Query string to be added to the fetch_url to filter for a collection
|
|
311
313
|
single_collection_fetch_qs: str
|
|
312
|
-
#: Mapping for
|
|
313
|
-
#: is redefined in this mapping, it will replace ``
|
|
314
|
-
|
|
314
|
+
#: Mapping for collection metadata returned by the endpoint given in single_collection_fetch_url. If ``ID``
|
|
315
|
+
#: is redefined in this mapping, it will replace ``generic_collection_id`` value
|
|
316
|
+
single_collection_parsable_metadata: dict[str, str]
|
|
315
317
|
|
|
316
318
|
class DiscoverQueryables(TypedDict, total=False):
|
|
317
319
|
"""Configuration for queryables discovery"""
|
|
318
320
|
|
|
319
|
-
#: URL to fetch the queryables valid for all
|
|
321
|
+
#: URL to fetch the queryables valid for all collections
|
|
320
322
|
fetch_url: Optional[str]
|
|
321
|
-
#: URL to fetch the queryables for a specific
|
|
322
|
-
|
|
323
|
+
#: URL to fetch the queryables for a specific collection
|
|
324
|
+
collection_fetch_url: Optional[str]
|
|
323
325
|
#: Type of the result
|
|
324
326
|
result_type: str
|
|
325
327
|
#: JsonPath to retrieve the queryables from the provider result
|
|
@@ -431,8 +433,8 @@ class PluginConfig(yaml.YAMLObject):
|
|
|
431
433
|
# search & api -----------------------------------------------------------------------------------------------------
|
|
432
434
|
# copied from ProviderConfig in PluginManager.get_search_plugins()
|
|
433
435
|
priority: int
|
|
434
|
-
# per
|
|
435
|
-
|
|
436
|
+
# per collection metadata-mapping, set in core._prepare_search
|
|
437
|
+
collection_config: dict[str, Any]
|
|
436
438
|
|
|
437
439
|
#: :class:`~eodag.plugins.search.base.Search` Plugin API endpoint
|
|
438
440
|
api_endpoint: str
|
|
@@ -449,14 +451,14 @@ class PluginConfig(yaml.YAMLObject):
|
|
|
449
451
|
sort: PluginConfig.Sort
|
|
450
452
|
#: :class:`~eodag.plugins.search.base.Search` Configuration for the metadata auto-discovery
|
|
451
453
|
discover_metadata: PluginConfig.DiscoverMetadata
|
|
452
|
-
#: :class:`~eodag.plugins.search.base.Search` Configuration for the
|
|
453
|
-
|
|
454
|
+
#: :class:`~eodag.plugins.search.base.Search` Configuration for the collections auto-discovery
|
|
455
|
+
discover_collections: PluginConfig.DiscoverCollections
|
|
454
456
|
#: :class:`~eodag.plugins.search.base.Search` Configuration for the queryables auto-discovery
|
|
455
457
|
discover_queryables: PluginConfig.DiscoverQueryables
|
|
456
458
|
#: :class:`~eodag.plugins.search.base.Search` The mapping between eodag metadata and the plugin specific metadata
|
|
457
459
|
metadata_mapping: dict[str, Union[str, list[str]]]
|
|
458
460
|
#: :class:`~eodag.plugins.search.base.Search` :attr:`~eodag.config.PluginConfig.metadata_mapping` got from the given
|
|
459
|
-
#:
|
|
461
|
+
#: collection
|
|
460
462
|
metadata_mapping_from_product: str
|
|
461
463
|
#: :class:`~eodag.plugins.search.base.Search` A mapping for the metadata of individual assets
|
|
462
464
|
assets_mapping: dict[str, dict[str, Any]]
|
|
@@ -476,26 +478,19 @@ class PluginConfig(yaml.YAMLObject):
|
|
|
476
478
|
per_product_metadata_query: bool
|
|
477
479
|
#: :class:`~eodag.plugins.search.qssearch.ODataV4Search` Dict used to simplify further metadata extraction
|
|
478
480
|
metadata_pre_mapping: PluginConfig.MetadataPreMapping
|
|
479
|
-
#: :class:`~eodag.plugins.search.data_request_search.DataRequestSearch` URL to which the data request shall be sent
|
|
480
|
-
data_request_url: str
|
|
481
|
-
#: :class:`~eodag.plugins.search.data_request_search.DataRequestSearch` URL to fetch the status of the data request
|
|
482
|
-
status_url: str
|
|
483
|
-
#: :class:`~eodag.plugins.search.data_request_search.DataRequestSearch`
|
|
484
|
-
#: URL to fetch the search result when the data request is done
|
|
485
|
-
result_url: str
|
|
486
|
-
#: :class:`~eodag.plugins.search.data_request_search.DataRequestSearch`
|
|
487
|
-
#: if date parameters are mandatory in the request
|
|
488
|
-
dates_required: bool
|
|
489
481
|
#: :class:`~eodag.plugins.search.csw.CSWSearch` Search definition dictionary
|
|
490
482
|
search_definition: dict[str, Any]
|
|
491
483
|
#: :class:`~eodag.plugins.search.qssearch.PostJsonSearch` Whether to merge responses or not (`aws_eos` specific)
|
|
492
484
|
merge_responses: bool
|
|
493
485
|
#: :class:`~eodag.plugins.search.qssearch.PostJsonSearch` Collections names (`aws_eos` specific)
|
|
494
|
-
|
|
486
|
+
_collection: list[str]
|
|
495
487
|
#: :class:`~eodag.plugins.search.static_stac_search.StaticStacSearch`
|
|
496
488
|
#: Maximum number of connections for concurrent HTTP requests
|
|
497
489
|
max_connections: int
|
|
498
490
|
#: :class:`~eodag.plugins.search.build_search_result.ECMWFSearch`
|
|
491
|
+
#: if date parameters are mandatory in the request
|
|
492
|
+
dates_required: bool
|
|
493
|
+
#: :class:`~eodag.plugins.search.build_search_result.ECMWFSearch`
|
|
499
494
|
#: Whether end date should be excluded from search request or not
|
|
500
495
|
end_date_excluded: bool
|
|
501
496
|
#: :class:`~eodag.plugins.search.build_search_result.ECMWFSearch`
|
|
@@ -521,9 +516,10 @@ class PluginConfig(yaml.YAMLObject):
|
|
|
521
516
|
flatten_top_dirs: bool
|
|
522
517
|
#: :class:`~eodag.plugins.download.base.Download` Level in extracted path tree where to find data
|
|
523
518
|
archive_depth: int
|
|
524
|
-
#: :class:`~eodag.plugins.download.base.Download` Whether ignore assets and download using ``
|
|
519
|
+
#: :class:`~eodag.plugins.download.base.Download` Whether ignore assets and download using ``eodag:download_link``
|
|
520
|
+
#: or not
|
|
525
521
|
ignore_assets: bool
|
|
526
|
-
#: :class:`~eodag.plugins.download.base.Download`
|
|
522
|
+
#: :class:`~eodag.plugins.download.base.Download` Collection specific configuration
|
|
527
523
|
products: dict[str, dict[str, Any]]
|
|
528
524
|
#: :class:`~eodag.plugins.download.http.HTTPDownload` Whether the product has to be ordered to download it or not
|
|
529
525
|
order_enabled: bool
|
|
@@ -542,7 +538,7 @@ class PluginConfig(yaml.YAMLObject):
|
|
|
542
538
|
no_auth_download: bool
|
|
543
539
|
#: :class:`~eodag.plugins.download.http.HTTPDownload` Parameters to be added to the query params of the request
|
|
544
540
|
dl_url_params: dict[str, str]
|
|
545
|
-
#: :class:`~eodag.plugins.download.
|
|
541
|
+
#: :class:`~eodag.plugins.download.aws.AwsDownload`
|
|
546
542
|
#: At which level of the path part of the url the bucket can be found
|
|
547
543
|
bucket_path_level: int
|
|
548
544
|
#: :class:`~eodag.plugins.download.aws.AwsDownload` S3 endpoint
|
|
@@ -1069,15 +1065,15 @@ def load_stac_provider_config() -> dict[str, Any]:
|
|
|
1069
1065
|
).source
|
|
1070
1066
|
|
|
1071
1067
|
|
|
1072
|
-
def
|
|
1073
|
-
conf_uri: str =
|
|
1068
|
+
def get_ext_collections_conf(
|
|
1069
|
+
conf_uri: str = EXT_COLLECTIONS_CONF_URI,
|
|
1074
1070
|
) -> dict[str, Any]:
|
|
1075
|
-
"""Read external
|
|
1071
|
+
"""Read external collections conf
|
|
1076
1072
|
|
|
1077
1073
|
:param conf_uri: URI to local or remote configuration file
|
|
1078
|
-
:returns: The external
|
|
1074
|
+
:returns: The external collections configuration
|
|
1079
1075
|
"""
|
|
1080
|
-
logger.info("Fetching external
|
|
1076
|
+
logger.info("Fetching external collections from %s", conf_uri)
|
|
1081
1077
|
if conf_uri.lower().startswith("http"):
|
|
1082
1078
|
# read from remote
|
|
1083
1079
|
try:
|
|
@@ -1089,7 +1085,7 @@ def get_ext_product_types_conf(
|
|
|
1089
1085
|
except requests.RequestException as e:
|
|
1090
1086
|
logger.debug(e)
|
|
1091
1087
|
logger.warning(
|
|
1092
|
-
"Could not read remote external
|
|
1088
|
+
"Could not read remote external collections conf from %s", conf_uri
|
|
1093
1089
|
)
|
|
1094
1090
|
return {}
|
|
1095
1091
|
elif conf_uri.lower().startswith("file"):
|
|
@@ -1102,6 +1098,6 @@ def get_ext_product_types_conf(
|
|
|
1102
1098
|
except (orjson.JSONDecodeError, FileNotFoundError) as e:
|
|
1103
1099
|
logger.debug(e)
|
|
1104
1100
|
logger.warning(
|
|
1105
|
-
"Could not read local external
|
|
1101
|
+
"Could not read local external collections conf from %s", conf_uri
|
|
1106
1102
|
)
|
|
1107
1103
|
return {}
|
eodag/plugins/apis/base.py
CHANGED
|
@@ -45,8 +45,8 @@ class Api(Search, Download):
|
|
|
45
45
|
(e.g. 'file:///tmp/product_folder' on Linux or
|
|
46
46
|
'file:///C:/Users/username/AppData/Local/Temp' on Windows)
|
|
47
47
|
- save a *record* file in the directory ``output_dir/.downloaded`` whose name
|
|
48
|
-
is built on the MD5 hash of the product's ``
|
|
49
|
-
attributes (``hashlib.md5((product.
|
|
48
|
+
is built on the MD5 hash of the product's ``collection`` and ``properties['id']``
|
|
49
|
+
attributes (``hashlib.md5((product.collection+"-"+product.properties['id']).encode("utf-8")).hexdigest()``)
|
|
50
50
|
and whose content is the product's ``remote_location`` attribute itself.
|
|
51
51
|
- not try to download a product whose ``location`` attribute already points to an
|
|
52
52
|
existing file/directory
|
eodag/plugins/apis/ecmwf.py
CHANGED
|
@@ -35,6 +35,7 @@ from eodag.utils import (
|
|
|
35
35
|
DEFAULT_DOWNLOAD_TIMEOUT,
|
|
36
36
|
DEFAULT_DOWNLOAD_WAIT,
|
|
37
37
|
DEFAULT_MISSION_START_DATE,
|
|
38
|
+
get_collection_dates,
|
|
38
39
|
get_geometry_from_various,
|
|
39
40
|
path_to_uri,
|
|
40
41
|
sanitize,
|
|
@@ -52,7 +53,6 @@ if TYPE_CHECKING:
|
|
|
52
53
|
from eodag.api.product import EOProduct
|
|
53
54
|
from eodag.api.search_result import SearchResult
|
|
54
55
|
from eodag.config import PluginConfig
|
|
55
|
-
from eodag.types import S3SessionKwargs
|
|
56
56
|
from eodag.types.download_args import DownloadConf
|
|
57
57
|
from eodag.utils import DownloadedCallback, ProgressCallback, Unpack
|
|
58
58
|
|
|
@@ -114,25 +114,24 @@ class EcmwfApi(Api, ECMWFSearch):
|
|
|
114
114
|
) -> tuple[list[EOProduct], Optional[int]]:
|
|
115
115
|
"""Build ready-to-download SearchResult"""
|
|
116
116
|
|
|
117
|
-
# check
|
|
118
|
-
#
|
|
119
|
-
if not kwargs.get("
|
|
120
|
-
kwargs["
|
|
117
|
+
# check collection, dates, geometry, use defaults if not specified
|
|
118
|
+
# collection
|
|
119
|
+
if not kwargs.get("collection"):
|
|
120
|
+
kwargs["collection"] = "%s_%s_%s" % (
|
|
121
121
|
kwargs.get("ecmwf:dataset", "mars"),
|
|
122
122
|
kwargs.get("ecmwf:type", ""),
|
|
123
123
|
kwargs.get("ecmwf:levtype", ""),
|
|
124
124
|
)
|
|
125
|
+
|
|
126
|
+
col_start, col_end = get_collection_dates(
|
|
127
|
+
getattr(self.config, "collection_config", {})
|
|
128
|
+
)
|
|
125
129
|
# start date
|
|
126
|
-
if "
|
|
127
|
-
kwargs["
|
|
128
|
-
getattr(self.config, "product_type_config", {}).get("missionStartDate")
|
|
129
|
-
or DEFAULT_MISSION_START_DATE
|
|
130
|
-
)
|
|
130
|
+
if "start_datetime" not in kwargs:
|
|
131
|
+
kwargs["start_datetime"] = col_start or DEFAULT_MISSION_START_DATE
|
|
131
132
|
# end date
|
|
132
|
-
if "
|
|
133
|
-
kwargs["
|
|
134
|
-
self.config, "product_type_config", {}
|
|
135
|
-
).get("missionEndDate") or datetime.now(timezone.utc).isoformat(
|
|
133
|
+
if "end_datetime" not in kwargs:
|
|
134
|
+
kwargs["end_datetime"] = col_end or datetime.now(timezone.utc).isoformat(
|
|
136
135
|
timespec="seconds"
|
|
137
136
|
)
|
|
138
137
|
|
|
@@ -173,7 +172,7 @@ class EcmwfApi(Api, ECMWFSearch):
|
|
|
173
172
|
def download(
|
|
174
173
|
self,
|
|
175
174
|
product: EOProduct,
|
|
176
|
-
auth: Optional[Union[AuthBase,
|
|
175
|
+
auth: Optional[Union[AuthBase, S3ServiceResource]] = None,
|
|
177
176
|
progress_callback: Optional[ProgressCallback] = None,
|
|
178
177
|
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
179
178
|
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
@@ -205,7 +204,7 @@ class EcmwfApi(Api, ECMWFSearch):
|
|
|
205
204
|
os.makedirs(new_fs_path)
|
|
206
205
|
fs_path = os.path.join(new_fs_path, os.path.basename(fs_path))
|
|
207
206
|
|
|
208
|
-
# get download request dict from product.location/
|
|
207
|
+
# get download request dict from product.location/eodag:download_link url query string
|
|
209
208
|
# separate url & parameters
|
|
210
209
|
download_request = geojson.loads(urlsplit(product.location).query)
|
|
211
210
|
|
|
@@ -245,7 +244,7 @@ class EcmwfApi(Api, ECMWFSearch):
|
|
|
245
244
|
raise DownloadError(e)
|
|
246
245
|
|
|
247
246
|
with open(record_filename, "w") as fh:
|
|
248
|
-
fh.write(product.properties["
|
|
247
|
+
fh.write(product.properties["eodag:download_link"])
|
|
249
248
|
logger.debug("Download recorded in %s", record_filename)
|
|
250
249
|
|
|
251
250
|
# do not try to extract a directory
|
|
@@ -262,7 +261,7 @@ class EcmwfApi(Api, ECMWFSearch):
|
|
|
262
261
|
def download_all(
|
|
263
262
|
self,
|
|
264
263
|
products: SearchResult,
|
|
265
|
-
auth: Optional[Union[AuthBase,
|
|
264
|
+
auth: Optional[Union[AuthBase, S3ServiceResource]] = None,
|
|
266
265
|
downloaded_callback: Optional[DownloadedCallback] = None,
|
|
267
266
|
progress_callback: Optional[ProgressCallback] = None,
|
|
268
267
|
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
@@ -291,9 +290,9 @@ class EcmwfApi(Api, ECMWFSearch):
|
|
|
291
290
|
) -> Optional[dict[str, Annotated[Any, FieldInfo]]]:
|
|
292
291
|
"""Fetch queryables list from provider using metadata mapping
|
|
293
292
|
|
|
294
|
-
:param kwargs: additional filters for queryables (`
|
|
293
|
+
:param kwargs: additional filters for queryables (`collection` and other search
|
|
295
294
|
arguments)
|
|
296
295
|
:returns: fetched queryable parameters dict
|
|
297
296
|
"""
|
|
298
|
-
|
|
299
|
-
return self.queryables_from_metadata_mapping(
|
|
297
|
+
collection = kwargs.get("collection")
|
|
298
|
+
return self.queryables_from_metadata_mapping(collection)
|
eodag/plugins/apis/usgs.py
CHANGED
|
@@ -31,7 +31,6 @@ from usgs import USGSAuthExpiredError, USGSError, api
|
|
|
31
31
|
|
|
32
32
|
from eodag.api.product import EOProduct
|
|
33
33
|
from eodag.api.product.metadata_mapping import (
|
|
34
|
-
DEFAULT_METADATA_MAPPING,
|
|
35
34
|
mtd_cfg_as_conversion_and_querypath,
|
|
36
35
|
properties_from_json,
|
|
37
36
|
)
|
|
@@ -42,7 +41,7 @@ from eodag.utils import (
|
|
|
42
41
|
DEFAULT_DOWNLOAD_WAIT,
|
|
43
42
|
DEFAULT_ITEMS_PER_PAGE,
|
|
44
43
|
DEFAULT_PAGE,
|
|
45
|
-
|
|
44
|
+
GENERIC_COLLECTION,
|
|
46
45
|
USER_AGENT,
|
|
47
46
|
ProgressCallback,
|
|
48
47
|
format_dict_items,
|
|
@@ -50,7 +49,7 @@ from eodag.utils import (
|
|
|
50
49
|
)
|
|
51
50
|
from eodag.utils.exceptions import (
|
|
52
51
|
AuthenticationError,
|
|
53
|
-
|
|
52
|
+
NoMatchingCollection,
|
|
54
53
|
NotAvailableError,
|
|
55
54
|
RequestError,
|
|
56
55
|
ValidationError,
|
|
@@ -62,7 +61,6 @@ if TYPE_CHECKING:
|
|
|
62
61
|
|
|
63
62
|
from eodag.api.search_result import SearchResult
|
|
64
63
|
from eodag.config import PluginConfig
|
|
65
|
-
from eodag.types import S3SessionKwargs
|
|
66
64
|
from eodag.types.download_args import DownloadConf
|
|
67
65
|
from eodag.utils import DownloadedCallback, Unpack
|
|
68
66
|
|
|
@@ -101,7 +99,7 @@ class UsgsApi(Api):
|
|
|
101
99
|
# Same method as in base.py, Search.__init__()
|
|
102
100
|
# Prepare the metadata mapping
|
|
103
101
|
# Do a shallow copy, the structure is flat enough for this to be sufficient
|
|
104
|
-
metas: dict[str, Any] =
|
|
102
|
+
metas: dict[str, Any] = {}
|
|
105
103
|
# Update the defaults with the mapping value. This will add any new key
|
|
106
104
|
# added by the provider mapping that is not in the default metadata.
|
|
107
105
|
metas.update(self.config.metadata_mapping)
|
|
@@ -148,23 +146,25 @@ class UsgsApi(Api):
|
|
|
148
146
|
if prep.items_per_page is not None
|
|
149
147
|
else DEFAULT_ITEMS_PER_PAGE
|
|
150
148
|
)
|
|
151
|
-
|
|
152
|
-
if
|
|
153
|
-
raise
|
|
154
|
-
"Cannot search on USGS without
|
|
149
|
+
collection = kwargs.get("collection")
|
|
150
|
+
if collection is None:
|
|
151
|
+
raise NoMatchingCollection(
|
|
152
|
+
"Cannot search on USGS without collection specified"
|
|
155
153
|
)
|
|
156
154
|
if kwargs.get("sort_by"):
|
|
157
155
|
raise ValidationError("USGS does not support sorting feature")
|
|
158
156
|
|
|
159
157
|
self.authenticate()
|
|
160
158
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
self.config.products[
|
|
159
|
+
collection_def_params = self.config.products.get( # type: ignore
|
|
160
|
+
collection,
|
|
161
|
+
self.config.products[GENERIC_COLLECTION], # type: ignore
|
|
164
162
|
)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
163
|
+
usgs_collection = format_dict_items(collection_def_params, **kwargs)[
|
|
164
|
+
"_collection"
|
|
165
|
+
]
|
|
166
|
+
start_date = kwargs.pop("start_datetime", None)
|
|
167
|
+
end_date = kwargs.pop("end_datetime", None)
|
|
168
168
|
geom = kwargs.pop("geometry", None)
|
|
169
169
|
footprint: dict[str, str] = {}
|
|
170
170
|
if hasattr(geom, "bounds"):
|
|
@@ -201,14 +201,16 @@ class UsgsApi(Api):
|
|
|
201
201
|
|
|
202
202
|
# search by id
|
|
203
203
|
if searched_id := kwargs.get("id"):
|
|
204
|
-
dataset_filters = api.dataset_filters(
|
|
204
|
+
dataset_filters = api.dataset_filters(usgs_collection)
|
|
205
205
|
# ip pattern set as parameter queryable (first element of param conf list)
|
|
206
206
|
id_pattern = self.config.metadata_mapping["id"][0]
|
|
207
207
|
# loop on matching dataset_filters until one returns expected results
|
|
208
208
|
for dataset_filter in dataset_filters["data"]:
|
|
209
209
|
if id_pattern in dataset_filter["searchSql"]:
|
|
210
210
|
logger.debug(
|
|
211
|
-
|
|
211
|
+
"Try using %s dataset filter to search by id on %s",
|
|
212
|
+
dataset_filter["searchSql"],
|
|
213
|
+
usgs_collection,
|
|
212
214
|
)
|
|
213
215
|
full_api_search_kwargs = {
|
|
214
216
|
"where": {
|
|
@@ -218,19 +220,19 @@ class UsgsApi(Api):
|
|
|
218
220
|
**api_search_kwargs,
|
|
219
221
|
}
|
|
220
222
|
logger.info(
|
|
221
|
-
f"Sending search request for {
|
|
223
|
+
f"Sending search request for {usgs_collection} with {full_api_search_kwargs}"
|
|
222
224
|
)
|
|
223
225
|
results = api.scene_search(
|
|
224
|
-
|
|
226
|
+
usgs_collection, **full_api_search_kwargs
|
|
225
227
|
)
|
|
226
228
|
if len(results["data"]["results"]) == 1:
|
|
227
229
|
# search by id using this dataset_filter succeeded
|
|
228
230
|
break
|
|
229
231
|
else:
|
|
230
232
|
logger.info(
|
|
231
|
-
f"Sending search request for {
|
|
233
|
+
f"Sending search request for {usgs_collection} with {api_search_kwargs}"
|
|
232
234
|
)
|
|
233
|
-
results = api.scene_search(
|
|
235
|
+
results = api.scene_search(usgs_collection, **api_search_kwargs)
|
|
234
236
|
|
|
235
237
|
# update results with storage info from download_options()
|
|
236
238
|
results_by_entity_id = {
|
|
@@ -240,7 +242,7 @@ class UsgsApi(Api):
|
|
|
240
242
|
f"Adapting {len(results_by_entity_id)} plugin results to eodag product representation"
|
|
241
243
|
)
|
|
242
244
|
download_options = api.download_options(
|
|
243
|
-
|
|
245
|
+
usgs_collection, list(results_by_entity_id.keys())
|
|
244
246
|
)
|
|
245
247
|
if download_options.get("data") is not None:
|
|
246
248
|
for download_option in download_options["data"]:
|
|
@@ -262,7 +264,7 @@ class UsgsApi(Api):
|
|
|
262
264
|
results["data"]["results"] = list(results_by_entity_id.values())
|
|
263
265
|
|
|
264
266
|
for result in results["data"]["results"]:
|
|
265
|
-
result["
|
|
267
|
+
result["collection"] = usgs_collection
|
|
266
268
|
|
|
267
269
|
product_properties = properties_from_json(
|
|
268
270
|
result, self.config.metadata_mapping
|
|
@@ -270,7 +272,7 @@ class UsgsApi(Api):
|
|
|
270
272
|
|
|
271
273
|
final.append(
|
|
272
274
|
EOProduct(
|
|
273
|
-
|
|
275
|
+
collection=collection,
|
|
274
276
|
provider=self.provider,
|
|
275
277
|
properties=product_properties,
|
|
276
278
|
geometry=footprint,
|
|
@@ -278,7 +280,7 @@ class UsgsApi(Api):
|
|
|
278
280
|
)
|
|
279
281
|
except USGSError as e:
|
|
280
282
|
logger.warning(
|
|
281
|
-
f"
|
|
283
|
+
f"Collection {usgs_collection} may not exist on USGS EE catalog"
|
|
282
284
|
)
|
|
283
285
|
api.logout()
|
|
284
286
|
raise RequestError.from_error(e) from e
|
|
@@ -297,7 +299,7 @@ class UsgsApi(Api):
|
|
|
297
299
|
def download(
|
|
298
300
|
self,
|
|
299
301
|
product: EOProduct,
|
|
300
|
-
auth: Optional[Union[AuthBase,
|
|
302
|
+
auth: Optional[Union[AuthBase, S3ServiceResource]] = None,
|
|
301
303
|
progress_callback: Optional[ProgressCallback] = None,
|
|
302
304
|
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
303
305
|
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
@@ -314,7 +316,7 @@ class UsgsApi(Api):
|
|
|
314
316
|
output_extension = cast(
|
|
315
317
|
str,
|
|
316
318
|
self.config.products.get( # type: ignore
|
|
317
|
-
product.
|
|
319
|
+
product.collection, self.config.products[GENERIC_COLLECTION] # type: ignore
|
|
318
320
|
).get("output_extension", ".tar.gz"),
|
|
319
321
|
)
|
|
320
322
|
kwargs["output_extension"] = kwargs.get("output_extension", output_extension)
|
|
@@ -335,11 +337,13 @@ class UsgsApi(Api):
|
|
|
335
337
|
raise NotAvailableError(
|
|
336
338
|
f"No USGS products found for {product.properties['id']}"
|
|
337
339
|
)
|
|
338
|
-
|
|
340
|
+
usgs_dataset = self.config.products.get(product.collection, {}).get(
|
|
341
|
+
"_collection", GENERIC_COLLECTION
|
|
342
|
+
)
|
|
339
343
|
download_request_results = api.download_request(
|
|
340
|
-
|
|
341
|
-
product.properties["entityId"],
|
|
342
|
-
product.properties["productId"],
|
|
344
|
+
usgs_dataset,
|
|
345
|
+
product.properties["usgs:entityId"],
|
|
346
|
+
product.properties["usgs:productId"],
|
|
343
347
|
)
|
|
344
348
|
|
|
345
349
|
req_urls: list[str] = []
|
|
@@ -422,7 +426,7 @@ class UsgsApi(Api):
|
|
|
422
426
|
download_request(product, fs_path, progress_callback, **kwargs)
|
|
423
427
|
|
|
424
428
|
with open(record_filename, "w") as fh:
|
|
425
|
-
fh.write(product.properties["
|
|
429
|
+
fh.write(product.properties["eodag:download_link"])
|
|
426
430
|
logger.debug(f"Download recorded in {record_filename}")
|
|
427
431
|
|
|
428
432
|
api.logout()
|
|
@@ -466,7 +470,7 @@ class UsgsApi(Api):
|
|
|
466
470
|
def download_all(
|
|
467
471
|
self,
|
|
468
472
|
products: SearchResult,
|
|
469
|
-
auth: Optional[Union[AuthBase,
|
|
473
|
+
auth: Optional[Union[AuthBase, S3ServiceResource]] = None,
|
|
470
474
|
downloaded_callback: Optional[DownloadedCallback] = None,
|
|
471
475
|
progress_callback: Optional[ProgressCallback] = None,
|
|
472
476
|
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
@@ -27,8 +27,6 @@ if TYPE_CHECKING:
|
|
|
27
27
|
from mypy_boto3_s3 import S3ServiceResource
|
|
28
28
|
from requests.auth import AuthBase
|
|
29
29
|
|
|
30
|
-
from eodag.types import S3SessionKwargs
|
|
31
|
-
|
|
32
30
|
|
|
33
31
|
class Authentication(PluginTopic):
|
|
34
32
|
"""Plugins authentication Base plugin
|
|
@@ -42,7 +40,7 @@ class Authentication(PluginTopic):
|
|
|
42
40
|
configuration that needs authentication and helps identifying it
|
|
43
41
|
"""
|
|
44
42
|
|
|
45
|
-
def authenticate(self) -> Union[AuthBase,
|
|
43
|
+
def authenticate(self) -> Union[AuthBase, S3ServiceResource]:
|
|
46
44
|
"""Authenticate"""
|
|
47
45
|
raise NotImplementedError
|
|
48
46
|
|
|
@@ -49,7 +49,7 @@ class FilterDate(Crunch):
|
|
|
49
49
|
@staticmethod
|
|
50
50
|
def sort_product_by_start_date(product: EOProduct) -> dt:
|
|
51
51
|
"""Get product start date"""
|
|
52
|
-
start_date = product.properties.get("
|
|
52
|
+
start_date = product.properties.get("start_datetime")
|
|
53
53
|
if not start_date:
|
|
54
54
|
# Retrieve year, month, day, hour, minute, second of EPOCH start
|
|
55
55
|
epoch = time.gmtime(0)[:-3]
|
|
@@ -93,7 +93,7 @@ class FilterDate(Crunch):
|
|
|
93
93
|
for product in products:
|
|
94
94
|
|
|
95
95
|
# product start date
|
|
96
|
-
product_start_str = product.properties.get("
|
|
96
|
+
product_start_str = product.properties.get("start_datetime")
|
|
97
97
|
if product_start_str:
|
|
98
98
|
product_start = dateutil.parser.parse(product_start_str)
|
|
99
99
|
if not product_start.tzinfo:
|
|
@@ -102,7 +102,7 @@ class FilterDate(Crunch):
|
|
|
102
102
|
product_start = None
|
|
103
103
|
|
|
104
104
|
# product end date
|
|
105
|
-
product_end_str = product.properties.get("
|
|
105
|
+
product_end_str = product.properties.get("end_datetime")
|
|
106
106
|
if product_end_str:
|
|
107
107
|
product_end = dateutil.parser.parse(product_end_str)
|
|
108
108
|
if not product_end.tzinfo:
|
|
@@ -46,7 +46,7 @@ class FilterLatestIntersect(Crunch):
|
|
|
46
46
|
@staticmethod
|
|
47
47
|
def sort_product_by_start_date(product: EOProduct) -> dt:
|
|
48
48
|
"""Get product start date"""
|
|
49
|
-
start_date = product.properties.get("
|
|
49
|
+
start_date = product.properties.get("start_datetime")
|
|
50
50
|
if not start_date:
|
|
51
51
|
# Retrieve year, month, day, hour, minute, second of EPOCH start
|
|
52
52
|
epoch = time.gmtime(0)[:-3]
|
|
@@ -67,7 +67,7 @@ class FilterLatestIntersect(Crunch):
|
|
|
67
67
|
logger.debug("Start filtering for latest products")
|
|
68
68
|
if not products:
|
|
69
69
|
return []
|
|
70
|
-
# Warning: May crash if
|
|
70
|
+
# Warning: May crash if start_datetime is not in the appropriate format
|
|
71
71
|
products.sort(key=self.sort_product_by_start_date, reverse=True)
|
|
72
72
|
filtered: list[EOProduct] = []
|
|
73
73
|
add_to_filtered = filtered.append
|
|
@@ -75,7 +75,7 @@ class FilterLatestByName(Crunch):
|
|
|
75
75
|
logger.debug(
|
|
76
76
|
"Latest product found for tileid=%s: date=%s",
|
|
77
77
|
tileid,
|
|
78
|
-
product.properties["
|
|
78
|
+
product.properties["start_datetime"],
|
|
79
79
|
)
|
|
80
80
|
filtered.append(product)
|
|
81
81
|
processed.append(tileid)
|