eodag 3.1.0b1__py3-none-any.whl → 3.1.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 +59 -52
- eodag/api/product/_assets.py +5 -5
- eodag/api/product/_product.py +27 -12
- eodag/api/product/drivers/__init__.py +81 -4
- eodag/api/product/drivers/base.py +65 -4
- eodag/api/product/drivers/generic.py +65 -0
- eodag/api/product/drivers/sentinel1.py +97 -0
- eodag/api/product/drivers/sentinel2.py +95 -0
- eodag/api/product/metadata_mapping.py +62 -74
- eodag/api/search_result.py +13 -23
- eodag/cli.py +4 -4
- eodag/config.py +66 -69
- eodag/plugins/apis/base.py +1 -1
- eodag/plugins/apis/ecmwf.py +10 -9
- eodag/plugins/apis/usgs.py +11 -10
- eodag/plugins/authentication/aws_auth.py +16 -13
- eodag/plugins/authentication/base.py +5 -3
- eodag/plugins/authentication/header.py +3 -3
- eodag/plugins/authentication/keycloak.py +4 -4
- eodag/plugins/authentication/oauth.py +7 -3
- eodag/plugins/authentication/openid_connect.py +14 -14
- eodag/plugins/authentication/sas_auth.py +4 -4
- eodag/plugins/authentication/token.py +7 -7
- eodag/plugins/authentication/token_exchange.py +1 -1
- eodag/plugins/base.py +4 -4
- eodag/plugins/crunch/base.py +4 -4
- eodag/plugins/crunch/filter_date.py +4 -4
- eodag/plugins/crunch/filter_latest_intersect.py +6 -6
- eodag/plugins/crunch/filter_latest_tpl_name.py +7 -7
- eodag/plugins/crunch/filter_overlap.py +4 -4
- eodag/plugins/crunch/filter_property.py +4 -4
- eodag/plugins/download/aws.py +47 -66
- eodag/plugins/download/base.py +8 -17
- eodag/plugins/download/creodias_s3.py +2 -2
- eodag/plugins/download/http.py +30 -32
- eodag/plugins/download/s3rest.py +5 -4
- eodag/plugins/manager.py +10 -20
- eodag/plugins/search/__init__.py +6 -5
- eodag/plugins/search/base.py +35 -40
- eodag/plugins/search/build_search_result.py +69 -68
- eodag/plugins/search/cop_marine.py +22 -12
- eodag/plugins/search/creodias_s3.py +8 -78
- eodag/plugins/search/csw.py +11 -11
- eodag/plugins/search/data_request_search.py +16 -15
- eodag/plugins/search/qssearch.py +56 -52
- eodag/plugins/search/stac_list_assets.py +85 -0
- eodag/plugins/search/static_stac_search.py +3 -3
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +288 -288
- eodag/resources/providers.yml +146 -6
- eodag/resources/stac_api.yml +2 -2
- eodag/resources/user_conf_template.yml +11 -0
- eodag/rest/cache.py +2 -2
- eodag/rest/config.py +3 -3
- eodag/rest/core.py +24 -24
- eodag/rest/errors.py +5 -5
- eodag/rest/server.py +3 -11
- eodag/rest/stac.py +40 -38
- eodag/rest/types/collections_search.py +3 -3
- eodag/rest/types/eodag_search.py +23 -23
- eodag/rest/types/queryables.py +13 -13
- eodag/rest/types/stac_search.py +15 -25
- eodag/rest/utils/__init__.py +11 -21
- eodag/rest/utils/cql_evaluate.py +6 -6
- eodag/rest/utils/rfc3339.py +2 -2
- eodag/types/__init__.py +24 -18
- eodag/types/bbox.py +2 -2
- eodag/types/download_args.py +2 -2
- eodag/types/queryables.py +5 -2
- eodag/types/search_args.py +4 -4
- eodag/types/whoosh.py +1 -3
- eodag/utils/__init__.py +81 -40
- eodag/utils/exceptions.py +2 -2
- eodag/utils/import_system.py +2 -2
- eodag/utils/requests.py +2 -2
- eodag/utils/rest.py +2 -2
- eodag/utils/s3.py +208 -0
- eodag/utils/stac_reader.py +10 -10
- {eodag-3.1.0b1.dist-info → eodag-3.1.0b2.dist-info}/METADATA +5 -4
- eodag-3.1.0b2.dist-info/RECORD +113 -0
- {eodag-3.1.0b1.dist-info → eodag-3.1.0b2.dist-info}/entry_points.txt +1 -0
- eodag-3.1.0b1.dist-info/RECORD +0 -108
- {eodag-3.1.0b1.dist-info → eodag-3.1.0b2.dist-info}/LICENSE +0 -0
- {eodag-3.1.0b1.dist-info → eodag-3.1.0b2.dist-info}/WHEEL +0 -0
- {eodag-3.1.0b1.dist-info → eodag-3.1.0b2.dist-info}/top_level.txt +0 -0
eodag/rest/stac.py
CHANGED
|
@@ -21,7 +21,7 @@ import logging
|
|
|
21
21
|
import os
|
|
22
22
|
from collections import defaultdict
|
|
23
23
|
from datetime import datetime, timezone
|
|
24
|
-
from typing import TYPE_CHECKING, Any,
|
|
24
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
25
25
|
from urllib.parse import (
|
|
26
26
|
parse_qs,
|
|
27
27
|
quote,
|
|
@@ -82,6 +82,8 @@ COLLECTION_PROPERTIES = [
|
|
|
82
82
|
"missionEndDate",
|
|
83
83
|
"keywords",
|
|
84
84
|
"stacCollection",
|
|
85
|
+
"alias",
|
|
86
|
+
"productType",
|
|
85
87
|
]
|
|
86
88
|
IGNORED_ITEM_PROPERTIES = [
|
|
87
89
|
"_id",
|
|
@@ -118,7 +120,7 @@ class StacCommon:
|
|
|
118
120
|
def __init__(
|
|
119
121
|
self,
|
|
120
122
|
url: str,
|
|
121
|
-
stac_config:
|
|
123
|
+
stac_config: dict[str, Any],
|
|
122
124
|
provider: Optional[str],
|
|
123
125
|
eodag_api: EODataAccessGateway,
|
|
124
126
|
root: str = "/",
|
|
@@ -129,9 +131,9 @@ class StacCommon:
|
|
|
129
131
|
self.eodag_api = eodag_api
|
|
130
132
|
self.root = root.rstrip("/") if len(root) > 1 else root
|
|
131
133
|
|
|
132
|
-
self.data:
|
|
134
|
+
self.data: dict[str, Any] = {}
|
|
133
135
|
|
|
134
|
-
def update_data(self, data:
|
|
136
|
+
def update_data(self, data: dict[str, Any]) -> None:
|
|
135
137
|
"""Updates data using given input STAC dict data
|
|
136
138
|
|
|
137
139
|
:param data: Catalog data (parsed STAC dict)
|
|
@@ -165,8 +167,8 @@ class StacCommon:
|
|
|
165
167
|
|
|
166
168
|
@staticmethod
|
|
167
169
|
def get_stac_extension(
|
|
168
|
-
url: str, stac_config:
|
|
169
|
-
) ->
|
|
170
|
+
url: str, stac_config: dict[str, Any], extension: str, **kwargs: Any
|
|
171
|
+
) -> dict[str, str]:
|
|
170
172
|
"""Parse STAC extension from config and return as dict
|
|
171
173
|
|
|
172
174
|
:param url: Requested URL
|
|
@@ -185,7 +187,7 @@ class StacCommon:
|
|
|
185
187
|
}
|
|
186
188
|
return format_dict_items(extension_model, **format_args)
|
|
187
189
|
|
|
188
|
-
def get_provider_dict(self, provider: str) ->
|
|
190
|
+
def get_provider_dict(self, provider: str) -> dict[str, Any]:
|
|
189
191
|
"""Generate STAC provider dict"""
|
|
190
192
|
provider_config = next(
|
|
191
193
|
p
|
|
@@ -214,7 +216,7 @@ class StacItem(StacCommon):
|
|
|
214
216
|
def __init__(
|
|
215
217
|
self,
|
|
216
218
|
url: str,
|
|
217
|
-
stac_config:
|
|
219
|
+
stac_config: dict[str, Any],
|
|
218
220
|
provider: Optional[str],
|
|
219
221
|
eodag_api: EODataAccessGateway,
|
|
220
222
|
root: str = "/",
|
|
@@ -228,8 +230,8 @@ class StacItem(StacCommon):
|
|
|
228
230
|
)
|
|
229
231
|
|
|
230
232
|
def __get_item_list(
|
|
231
|
-
self, search_results: SearchResult, catalog:
|
|
232
|
-
) ->
|
|
233
|
+
self, search_results: SearchResult, catalog: dict[str, Any]
|
|
234
|
+
) -> list[dict[str, Any]]:
|
|
233
235
|
"""Build STAC items list from EODAG search results
|
|
234
236
|
|
|
235
237
|
:param search_results: EODAG search results
|
|
@@ -244,7 +246,7 @@ class StacItem(StacCommon):
|
|
|
244
246
|
)
|
|
245
247
|
|
|
246
248
|
# check if some items need to be converted
|
|
247
|
-
need_conversion:
|
|
249
|
+
need_conversion: dict[str, Any] = {}
|
|
248
250
|
for k, v in item_model["properties"].items():
|
|
249
251
|
if isinstance(v, str):
|
|
250
252
|
conversion, item_model["properties"][k] = get_metadata_path(
|
|
@@ -264,11 +266,11 @@ class StacItem(StacCommon):
|
|
|
264
266
|
]
|
|
265
267
|
ignored_props = COLLECTION_PROPERTIES + item_props + IGNORED_ITEM_PROPERTIES
|
|
266
268
|
|
|
267
|
-
item_list:
|
|
269
|
+
item_list: list[dict[str, Any]] = []
|
|
268
270
|
for product in search_results:
|
|
269
271
|
product_dict = deepcopy(product.__dict__)
|
|
270
272
|
|
|
271
|
-
product_item:
|
|
273
|
+
product_item: dict[str, Any] = jsonpath_parse_dict_items(
|
|
272
274
|
item_model,
|
|
273
275
|
{
|
|
274
276
|
"product": product_dict,
|
|
@@ -370,10 +372,10 @@ class StacItem(StacCommon):
|
|
|
370
372
|
product: EOProduct,
|
|
371
373
|
downloadlink_href: str,
|
|
372
374
|
without_arg_url: str,
|
|
373
|
-
query_dict: Optional[
|
|
375
|
+
query_dict: Optional[dict[str, Any]] = None,
|
|
374
376
|
_dc_qs: Optional[str] = None,
|
|
375
|
-
) ->
|
|
376
|
-
assets:
|
|
377
|
+
) -> dict[str, Any]:
|
|
378
|
+
assets: dict[str, Any] = {}
|
|
377
379
|
settings = Settings.from_environment()
|
|
378
380
|
|
|
379
381
|
if _dc_qs:
|
|
@@ -452,9 +454,9 @@ class StacItem(StacCommon):
|
|
|
452
454
|
self,
|
|
453
455
|
search_results: SearchResult,
|
|
454
456
|
total: int,
|
|
455
|
-
catalog:
|
|
456
|
-
next_link: Optional[
|
|
457
|
-
) ->
|
|
457
|
+
catalog: dict[str, Any],
|
|
458
|
+
next_link: Optional[dict[str, Any]],
|
|
459
|
+
) -> dict[str, Any]:
|
|
458
460
|
"""Build STAC items from EODAG search results
|
|
459
461
|
|
|
460
462
|
:param search_results: EODAG search results
|
|
@@ -503,8 +505,8 @@ class StacItem(StacCommon):
|
|
|
503
505
|
return self.data
|
|
504
506
|
|
|
505
507
|
def __filter_item_model_properties(
|
|
506
|
-
self, item_model:
|
|
507
|
-
) ->
|
|
508
|
+
self, item_model: dict[str, Any], product_type: str
|
|
509
|
+
) -> dict[str, Any]:
|
|
508
510
|
"""Filter item model depending on product type metadata and its extensions.
|
|
509
511
|
Removes not needed parameters, and adds supplementary ones as
|
|
510
512
|
part of oseo extension.
|
|
@@ -568,13 +570,13 @@ class StacItem(StacCommon):
|
|
|
568
570
|
|
|
569
571
|
return result_item_model
|
|
570
572
|
|
|
571
|
-
def __filter_item_properties_values(self, item:
|
|
573
|
+
def __filter_item_properties_values(self, item: dict[str, Any]) -> dict[str, Any]:
|
|
572
574
|
"""Removes empty properties, unused extensions, and add missing extensions
|
|
573
575
|
|
|
574
576
|
:param item: STAC item data
|
|
575
577
|
:returns: Filtered item model
|
|
576
578
|
"""
|
|
577
|
-
all_extensions_dict:
|
|
579
|
+
all_extensions_dict: dict[str, str] = deepcopy(
|
|
578
580
|
self.stac_config["stac_extensions"]
|
|
579
581
|
)
|
|
580
582
|
# parse f-strings with root
|
|
@@ -599,7 +601,7 @@ class StacItem(StacCommon):
|
|
|
599
601
|
|
|
600
602
|
return item
|
|
601
603
|
|
|
602
|
-
def get_stac_item_from_product(self, product: EOProduct) ->
|
|
604
|
+
def get_stac_item_from_product(self, product: EOProduct) -> dict[str, Any]:
|
|
603
605
|
"""Build STAC item from EODAG product
|
|
604
606
|
|
|
605
607
|
:param product: EODAG product
|
|
@@ -659,7 +661,7 @@ class StacCollection(StacCommon):
|
|
|
659
661
|
"""
|
|
660
662
|
|
|
661
663
|
# External STAC collections
|
|
662
|
-
ext_stac_collections:
|
|
664
|
+
ext_stac_collections: dict[str, dict[str, Any]] = dict()
|
|
663
665
|
|
|
664
666
|
@classmethod
|
|
665
667
|
def fetch_external_stac_collections(cls, eodag_api: EODataAccessGateway) -> None:
|
|
@@ -688,7 +690,7 @@ class StacCollection(StacCommon):
|
|
|
688
690
|
def __init__(
|
|
689
691
|
self,
|
|
690
692
|
url: str,
|
|
691
|
-
stac_config:
|
|
693
|
+
stac_config: dict[str, Any],
|
|
692
694
|
provider: Optional[str],
|
|
693
695
|
eodag_api: EODataAccessGateway,
|
|
694
696
|
root: str = "/",
|
|
@@ -701,7 +703,7 @@ class StacCollection(StacCommon):
|
|
|
701
703
|
root=root,
|
|
702
704
|
)
|
|
703
705
|
|
|
704
|
-
def __list_product_type_providers(self, product_type:
|
|
706
|
+
def __list_product_type_providers(self, product_type: dict[str, Any]) -> list[str]:
|
|
705
707
|
"""Retrieve a list of providers for a given product type.
|
|
706
708
|
|
|
707
709
|
:param product_type: Dictionary containing information about the product type.
|
|
@@ -718,8 +720,8 @@ class StacCollection(StacCommon):
|
|
|
718
720
|
]
|
|
719
721
|
|
|
720
722
|
def __generate_stac_collection(
|
|
721
|
-
self, collection_model: Any, product_type:
|
|
722
|
-
) ->
|
|
723
|
+
self, collection_model: Any, product_type: dict[str, Any]
|
|
724
|
+
) -> dict[str, Any]:
|
|
723
725
|
"""Generate a STAC collection dictionary for a given product type.
|
|
724
726
|
|
|
725
727
|
:param collection_model: The base model for the STAC collection.
|
|
@@ -728,7 +730,7 @@ class StacCollection(StacCommon):
|
|
|
728
730
|
"""
|
|
729
731
|
providers = self.__list_product_type_providers(product_type)
|
|
730
732
|
|
|
731
|
-
providers_dict:
|
|
733
|
+
providers_dict: dict[str, dict[str, Any]] = {}
|
|
732
734
|
for provider in providers:
|
|
733
735
|
p_dict = self.get_provider_dict(provider)
|
|
734
736
|
providers_dict.setdefault(p_dict["name"], p_dict)
|
|
@@ -815,7 +817,7 @@ class StacCollection(StacCommon):
|
|
|
815
817
|
instrument: Optional[str] = None,
|
|
816
818
|
constellation: Optional[str] = None,
|
|
817
819
|
datetime: Optional[str] = None,
|
|
818
|
-
) ->
|
|
820
|
+
) -> list[dict[str, Any]]:
|
|
819
821
|
"""Build STAC collections list
|
|
820
822
|
|
|
821
823
|
:param filters: (optional) Additional filters for collections search
|
|
@@ -850,7 +852,7 @@ class StacCollection(StacCommon):
|
|
|
850
852
|
product_types = all_pt
|
|
851
853
|
|
|
852
854
|
# list product types with all metadata using guessed ids
|
|
853
|
-
collection_list:
|
|
855
|
+
collection_list: list[dict[str, Any]] = []
|
|
854
856
|
for product_type in product_types:
|
|
855
857
|
stac_collection = self.__generate_stac_collection(
|
|
856
858
|
collection_model, product_type
|
|
@@ -874,7 +876,7 @@ class StacCatalog(StacCommon):
|
|
|
874
876
|
def __init__(
|
|
875
877
|
self,
|
|
876
878
|
url: str,
|
|
877
|
-
stac_config:
|
|
879
|
+
stac_config: dict[str, Any],
|
|
878
880
|
provider: Optional[str],
|
|
879
881
|
eodag_api: EODataAccessGateway,
|
|
880
882
|
root: str = "/",
|
|
@@ -890,8 +892,8 @@ class StacCatalog(StacCommon):
|
|
|
890
892
|
self.data = {}
|
|
891
893
|
|
|
892
894
|
self.shp_location_config = eodag_api.locations_config
|
|
893
|
-
self.search_args:
|
|
894
|
-
self.children:
|
|
895
|
+
self.search_args: dict[str, Any] = {}
|
|
896
|
+
self.children: list[dict[str, Any]] = []
|
|
895
897
|
|
|
896
898
|
self.catalog_config = deepcopy(stac_config["catalog"])
|
|
897
899
|
|
|
@@ -907,7 +909,7 @@ class StacCatalog(StacCommon):
|
|
|
907
909
|
# build catalog
|
|
908
910
|
self.__build_stac_catalog(collection)
|
|
909
911
|
|
|
910
|
-
def __update_data_from_catalog_config(self, catalog_config:
|
|
912
|
+
def __update_data_from_catalog_config(self, catalog_config: dict[str, Any]) -> bool:
|
|
911
913
|
"""Updates configuration and data using given input catalog config
|
|
912
914
|
|
|
913
915
|
:param catalog_config: Catalog config, from yml stac_config[catalogs]
|
|
@@ -959,7 +961,7 @@ class StacCatalog(StacCommon):
|
|
|
959
961
|
|
|
960
962
|
def set_stac_product_type_by_id(
|
|
961
963
|
self, product_type: str, **_: Any
|
|
962
|
-
) ->
|
|
964
|
+
) -> dict[str, Any]:
|
|
963
965
|
"""Updates catalog with given product_type
|
|
964
966
|
|
|
965
967
|
:param product_type: Product type
|
|
@@ -991,7 +993,7 @@ class StacCatalog(StacCommon):
|
|
|
991
993
|
format_args["catalog"] = defaultdict(str, **self.data)
|
|
992
994
|
format_args["collection"] = collections[0]
|
|
993
995
|
try:
|
|
994
|
-
parsed_dict:
|
|
996
|
+
parsed_dict: dict[str, Any] = format_dict_items(cat_model, **format_args)
|
|
995
997
|
except Exception:
|
|
996
998
|
logger.error("Could not format product_type catalog")
|
|
997
999
|
raise
|
|
@@ -15,7 +15,7 @@
|
|
|
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
|
-
from typing import Any,
|
|
18
|
+
from typing import Any, Optional
|
|
19
19
|
|
|
20
20
|
from pydantic import (
|
|
21
21
|
BaseModel,
|
|
@@ -39,6 +39,6 @@ class CollectionsSearchRequest(BaseModel):
|
|
|
39
39
|
constellation: Optional[str] = Field(default=None)
|
|
40
40
|
|
|
41
41
|
@model_serializer(mode="wrap")
|
|
42
|
-
def _serialize(self, handler: SerializerFunctionWrapHandler) ->
|
|
43
|
-
dumped:
|
|
42
|
+
def _serialize(self, handler: SerializerFunctionWrapHandler) -> dict[str, Any]:
|
|
43
|
+
dumped: dict[str, Any] = handler(self)
|
|
44
44
|
return {EODAGSearch.to_eodag(k): v for k, v in dumped.items()}
|
eodag/rest/types/eodag_search.py
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
# limitations under the License.
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
from typing import TYPE_CHECKING, Any,
|
|
20
|
+
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
21
21
|
|
|
22
22
|
from pydantic import (
|
|
23
23
|
AliasChoices,
|
|
@@ -52,7 +52,7 @@ if TYPE_CHECKING:
|
|
|
52
52
|
from typing_extensions import Self
|
|
53
53
|
|
|
54
54
|
Geometry = Union[
|
|
55
|
-
|
|
55
|
+
dict[str, Any],
|
|
56
56
|
Point,
|
|
57
57
|
MultiPoint,
|
|
58
58
|
LineString,
|
|
@@ -73,8 +73,8 @@ class EODAGSearch(BaseModel):
|
|
|
73
73
|
|
|
74
74
|
productType: Optional[str] = Field(None, alias="collections", validate_default=True)
|
|
75
75
|
provider: Optional[str] = Field(None)
|
|
76
|
-
ids: Optional[
|
|
77
|
-
id: Optional[
|
|
76
|
+
ids: Optional[list[str]] = Field(None)
|
|
77
|
+
id: Optional[list[str]] = Field(
|
|
78
78
|
None, alias="ids"
|
|
79
79
|
) # TODO: remove when updating queryables
|
|
80
80
|
geom: Optional[Geometry] = Field(None, alias="geometry")
|
|
@@ -101,7 +101,7 @@ class EODAGSearch(BaseModel):
|
|
|
101
101
|
orbitNumber: Optional[int] = Field(None, alias="sat:absolute_orbit")
|
|
102
102
|
# TODO: colision in property name. Need to handle "sar:product_type"
|
|
103
103
|
sensorMode: Optional[str] = Field(None, alias="sar:instrument_mode")
|
|
104
|
-
polarizationChannels: Optional[
|
|
104
|
+
polarizationChannels: Optional[list[str]] = Field(None, alias="sar:polarizations")
|
|
105
105
|
dopplerFrequency: Optional[str] = Field(None, alias="sar:frequency_band")
|
|
106
106
|
doi: Optional[str] = Field(None, alias="sci:doi")
|
|
107
107
|
illuminationElevationAngle: Optional[float] = Field(
|
|
@@ -110,10 +110,10 @@ class EODAGSearch(BaseModel):
|
|
|
110
110
|
illuminationAzimuthAngle: Optional[float] = Field(None, alias="view:sun_azimuth")
|
|
111
111
|
page: Optional[int] = Field(1)
|
|
112
112
|
items_per_page: int = Field(DEFAULT_ITEMS_PER_PAGE, alias="limit")
|
|
113
|
-
sort_by: Optional[
|
|
113
|
+
sort_by: Optional[list[tuple[str, str]]] = Field(None, alias="sortby")
|
|
114
114
|
raise_errors: bool = False
|
|
115
115
|
|
|
116
|
-
_to_eodag_map:
|
|
116
|
+
_to_eodag_map: dict[str, str]
|
|
117
117
|
|
|
118
118
|
@model_validator(mode="after")
|
|
119
119
|
def remove_timeFromAscendingNode(self) -> Self: # pylint: disable=invalid-name
|
|
@@ -129,7 +129,7 @@ class EODAGSearch(BaseModel):
|
|
|
129
129
|
if not self.__pydantic_extra__:
|
|
130
130
|
return self
|
|
131
131
|
|
|
132
|
-
keys_to_update:
|
|
132
|
+
keys_to_update: dict[str, str] = {}
|
|
133
133
|
for key in self.__pydantic_extra__.keys():
|
|
134
134
|
if key.startswith("unk:"):
|
|
135
135
|
keys_to_update[key] = key[len("unk:") :]
|
|
@@ -145,7 +145,7 @@ class EODAGSearch(BaseModel):
|
|
|
145
145
|
|
|
146
146
|
@model_validator(mode="before")
|
|
147
147
|
@classmethod
|
|
148
|
-
def remove_keys(cls, values:
|
|
148
|
+
def remove_keys(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
149
149
|
"""Remove 'datetime', 'crunch', 'intersects', and 'bbox' keys"""
|
|
150
150
|
for key in ["datetime", "crunch", "intersects", "bbox", "filter_lang"]:
|
|
151
151
|
values.pop(key, None)
|
|
@@ -154,8 +154,8 @@ class EODAGSearch(BaseModel):
|
|
|
154
154
|
@model_validator(mode="before")
|
|
155
155
|
@classmethod
|
|
156
156
|
def parse_collections(
|
|
157
|
-
cls, values:
|
|
158
|
-
) ->
|
|
157
|
+
cls, values: dict[str, Any], info: ValidationInfo
|
|
158
|
+
) -> dict[str, Any]:
|
|
159
159
|
"""convert collections to productType"""
|
|
160
160
|
|
|
161
161
|
if collections := values.pop("collections", None):
|
|
@@ -172,7 +172,7 @@ class EODAGSearch(BaseModel):
|
|
|
172
172
|
|
|
173
173
|
@model_validator(mode="before")
|
|
174
174
|
@classmethod
|
|
175
|
-
def parse_query(cls, values:
|
|
175
|
+
def parse_query(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
176
176
|
"""
|
|
177
177
|
Convert a STAC query parameter filter with the "eq" operator to a dict.
|
|
178
178
|
"""
|
|
@@ -190,9 +190,9 @@ class EODAGSearch(BaseModel):
|
|
|
190
190
|
if not query:
|
|
191
191
|
return values
|
|
192
192
|
|
|
193
|
-
query_props:
|
|
194
|
-
errors:
|
|
195
|
-
for property_name, conditions in cast(
|
|
193
|
+
query_props: dict[str, Any] = {}
|
|
194
|
+
errors: list[InitErrorDetails] = []
|
|
195
|
+
for property_name, conditions in cast(dict[str, Any], query).items():
|
|
196
196
|
# Remove the prefix "properties." if present
|
|
197
197
|
prop = property_name.replace("properties.", "", 1)
|
|
198
198
|
|
|
@@ -205,7 +205,7 @@ class EODAGSearch(BaseModel):
|
|
|
205
205
|
continue
|
|
206
206
|
|
|
207
207
|
# Retrieve the operator and its value
|
|
208
|
-
operator, value = next(iter(cast(
|
|
208
|
+
operator, value = next(iter(cast(dict[str, Any], conditions).items()))
|
|
209
209
|
|
|
210
210
|
# Validate the operator
|
|
211
211
|
# only eq, in and lte are allowed
|
|
@@ -239,7 +239,7 @@ class EODAGSearch(BaseModel):
|
|
|
239
239
|
|
|
240
240
|
@model_validator(mode="before")
|
|
241
241
|
@classmethod
|
|
242
|
-
def parse_cql(cls, values:
|
|
242
|
+
def parse_cql(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
243
243
|
"""
|
|
244
244
|
Process cql2 filter
|
|
245
245
|
"""
|
|
@@ -256,7 +256,7 @@ class EODAGSearch(BaseModel):
|
|
|
256
256
|
if not filter_:
|
|
257
257
|
return values
|
|
258
258
|
|
|
259
|
-
errors:
|
|
259
|
+
errors: list[InitErrorDetails] = []
|
|
260
260
|
try:
|
|
261
261
|
parsing_result = EodagEvaluator().evaluate(parse_json(filter_)) # type: ignore
|
|
262
262
|
except (ValueError, NotImplementedError) as e:
|
|
@@ -271,7 +271,7 @@ class EODAGSearch(BaseModel):
|
|
|
271
271
|
title=cls.__name__, line_errors=errors
|
|
272
272
|
)
|
|
273
273
|
|
|
274
|
-
cql_args:
|
|
274
|
+
cql_args: dict[str, Any] = cast(dict[str, Any], parsing_result)
|
|
275
275
|
|
|
276
276
|
invalid_keys = {
|
|
277
277
|
"collections": 'Use "collection" instead of "collections"',
|
|
@@ -298,7 +298,7 @@ class EODAGSearch(BaseModel):
|
|
|
298
298
|
|
|
299
299
|
@field_validator("instrument", mode="before")
|
|
300
300
|
@classmethod
|
|
301
|
-
def join_instruments(cls, v: Union[str,
|
|
301
|
+
def join_instruments(cls, v: Union[str, list[str]]) -> str:
|
|
302
302
|
"""convert instruments to instrument"""
|
|
303
303
|
if isinstance(v, list):
|
|
304
304
|
return ",".join(v)
|
|
@@ -308,8 +308,8 @@ class EODAGSearch(BaseModel):
|
|
|
308
308
|
@classmethod
|
|
309
309
|
def parse_sortby(
|
|
310
310
|
cls,
|
|
311
|
-
sortby_post_params:
|
|
312
|
-
) ->
|
|
311
|
+
sortby_post_params: list[dict[str, str]],
|
|
312
|
+
) -> list[tuple[str, str]]:
|
|
313
313
|
"""
|
|
314
314
|
Convert STAC POST sortby to EODAG sort_by
|
|
315
315
|
"""
|
|
@@ -363,7 +363,7 @@ class EODAGSearch(BaseModel):
|
|
|
363
363
|
def to_stac(
|
|
364
364
|
cls,
|
|
365
365
|
field_name: str,
|
|
366
|
-
stac_item_properties: Optional[
|
|
366
|
+
stac_item_properties: Optional[list[str]] = None,
|
|
367
367
|
provider: Optional[str] = None,
|
|
368
368
|
) -> str:
|
|
369
369
|
"""Get the alias of a field in a Pydantic model"""
|
eodag/rest/types/queryables.py
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
# limitations under the License.
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
from typing import TYPE_CHECKING, Annotated, Any, ClassVar,
|
|
20
|
+
from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Optional, Union
|
|
21
21
|
|
|
22
22
|
from pydantic import (
|
|
23
23
|
BaseModel,
|
|
@@ -46,8 +46,8 @@ class QueryablesGetParams(BaseModel):
|
|
|
46
46
|
model_config = ConfigDict(extra="allow", frozen=True)
|
|
47
47
|
|
|
48
48
|
@model_serializer(mode="wrap")
|
|
49
|
-
def _serialize(self, handler: SerializerFunctionWrapHandler) ->
|
|
50
|
-
dumped:
|
|
49
|
+
def _serialize(self, handler: SerializerFunctionWrapHandler) -> dict[str, Any]:
|
|
50
|
+
dumped: dict[str, Any] = handler(self)
|
|
51
51
|
return {EODAGSearch.to_eodag(k): v for k, v in dumped.items()}
|
|
52
52
|
|
|
53
53
|
# use [prop-decorator] mypy error code when mypy==1.12 is released
|
|
@@ -77,12 +77,12 @@ class StacQueryableProperty(BaseModel):
|
|
|
77
77
|
|
|
78
78
|
description: str
|
|
79
79
|
ref: Optional[str] = Field(default=None, serialization_alias="$ref")
|
|
80
|
-
type: Optional[Union[str,
|
|
81
|
-
enum: Optional[
|
|
80
|
+
type: Optional[Union[str, list[str]]] = None
|
|
81
|
+
enum: Optional[list[Any]] = None
|
|
82
82
|
value: Optional[Any] = None
|
|
83
|
-
min: Optional[Union[int,
|
|
84
|
-
max: Optional[Union[int,
|
|
85
|
-
oneOf: Optional[
|
|
83
|
+
min: Optional[Union[int, list[Union[int, None]]]] = None
|
|
84
|
+
max: Optional[Union[int, list[Union[int, None]]]] = None
|
|
85
|
+
oneOf: Optional[list[Any]] = None
|
|
86
86
|
items: Optional[Any] = None
|
|
87
87
|
|
|
88
88
|
@classmethod
|
|
@@ -104,7 +104,7 @@ class StacQueryableProperty(BaseModel):
|
|
|
104
104
|
_: SerializationInfo,
|
|
105
105
|
):
|
|
106
106
|
"""Remove none value property fields during serialization"""
|
|
107
|
-
props:
|
|
107
|
+
props: dict[str, Any] = handler(self)
|
|
108
108
|
return {k: v for k, v in props.items() if v is not None}
|
|
109
109
|
|
|
110
110
|
|
|
@@ -130,13 +130,13 @@ class StacQueryables(BaseModel):
|
|
|
130
130
|
description: str = Field(
|
|
131
131
|
default="Queryable names for the EODAG STAC API Item Search filter."
|
|
132
132
|
)
|
|
133
|
-
default_properties: ClassVar[
|
|
133
|
+
default_properties: ClassVar[dict[str, StacQueryableProperty]] = {
|
|
134
134
|
"collection": StacQueryableProperty(
|
|
135
135
|
description="Collection",
|
|
136
136
|
ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/collection",
|
|
137
137
|
)
|
|
138
138
|
}
|
|
139
|
-
possible_properties: ClassVar[
|
|
139
|
+
possible_properties: ClassVar[dict[str, StacQueryableProperty]] = {
|
|
140
140
|
"geometry": StacQueryableProperty(
|
|
141
141
|
description="Geometry",
|
|
142
142
|
ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/geometry",
|
|
@@ -152,8 +152,8 @@ class StacQueryables(BaseModel):
|
|
|
152
152
|
items={"type": "number"},
|
|
153
153
|
),
|
|
154
154
|
}
|
|
155
|
-
properties:
|
|
156
|
-
required: Optional[
|
|
155
|
+
properties: dict[str, Any] = Field()
|
|
156
|
+
required: Optional[list[str]] = Field(None)
|
|
157
157
|
additional_properties: bool = Field(
|
|
158
158
|
default=True, serialization_alias="additionalProperties"
|
|
159
159
|
)
|
eodag/rest/types/stac_search.py
CHANGED
|
@@ -19,17 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
|
-
from typing import
|
|
23
|
-
TYPE_CHECKING,
|
|
24
|
-
Annotated,
|
|
25
|
-
Any,
|
|
26
|
-
Dict,
|
|
27
|
-
List,
|
|
28
|
-
Literal,
|
|
29
|
-
Optional,
|
|
30
|
-
Tuple,
|
|
31
|
-
Union,
|
|
32
|
-
)
|
|
22
|
+
from typing import TYPE_CHECKING, Annotated, Any, Literal, Optional, Union
|
|
33
23
|
|
|
34
24
|
import geojson
|
|
35
25
|
from pydantic import (
|
|
@@ -63,8 +53,8 @@ if TYPE_CHECKING:
|
|
|
63
53
|
NumType = Union[float, int]
|
|
64
54
|
|
|
65
55
|
BBox = Union[
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
tuple[NumType, NumType, NumType, NumType],
|
|
57
|
+
tuple[NumType, NumType, NumType, NumType, NumType, NumType],
|
|
68
58
|
]
|
|
69
59
|
|
|
70
60
|
Geometry = Union[
|
|
@@ -106,8 +96,8 @@ class SearchPostRequest(BaseModel):
|
|
|
106
96
|
model_config = ConfigDict(populate_by_name=True, arbitrary_types_allowed=True)
|
|
107
97
|
|
|
108
98
|
provider: Optional[str] = None
|
|
109
|
-
collections: Optional[
|
|
110
|
-
ids: Optional[
|
|
99
|
+
collections: Optional[list[str]] = None
|
|
100
|
+
ids: Optional[list[str]] = None
|
|
111
101
|
bbox: Optional[BBox] = None
|
|
112
102
|
intersects: Optional[Geometry] = None
|
|
113
103
|
datetime: Optional[str] = None
|
|
@@ -117,21 +107,21 @@ class SearchPostRequest(BaseModel):
|
|
|
117
107
|
page: Optional[PositiveInt] = Field( # type: ignore
|
|
118
108
|
default=None, description="Page number, must be a positive integer."
|
|
119
109
|
)
|
|
120
|
-
query: Optional[
|
|
121
|
-
filter: Optional[
|
|
110
|
+
query: Optional[dict[str, Any]] = None
|
|
111
|
+
filter: Optional[dict[str, Any]] = None
|
|
122
112
|
filter_lang: Optional[str] = Field(
|
|
123
113
|
default=None,
|
|
124
114
|
alias="filter-lang",
|
|
125
115
|
description="The language used for filtering.",
|
|
126
116
|
validate_default=True,
|
|
127
117
|
)
|
|
128
|
-
sortby: Optional[
|
|
118
|
+
sortby: Optional[list[SortBy]] = None
|
|
129
119
|
crunch: Optional[str] = None
|
|
130
120
|
|
|
131
121
|
@field_serializer("intersects")
|
|
132
122
|
def serialize_intersects(
|
|
133
123
|
self, intersects: Optional[Geometry]
|
|
134
|
-
) -> Optional[
|
|
124
|
+
) -> Optional[dict[str, Any]]:
|
|
135
125
|
"""Serialize intersects from shapely to a proper dict"""
|
|
136
126
|
if intersects:
|
|
137
127
|
return geojson.loads(geojson.dumps(intersects)) # type: ignore
|
|
@@ -150,7 +140,7 @@ class SearchPostRequest(BaseModel):
|
|
|
150
140
|
|
|
151
141
|
@model_validator(mode="before")
|
|
152
142
|
@classmethod
|
|
153
|
-
def only_one_spatial(cls, values:
|
|
143
|
+
def only_one_spatial(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
154
144
|
"""Check bbox and intersects are not both supplied."""
|
|
155
145
|
if "intersects" in values and "bbox" in values:
|
|
156
146
|
raise ValueError("intersects and bbox parameters are mutually exclusive")
|
|
@@ -170,7 +160,7 @@ class SearchPostRequest(BaseModel):
|
|
|
170
160
|
|
|
171
161
|
@field_validator("ids", "collections", mode="before")
|
|
172
162
|
@classmethod
|
|
173
|
-
def str_to_str_list(cls, v: Union[str,
|
|
163
|
+
def str_to_str_list(cls, v: Union[str, list[str]]) -> list[str]:
|
|
174
164
|
"""Convert ids and collections strings to list of strings"""
|
|
175
165
|
if isinstance(v, str):
|
|
176
166
|
return [i.strip() for i in v.split(",")]
|
|
@@ -178,7 +168,7 @@ class SearchPostRequest(BaseModel):
|
|
|
178
168
|
|
|
179
169
|
@field_validator("intersects", mode="before")
|
|
180
170
|
@classmethod
|
|
181
|
-
def validate_intersects(cls, v: Union[
|
|
171
|
+
def validate_intersects(cls, v: Union[dict[str, Any], Geometry]) -> Geometry:
|
|
182
172
|
"""Verify format of intersects"""
|
|
183
173
|
if isinstance(v, BaseGeometry):
|
|
184
174
|
return v
|
|
@@ -224,7 +214,7 @@ class SearchPostRequest(BaseModel):
|
|
|
224
214
|
# Single date is interpreted as end date
|
|
225
215
|
values = ["..", v]
|
|
226
216
|
|
|
227
|
-
dates:
|
|
217
|
+
dates: list[str] = []
|
|
228
218
|
for value in values:
|
|
229
219
|
if value == ".." or value == "":
|
|
230
220
|
dates.append("..")
|
|
@@ -267,13 +257,13 @@ class SearchPostRequest(BaseModel):
|
|
|
267
257
|
|
|
268
258
|
def sortby2list(
|
|
269
259
|
v: Optional[str],
|
|
270
|
-
) -> Optional[
|
|
260
|
+
) -> Optional[list[SortBy]]:
|
|
271
261
|
"""
|
|
272
262
|
Convert sortby filter parameter GET syntax to POST syntax
|
|
273
263
|
"""
|
|
274
264
|
if not v:
|
|
275
265
|
return None
|
|
276
|
-
sortby:
|
|
266
|
+
sortby: list[SortBy] = []
|
|
277
267
|
for sortby_param in v.split(","):
|
|
278
268
|
sortby_param = sortby_param.strip()
|
|
279
269
|
direction: Direction = "desc" if sortby_param.startswith("-") else "asc"
|