eodag 3.0.0b1__py3-none-any.whl → 3.0.0b3__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/__init__.py +6 -8
- eodag/api/core.py +119 -171
- eodag/api/product/__init__.py +10 -4
- eodag/api/product/_assets.py +52 -14
- eodag/api/product/_product.py +59 -30
- eodag/api/product/drivers/__init__.py +7 -2
- eodag/api/product/drivers/base.py +0 -3
- eodag/api/product/metadata_mapping.py +0 -28
- eodag/api/search_result.py +31 -9
- eodag/config.py +45 -41
- eodag/plugins/apis/base.py +3 -3
- eodag/plugins/apis/ecmwf.py +2 -3
- eodag/plugins/apis/usgs.py +43 -14
- eodag/plugins/authentication/aws_auth.py +11 -2
- eodag/plugins/authentication/openid_connect.py +5 -4
- eodag/plugins/authentication/token.py +2 -1
- eodag/plugins/crunch/base.py +3 -1
- eodag/plugins/crunch/filter_date.py +3 -9
- eodag/plugins/crunch/filter_latest_intersect.py +0 -3
- eodag/plugins/crunch/filter_latest_tpl_name.py +1 -4
- eodag/plugins/crunch/filter_overlap.py +4 -8
- eodag/plugins/crunch/filter_property.py +5 -11
- eodag/plugins/download/aws.py +46 -78
- eodag/plugins/download/base.py +27 -68
- eodag/plugins/download/http.py +48 -57
- eodag/plugins/download/s3rest.py +17 -25
- eodag/plugins/manager.py +6 -18
- eodag/plugins/search/__init__.py +9 -9
- eodag/plugins/search/base.py +7 -26
- eodag/plugins/search/build_search_result.py +0 -13
- eodag/plugins/search/cop_marine.py +1 -3
- eodag/plugins/search/creodias_s3.py +0 -3
- eodag/plugins/search/data_request_search.py +10 -5
- eodag/plugins/search/qssearch.py +95 -53
- eodag/plugins/search/static_stac_search.py +6 -3
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +24 -0
- eodag/resources/providers.yml +198 -154
- eodag/resources/user_conf_template.yml +27 -27
- eodag/rest/core.py +11 -43
- eodag/rest/server.py +1 -6
- eodag/rest/stac.py +13 -87
- eodag/rest/types/eodag_search.py +4 -7
- eodag/rest/types/queryables.py +4 -12
- eodag/rest/types/stac_search.py +7 -11
- eodag/rest/utils/rfc3339.py +0 -1
- eodag/types/__init__.py +9 -3
- eodag/types/download_args.py +14 -5
- eodag/types/search_args.py +7 -8
- eodag/types/whoosh.py +0 -2
- eodag/utils/__init__.py +20 -79
- eodag/utils/constraints.py +0 -8
- eodag/utils/import_system.py +0 -4
- eodag/utils/logging.py +0 -3
- eodag/utils/notebook.py +4 -4
- eodag/utils/repr.py +113 -0
- eodag/utils/requests.py +12 -20
- eodag/utils/rest.py +0 -4
- eodag/utils/stac_reader.py +2 -14
- {eodag-3.0.0b1.dist-info → eodag-3.0.0b3.dist-info}/METADATA +33 -14
- eodag-3.0.0b3.dist-info/RECORD +110 -0
- {eodag-3.0.0b1.dist-info → eodag-3.0.0b3.dist-info}/WHEEL +1 -1
- eodag-3.0.0b1.dist-info/RECORD +0 -109
- {eodag-3.0.0b1.dist-info → eodag-3.0.0b3.dist-info}/LICENSE +0 -0
- {eodag-3.0.0b1.dist-info → eodag-3.0.0b3.dist-info}/entry_points.txt +0 -0
- {eodag-3.0.0b1.dist-info → eodag-3.0.0b3.dist-info}/top_level.txt +0 -0
|
@@ -85,9 +85,7 @@ class BuildPostSearchResult(PostJsonSearch):
|
|
|
85
85
|
method before being loaded as json.
|
|
86
86
|
|
|
87
87
|
:param provider: An eodag providers configuration dictionary
|
|
88
|
-
:type provider: dict
|
|
89
88
|
:param config: Path to the user configuration file
|
|
90
|
-
:type config: str
|
|
91
89
|
"""
|
|
92
90
|
|
|
93
91
|
def count_hits(
|
|
@@ -125,11 +123,8 @@ class BuildPostSearchResult(PostJsonSearch):
|
|
|
125
123
|
"""Build :class:`~eodag.api.product._product.EOProduct` from provider result
|
|
126
124
|
|
|
127
125
|
:param results: Raw provider result as single dict in list
|
|
128
|
-
:type results: list
|
|
129
126
|
:param kwargs: Search arguments
|
|
130
|
-
:type kwargs: Union[int, str, bool, dict, list]
|
|
131
127
|
:returns: list of single :class:`~eodag.api.product._product.EOProduct`
|
|
132
|
-
:rtype: list
|
|
133
128
|
"""
|
|
134
129
|
product_type = kwargs.get("productType")
|
|
135
130
|
|
|
@@ -256,9 +251,7 @@ class BuildSearchResult(BuildPostSearchResult):
|
|
|
256
251
|
- **constraints_file_url**: url of the constraint file used to build queryables
|
|
257
252
|
|
|
258
253
|
:param provider: An eodag providers configuration dictionary
|
|
259
|
-
:type provider: dict
|
|
260
254
|
:param config: Path to the user configuration file
|
|
261
|
-
:type config: str
|
|
262
255
|
"""
|
|
263
256
|
|
|
264
257
|
def __init__(self, provider: str, config: PluginConfig) -> None:
|
|
@@ -355,12 +348,9 @@ class BuildSearchResult(BuildPostSearchResult):
|
|
|
355
348
|
default value is returned.
|
|
356
349
|
|
|
357
350
|
:param key: The configuration option key.
|
|
358
|
-
:type key: str
|
|
359
351
|
:param default: The default value to be returned if the option is not found (default is None).
|
|
360
|
-
:type default: Any
|
|
361
352
|
|
|
362
353
|
:return: The value of the specified configuration option or the default value.
|
|
363
|
-
:rtype: Any
|
|
364
354
|
"""
|
|
365
355
|
product_type_cfg = getattr(self.config, "product_type_config", {})
|
|
366
356
|
non_none_cfg = {k: v for k, v in product_type_cfg.items() if v}
|
|
@@ -376,7 +366,6 @@ class BuildSearchResult(BuildPostSearchResult):
|
|
|
376
366
|
in the input parameters, default values or values from the configuration are used.
|
|
377
367
|
|
|
378
368
|
:param params: Search parameters to be preprocessed.
|
|
379
|
-
:type params: dict
|
|
380
369
|
"""
|
|
381
370
|
_dc_qs = params.get("_dc_qs", None)
|
|
382
371
|
if _dc_qs is not None:
|
|
@@ -454,9 +443,7 @@ class BuildSearchResult(BuildPostSearchResult):
|
|
|
454
443
|
|
|
455
444
|
:param kwargs: additional filters for queryables (`productType` and other search
|
|
456
445
|
arguments)
|
|
457
|
-
:type kwargs: Any
|
|
458
446
|
:returns: fetched queryable parameters dict
|
|
459
|
-
:rtype: Optional[Dict[str, Annotated[Any, FieldInfo]]]
|
|
460
447
|
"""
|
|
461
448
|
constraints_file_url = getattr(self.config, "constraints_file_url", "")
|
|
462
449
|
if not constraints_file_url:
|
|
@@ -238,16 +238,14 @@ class CopMarineSearch(StaticStacSearch):
|
|
|
238
238
|
"""
|
|
239
239
|
Implementation of search for the Copernicus Marine provider
|
|
240
240
|
:param prep: object containing search parameterds
|
|
241
|
-
:type prep: PreparedSearch
|
|
242
241
|
:param kwargs: additional search arguments
|
|
243
242
|
:returns: list of products and total number of products
|
|
244
|
-
:rtype: Tuple[List[EOProduct], Optional[int]]
|
|
245
243
|
"""
|
|
246
244
|
page = prep.page
|
|
247
245
|
items_per_page = prep.items_per_page
|
|
248
246
|
|
|
249
247
|
# only return 1 page if pagination is disabled
|
|
250
|
-
if page > 1 and items_per_page <= 0:
|
|
248
|
+
if page is None or items_per_page is None or page > 1 and items_per_page <= 0:
|
|
251
249
|
return ([], 0) if prep.count else ([], None)
|
|
252
250
|
|
|
253
251
|
product_type = kwargs.get("productType", prep.product_type)
|
|
@@ -38,13 +38,10 @@ logger = logging.getLogger("eodag.search.creodiass3")
|
|
|
38
38
|
def patched_register_downloader(self, downloader, authenticator):
|
|
39
39
|
"""Add the download information to the product.
|
|
40
40
|
:param self: product to which information should be added
|
|
41
|
-
:type self: EoProduct
|
|
42
41
|
:param downloader: The download method that it can use
|
|
43
|
-
:type downloader: Concrete subclass of
|
|
44
42
|
:class:`~eodag.plugins.download.base.Download` or
|
|
45
43
|
:class:`~eodag.plugins.api.base.Api`
|
|
46
44
|
:param authenticator: The authentication method needed to perform the download
|
|
47
|
-
:type authenticator: Concrete subclass of
|
|
48
45
|
:class:`~eodag.plugins.authentication.base.Authentication`
|
|
49
46
|
"""
|
|
50
47
|
# register downloader
|
|
@@ -116,7 +116,6 @@ class DataRequestSearch(Search):
|
|
|
116
116
|
"""Fetch product types is disabled for `DataRequestSearch`
|
|
117
117
|
|
|
118
118
|
:returns: empty dict
|
|
119
|
-
:rtype: (optional) dict
|
|
120
119
|
"""
|
|
121
120
|
return None
|
|
122
121
|
|
|
@@ -133,7 +132,7 @@ class DataRequestSearch(Search):
|
|
|
133
132
|
"""
|
|
134
133
|
performs the search for a provider where several steps are required to fetch the data
|
|
135
134
|
"""
|
|
136
|
-
if kwargs.get("
|
|
135
|
+
if kwargs.get("sort_by"):
|
|
137
136
|
raise ValidationError(f"{self.provider} does not support sorting feature")
|
|
138
137
|
|
|
139
138
|
product_type = kwargs.get("productType", None)
|
|
@@ -386,8 +385,11 @@ class DataRequestSearch(Search):
|
|
|
386
385
|
total_items_nb_key_path = string_to_jsonpath(
|
|
387
386
|
self.config.pagination["total_items_nb_key_path"]
|
|
388
387
|
)
|
|
389
|
-
|
|
390
|
-
|
|
388
|
+
found_total_items_nb_paths = total_items_nb_key_path.find(results)
|
|
389
|
+
if found_total_items_nb_paths and not isinstance(
|
|
390
|
+
found_total_items_nb_paths, int
|
|
391
|
+
):
|
|
392
|
+
total_items_nb = found_total_items_nb_paths[0].value
|
|
391
393
|
else:
|
|
392
394
|
total_items_nb = 0
|
|
393
395
|
for p in products:
|
|
@@ -423,7 +425,10 @@ class DataRequestSearch(Search):
|
|
|
423
425
|
path = string_to_jsonpath(custom_filters["filter_attribute"])
|
|
424
426
|
indexes = custom_filters["indexes"].split("-")
|
|
425
427
|
for record in results:
|
|
426
|
-
|
|
428
|
+
found_paths = path.find(record)
|
|
429
|
+
if not found_paths or isinstance(found_paths, int):
|
|
430
|
+
continue
|
|
431
|
+
filter_param = found_paths[0].value
|
|
427
432
|
filter_value = filter_param[int(indexes[0]) : int(indexes[1])]
|
|
428
433
|
filter_clause = "'" + filter_value + "' " + custom_filters["filter_clause"]
|
|
429
434
|
if eval(filter_clause):
|
eodag/plugins/search/qssearch.py
CHANGED
|
@@ -19,7 +19,6 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import logging
|
|
21
21
|
import re
|
|
22
|
-
from collections.abc import Iterable
|
|
23
22
|
from copy import copy as copy_copy
|
|
24
23
|
from typing import (
|
|
25
24
|
TYPE_CHECKING,
|
|
@@ -28,6 +27,7 @@ from typing import (
|
|
|
28
27
|
Dict,
|
|
29
28
|
List,
|
|
30
29
|
Optional,
|
|
30
|
+
Sequence,
|
|
31
31
|
Set,
|
|
32
32
|
Tuple,
|
|
33
33
|
TypedDict,
|
|
@@ -48,6 +48,7 @@ import geojson
|
|
|
48
48
|
import orjson
|
|
49
49
|
import requests
|
|
50
50
|
import yaml
|
|
51
|
+
from jsonpath_ng import JSONPath
|
|
51
52
|
from lxml import etree
|
|
52
53
|
from pydantic import create_model
|
|
53
54
|
from pydantic.fields import FieldInfo
|
|
@@ -93,6 +94,7 @@ from eodag.utils.constraints import (
|
|
|
93
94
|
from eodag.utils.exceptions import (
|
|
94
95
|
AuthenticationError,
|
|
95
96
|
MisconfiguredError,
|
|
97
|
+
PluginImplementationError,
|
|
96
98
|
RequestError,
|
|
97
99
|
TimeOutError,
|
|
98
100
|
ValidationError,
|
|
@@ -197,12 +199,13 @@ class QueryStringSearch(Search):
|
|
|
197
199
|
``free_text_search_operations`` configuration parameter follow the same rule.
|
|
198
200
|
|
|
199
201
|
:param provider: An eodag providers configuration dictionary
|
|
200
|
-
:type provider: dict
|
|
201
202
|
:param config: Path to the user configuration file
|
|
202
|
-
:type config: str
|
|
203
203
|
"""
|
|
204
204
|
|
|
205
|
-
extract_properties
|
|
205
|
+
extract_properties: Dict[str, Callable[..., Dict[str, Any]]] = {
|
|
206
|
+
"xml": properties_from_xml,
|
|
207
|
+
"json": properties_from_json,
|
|
208
|
+
}
|
|
206
209
|
|
|
207
210
|
def __init__(self, provider: str, config: PluginConfig) -> None:
|
|
208
211
|
super(QueryStringSearch, self).__init__(provider, config)
|
|
@@ -360,7 +363,6 @@ class QueryStringSearch(Search):
|
|
|
360
363
|
"""Fetch product types list from provider using `discover_product_types` conf
|
|
361
364
|
|
|
362
365
|
:returns: configuration dict containing fetched product types information
|
|
363
|
-
:rtype: (optional) dict
|
|
364
366
|
"""
|
|
365
367
|
try:
|
|
366
368
|
prep = PreparedSearch()
|
|
@@ -527,9 +529,7 @@ class QueryStringSearch(Search):
|
|
|
527
529
|
"""
|
|
528
530
|
retrieves additional product type information from an endpoint returning data for a single collection
|
|
529
531
|
:param product_type: product type
|
|
530
|
-
:type product_type: str
|
|
531
532
|
:return: product types and their metadata
|
|
532
|
-
:rtype: Dict[str, Any]
|
|
533
533
|
"""
|
|
534
534
|
single_collection_url = self.config.discover_product_types[
|
|
535
535
|
"single_collection_fetch_url"
|
|
@@ -558,9 +558,7 @@ class QueryStringSearch(Search):
|
|
|
558
558
|
|
|
559
559
|
:param kwargs: additional filters for queryables (`productType` and other search
|
|
560
560
|
arguments)
|
|
561
|
-
:type kwargs: Any
|
|
562
561
|
:returns: fetched queryable parameters dict
|
|
563
|
-
:rtype: Optional[Dict[str, Annotated[Any, FieldInfo]]]
|
|
564
562
|
"""
|
|
565
563
|
product_type = kwargs.pop("productType", None)
|
|
566
564
|
if not product_type:
|
|
@@ -630,7 +628,7 @@ class QueryStringSearch(Search):
|
|
|
630
628
|
)
|
|
631
629
|
)
|
|
632
630
|
|
|
633
|
-
field_definitions = dict()
|
|
631
|
+
field_definitions: Dict[str, Any] = dict()
|
|
634
632
|
for json_param, json_mtd in constraint_params.items():
|
|
635
633
|
param = (
|
|
636
634
|
get_queryable_from_provider(
|
|
@@ -657,7 +655,6 @@ class QueryStringSearch(Search):
|
|
|
657
655
|
"""Perform a search on an OpenSearch-like interface
|
|
658
656
|
|
|
659
657
|
:param prep: Object collecting needed information for search.
|
|
660
|
-
:type prep: :class:`~eodag.plugins.search.PreparedSearch`
|
|
661
658
|
"""
|
|
662
659
|
count = prep.count
|
|
663
660
|
product_type = kwargs.get("productType", prep.product_type)
|
|
@@ -751,7 +748,9 @@ class QueryStringSearch(Search):
|
|
|
751
748
|
|
|
752
749
|
# Build the final query string, in one go without quoting it
|
|
753
750
|
# (some providers do not operate well with urlencoded and quoted query strings)
|
|
754
|
-
quote_via:
|
|
751
|
+
def quote_via(x: Any, *_args, **_kwargs) -> str:
|
|
752
|
+
return x
|
|
753
|
+
|
|
755
754
|
return (
|
|
756
755
|
query_params,
|
|
757
756
|
urlencode(query_params, doseq=True, quote_via=quote_via),
|
|
@@ -783,7 +782,7 @@ class QueryStringSearch(Search):
|
|
|
783
782
|
prep.need_count = True
|
|
784
783
|
prep.total_items_nb = None
|
|
785
784
|
|
|
786
|
-
for collection in self.get_collections(prep, **kwargs):
|
|
785
|
+
for collection in self.get_collections(prep, **kwargs) or (None,):
|
|
787
786
|
# skip empty collection if one is required in api_endpoint
|
|
788
787
|
if "{collection}" in self.config.api_endpoint and not collection:
|
|
789
788
|
continue
|
|
@@ -811,6 +810,10 @@ class QueryStringSearch(Search):
|
|
|
811
810
|
0 if total_results is None else total_results
|
|
812
811
|
)
|
|
813
812
|
total_results += _total_results or 0
|
|
813
|
+
if "next_page_url_tpl" not in self.config.pagination:
|
|
814
|
+
raise MisconfiguredError(
|
|
815
|
+
f"next_page_url_tpl is missing in {self.provider} search.pagination configuration"
|
|
816
|
+
)
|
|
814
817
|
next_url = self.config.pagination["next_page_url_tpl"].format(
|
|
815
818
|
url=search_endpoint,
|
|
816
819
|
search=qs_with_sort,
|
|
@@ -833,7 +836,6 @@ class QueryStringSearch(Search):
|
|
|
833
836
|
as this number is reached
|
|
834
837
|
|
|
835
838
|
:param prep: Object collecting needed information for search.
|
|
836
|
-
:type prep: :class:`~eodag.plugins.search.PreparedSearch`
|
|
837
839
|
"""
|
|
838
840
|
items_per_page = prep.items_per_page
|
|
839
841
|
total_items_nb = 0
|
|
@@ -873,7 +875,7 @@ class QueryStringSearch(Search):
|
|
|
873
875
|
)
|
|
874
876
|
result = (
|
|
875
877
|
[etree.tostring(element_or_tree=entry) for entry in results_xpath]
|
|
876
|
-
if isinstance(results_xpath,
|
|
878
|
+
if isinstance(results_xpath, Sequence)
|
|
877
879
|
else []
|
|
878
880
|
)
|
|
879
881
|
|
|
@@ -893,7 +895,7 @@ class QueryStringSearch(Search):
|
|
|
893
895
|
)
|
|
894
896
|
total_nb_results = (
|
|
895
897
|
total_nb_results_xpath
|
|
896
|
-
if isinstance(total_nb_results_xpath,
|
|
898
|
+
if isinstance(total_nb_results_xpath, Sequence)
|
|
897
899
|
else []
|
|
898
900
|
)[0]
|
|
899
901
|
_total_items_nb = int(total_nb_results)
|
|
@@ -910,55 +912,60 @@ class QueryStringSearch(Search):
|
|
|
910
912
|
resp_as_json = response.json()
|
|
911
913
|
if next_page_url_key_path:
|
|
912
914
|
path_parsed = next_page_url_key_path
|
|
913
|
-
|
|
914
|
-
|
|
915
|
+
found_paths = path_parsed.find(resp_as_json)
|
|
916
|
+
if found_paths and not isinstance(found_paths, int):
|
|
917
|
+
self.next_page_url = found_paths[0].value
|
|
915
918
|
logger.debug(
|
|
916
919
|
"Next page URL collected and set for the next search",
|
|
917
920
|
)
|
|
918
|
-
|
|
921
|
+
else:
|
|
919
922
|
logger.debug("Next page URL could not be collected")
|
|
920
923
|
if next_page_query_obj_key_path:
|
|
921
924
|
path_parsed = next_page_query_obj_key_path
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
].value
|
|
925
|
+
found_paths = path_parsed.find(resp_as_json)
|
|
926
|
+
if found_paths and not isinstance(found_paths, int):
|
|
927
|
+
self.next_page_query_obj = found_paths[0].value
|
|
926
928
|
logger.debug(
|
|
927
929
|
"Next page Query-object collected and set for the next search",
|
|
928
930
|
)
|
|
929
|
-
|
|
931
|
+
else:
|
|
930
932
|
logger.debug("Next page Query-object could not be collected")
|
|
931
933
|
if next_page_merge_key_path:
|
|
932
934
|
path_parsed = next_page_merge_key_path
|
|
933
|
-
|
|
934
|
-
|
|
935
|
+
found_paths = path_parsed.find(resp_as_json)
|
|
936
|
+
if found_paths and not isinstance(found_paths, int):
|
|
937
|
+
self.next_page_merge = found_paths[0].value
|
|
935
938
|
logger.debug(
|
|
936
939
|
"Next page merge collected and set for the next search",
|
|
937
940
|
)
|
|
938
|
-
|
|
941
|
+
else:
|
|
939
942
|
logger.debug("Next page merge could not be collected")
|
|
940
943
|
|
|
941
944
|
results_entry = string_to_jsonpath(
|
|
942
945
|
self.config.results_entry, force=True
|
|
943
946
|
)
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
+
found_entry_paths = results_entry.find(resp_as_json)
|
|
948
|
+
if found_entry_paths and not isinstance(found_entry_paths, int):
|
|
949
|
+
result = found_entry_paths[0].value
|
|
950
|
+
else:
|
|
947
951
|
result = []
|
|
948
952
|
if not isinstance(result, list):
|
|
949
953
|
result = [result]
|
|
950
954
|
|
|
951
955
|
if getattr(prep, "need_count", False):
|
|
952
956
|
# extract total_items_nb from search results
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
+
found_total_items_nb_paths = total_items_nb_key_path_parsed.find(
|
|
958
|
+
resp_as_json
|
|
959
|
+
)
|
|
960
|
+
if found_total_items_nb_paths and not isinstance(
|
|
961
|
+
found_total_items_nb_paths, int
|
|
962
|
+
):
|
|
963
|
+
_total_items_nb = found_total_items_nb_paths[0].value
|
|
957
964
|
if getattr(self.config, "merge_responses", False):
|
|
958
965
|
total_items_nb = _total_items_nb or 0
|
|
959
966
|
else:
|
|
960
967
|
total_items_nb += _total_items_nb or 0
|
|
961
|
-
|
|
968
|
+
else:
|
|
962
969
|
logger.debug(
|
|
963
970
|
"Could not extract total_items_nb from search results"
|
|
964
971
|
)
|
|
@@ -1036,25 +1043,34 @@ class QueryStringSearch(Search):
|
|
|
1036
1043
|
count_results = response.json()
|
|
1037
1044
|
if isinstance(count_results, dict):
|
|
1038
1045
|
path_parsed = self.config.pagination["total_items_nb_key_path"]
|
|
1039
|
-
|
|
1046
|
+
if not isinstance(path_parsed, JSONPath):
|
|
1047
|
+
raise PluginImplementationError(
|
|
1048
|
+
"total_items_nb_key_path must be parsed to JSONPath on plugin init"
|
|
1049
|
+
)
|
|
1050
|
+
found_paths = path_parsed.find(count_results)
|
|
1051
|
+
if found_paths and not isinstance(found_paths, int):
|
|
1052
|
+
total_results = found_paths[0].value
|
|
1053
|
+
else:
|
|
1054
|
+
raise MisconfiguredError(
|
|
1055
|
+
"Could not get results count from response using total_items_nb_key_path"
|
|
1056
|
+
)
|
|
1040
1057
|
else: # interpret the result as a raw int
|
|
1041
1058
|
total_results = int(count_results)
|
|
1042
1059
|
return total_results
|
|
1043
1060
|
|
|
1044
|
-
def get_collections(
|
|
1045
|
-
self, prep: PreparedSearch, **kwargs: Any
|
|
1046
|
-
) -> Tuple[Set[Dict[str, Any]], ...]:
|
|
1061
|
+
def get_collections(self, prep: PreparedSearch, **kwargs: Any) -> Tuple[str, ...]:
|
|
1047
1062
|
"""Get the collection to which the product belongs"""
|
|
1048
1063
|
# See https://earth.esa.int/web/sentinel/missions/sentinel-2/news/-
|
|
1049
1064
|
# /asset_publisher/Ac0d/content/change-of
|
|
1050
1065
|
# -format-for-new-sentinel-2-level-1c-products-starting-on-6-december
|
|
1051
1066
|
product_type: Optional[str] = kwargs.get("productType")
|
|
1067
|
+
collection: Optional[str] = None
|
|
1052
1068
|
if product_type is None and (
|
|
1053
1069
|
not hasattr(prep, "product_type_def_params")
|
|
1054
1070
|
or not prep.product_type_def_params
|
|
1055
1071
|
):
|
|
1056
|
-
collections: Set[
|
|
1057
|
-
collection
|
|
1072
|
+
collections: Set[str] = set()
|
|
1073
|
+
collection = getattr(self.config, "collection", None)
|
|
1058
1074
|
if collection is None:
|
|
1059
1075
|
try:
|
|
1060
1076
|
for product_type, product_config in self.config.products.items():
|
|
@@ -1072,18 +1088,26 @@ class QueryStringSearch(Search):
|
|
|
1072
1088
|
collections.add(collection)
|
|
1073
1089
|
return tuple(collections)
|
|
1074
1090
|
|
|
1075
|
-
collection
|
|
1091
|
+
collection = getattr(self.config, "collection", None)
|
|
1076
1092
|
if collection is None:
|
|
1077
1093
|
collection = (
|
|
1078
1094
|
prep.product_type_def_params.get("collection", None) or product_type
|
|
1079
1095
|
)
|
|
1080
|
-
|
|
1096
|
+
|
|
1097
|
+
if collection is None:
|
|
1098
|
+
return ()
|
|
1099
|
+
elif not isinstance(collection, list):
|
|
1100
|
+
return (collection,)
|
|
1101
|
+
else:
|
|
1102
|
+
return tuple(collection)
|
|
1081
1103
|
|
|
1082
1104
|
def _request(
|
|
1083
1105
|
self,
|
|
1084
1106
|
prep: PreparedSearch,
|
|
1085
1107
|
) -> Response:
|
|
1086
1108
|
url = prep.url
|
|
1109
|
+
if url is None:
|
|
1110
|
+
raise ValidationError("Cannot request empty URL")
|
|
1087
1111
|
info_message = prep.info_message
|
|
1088
1112
|
exception_message = prep.exception_message
|
|
1089
1113
|
try:
|
|
@@ -1329,8 +1353,11 @@ class PostJsonSearch(QueryStringSearch):
|
|
|
1329
1353
|
"specific_qssearch"
|
|
1330
1354
|
].get("merge_responses", None)
|
|
1331
1355
|
|
|
1332
|
-
self
|
|
1333
|
-
|
|
1356
|
+
def count_hits(self, *x, **y):
|
|
1357
|
+
return 1
|
|
1358
|
+
|
|
1359
|
+
def _request(self, *x, **y):
|
|
1360
|
+
return super(PostJsonSearch, self)._request(*x, **y)
|
|
1334
1361
|
|
|
1335
1362
|
try:
|
|
1336
1363
|
eo_products, total_items = super(PostJsonSearch, self).query(
|
|
@@ -1431,7 +1458,7 @@ class PostJsonSearch(QueryStringSearch):
|
|
|
1431
1458
|
auth_conf_dict = getattr(prep.auth_plugin.config, "credentials", {})
|
|
1432
1459
|
else:
|
|
1433
1460
|
auth_conf_dict = {}
|
|
1434
|
-
for collection in self.get_collections(prep, **kwargs):
|
|
1461
|
+
for collection in self.get_collections(prep, **kwargs) or (None,):
|
|
1435
1462
|
try:
|
|
1436
1463
|
search_endpoint: str = self.config.api_endpoint.rstrip("/").format(
|
|
1437
1464
|
**dict(collection=collection, **auth_conf_dict)
|
|
@@ -1454,7 +1481,11 @@ class PostJsonSearch(QueryStringSearch):
|
|
|
1454
1481
|
if getattr(self.config, "merge_responses", False):
|
|
1455
1482
|
total_results = _total_results or 0
|
|
1456
1483
|
else:
|
|
1457
|
-
total_results
|
|
1484
|
+
total_results = (
|
|
1485
|
+
(_total_results or 0)
|
|
1486
|
+
if total_results is None
|
|
1487
|
+
else total_results + (_total_results or 0)
|
|
1488
|
+
)
|
|
1458
1489
|
if "next_page_query_obj" in self.config.pagination and isinstance(
|
|
1459
1490
|
self.config.pagination["next_page_query_obj"], str
|
|
1460
1491
|
):
|
|
@@ -1479,6 +1510,8 @@ class PostJsonSearch(QueryStringSearch):
|
|
|
1479
1510
|
prep: PreparedSearch,
|
|
1480
1511
|
) -> Response:
|
|
1481
1512
|
url = prep.url
|
|
1513
|
+
if url is None:
|
|
1514
|
+
raise ValidationError("Cannot request empty URL")
|
|
1482
1515
|
info_message = prep.info_message
|
|
1483
1516
|
exception_message = prep.exception_message
|
|
1484
1517
|
timeout = getattr(self.config, "timeout", HTTP_REQ_TIMEOUT)
|
|
@@ -1497,7 +1530,10 @@ class PostJsonSearch(QueryStringSearch):
|
|
|
1497
1530
|
kwargs["auth"] = prep.auth
|
|
1498
1531
|
|
|
1499
1532
|
# perform the request using the next page arguments if they are defined
|
|
1500
|
-
if
|
|
1533
|
+
if (
|
|
1534
|
+
hasattr(self, "next_page_query_obj")
|
|
1535
|
+
and self.next_page_query_obj is not None
|
|
1536
|
+
):
|
|
1501
1537
|
prep.query_params = self.next_page_query_obj
|
|
1502
1538
|
if info_message:
|
|
1503
1539
|
logger.info(info_message)
|
|
@@ -1520,7 +1556,9 @@ class PostJsonSearch(QueryStringSearch):
|
|
|
1520
1556
|
if not isinstance(auth_errors, list):
|
|
1521
1557
|
auth_errors = [auth_errors]
|
|
1522
1558
|
if (
|
|
1523
|
-
hasattr(err
|
|
1559
|
+
hasattr(err, "response")
|
|
1560
|
+
and err.response is not None
|
|
1561
|
+
and getattr(err.response, "status_code", None)
|
|
1524
1562
|
and err.response.status_code in auth_errors
|
|
1525
1563
|
):
|
|
1526
1564
|
raise AuthenticationError(
|
|
@@ -1542,7 +1580,11 @@ class PostJsonSearch(QueryStringSearch):
|
|
|
1542
1580
|
if "response" in locals():
|
|
1543
1581
|
logger.debug(response.content)
|
|
1544
1582
|
error_text = str(err)
|
|
1545
|
-
if
|
|
1583
|
+
if (
|
|
1584
|
+
hasattr(err, "response")
|
|
1585
|
+
and err.response is not None
|
|
1586
|
+
and getattr(err.response, "text", None)
|
|
1587
|
+
):
|
|
1546
1588
|
error_text = err.response.text
|
|
1547
1589
|
raise RequestError(error_text) from err
|
|
1548
1590
|
return response
|
|
@@ -1578,7 +1620,9 @@ class StacSearch(PostJsonSearch):
|
|
|
1578
1620
|
|
|
1579
1621
|
# Build the final query string, in one go without quoting it
|
|
1580
1622
|
# (some providers do not operate well with urlencoded and quoted query strings)
|
|
1581
|
-
quote_via:
|
|
1623
|
+
def quote_via(x: Any, *_args, **_kwargs) -> str:
|
|
1624
|
+
return x
|
|
1625
|
+
|
|
1582
1626
|
return (
|
|
1583
1627
|
query_params,
|
|
1584
1628
|
urlencode(query_params, doseq=True, quote_via=quote_via),
|
|
@@ -1591,9 +1635,7 @@ class StacSearch(PostJsonSearch):
|
|
|
1591
1635
|
|
|
1592
1636
|
:param kwargs: additional filters for queryables (`productType` and other search
|
|
1593
1637
|
arguments)
|
|
1594
|
-
:type kwargs: Any
|
|
1595
1638
|
:returns: fetched queryable parameters dict
|
|
1596
|
-
:rtype: Optional[Dict[str, Annotated[Any, FieldInfo]]]
|
|
1597
1639
|
"""
|
|
1598
1640
|
product_type = kwargs.get("productType", None)
|
|
1599
1641
|
provider_product_type = (
|
|
@@ -60,9 +60,7 @@ class StaticStacSearch(StacSearch):
|
|
|
60
60
|
Then it uses crunchers to only keep products matching query parameters.
|
|
61
61
|
|
|
62
62
|
:param provider: An eodag providers configuration dictionary
|
|
63
|
-
:type provider: dict
|
|
64
63
|
:param config: Path to the user configuration file
|
|
65
|
-
:type config: str
|
|
66
64
|
"""
|
|
67
65
|
|
|
68
66
|
def __init__(self, provider: str, config: PluginConfig) -> None:
|
|
@@ -85,12 +83,17 @@ class StaticStacSearch(StacSearch):
|
|
|
85
83
|
"total_items_nb_key_path", "$.null"
|
|
86
84
|
)
|
|
87
85
|
self.config.__dict__["pagination"].setdefault("max_items_per_page", -1)
|
|
86
|
+
# disable product types discovery by default (if endpoints equals to STAC API default)
|
|
87
|
+
if (
|
|
88
|
+
getattr(self.config, "discover_product_types", {}).get("fetch_url")
|
|
89
|
+
== "{api_endpoint}/../collections"
|
|
90
|
+
):
|
|
91
|
+
self.config.discover_product_types = {"fetch_url": None}
|
|
88
92
|
|
|
89
93
|
def discover_product_types(self, **kwargs: Any) -> Optional[Dict[str, Any]]:
|
|
90
94
|
"""Fetch product types list from a static STAC Catalog provider using `discover_product_types` conf
|
|
91
95
|
|
|
92
96
|
:returns: configuration dict containing fetched product types information
|
|
93
|
-
:rtype: Optional[Dict[str, Any]]
|
|
94
97
|
"""
|
|
95
98
|
fetch_url = cast(
|
|
96
99
|
str,
|