eodag 3.0.1__py3-none-any.whl → 3.1.0__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 +174 -138
- eodag/api/product/_assets.py +44 -15
- eodag/api/product/_product.py +58 -47
- 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 +117 -90
- eodag/api/search_result.py +13 -23
- eodag/cli.py +26 -5
- eodag/config.py +86 -92
- eodag/plugins/apis/base.py +1 -1
- eodag/plugins/apis/ecmwf.py +42 -22
- eodag/plugins/apis/usgs.py +17 -16
- 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 +22 -16
- eodag/plugins/authentication/sas_auth.py +4 -4
- eodag/plugins/authentication/token.py +41 -10
- 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 +6 -7
- eodag/plugins/download/aws.py +146 -87
- eodag/plugins/download/base.py +38 -56
- eodag/plugins/download/creodias_s3.py +29 -0
- eodag/plugins/download/http.py +173 -183
- eodag/plugins/download/s3rest.py +10 -11
- eodag/plugins/manager.py +10 -20
- eodag/plugins/search/__init__.py +6 -5
- eodag/plugins/search/base.py +90 -46
- eodag/plugins/search/build_search_result.py +1048 -361
- eodag/plugins/search/cop_marine.py +22 -12
- eodag/plugins/search/creodias_s3.py +9 -73
- eodag/plugins/search/csw.py +11 -11
- eodag/plugins/search/data_request_search.py +19 -18
- eodag/plugins/search/qssearch.py +99 -258
- eodag/plugins/search/stac_list_assets.py +85 -0
- eodag/plugins/search/static_stac_search.py +4 -4
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +1134 -325
- eodag/resources/providers.yml +906 -2006
- eodag/resources/stac_api.yml +2 -2
- eodag/resources/user_conf_template.yml +10 -9
- eodag/rest/cache.py +2 -2
- eodag/rest/config.py +3 -3
- eodag/rest/core.py +112 -82
- eodag/rest/errors.py +5 -5
- eodag/rest/server.py +33 -14
- eodag/rest/stac.py +41 -38
- eodag/rest/types/collections_search.py +3 -3
- eodag/rest/types/eodag_search.py +29 -23
- eodag/rest/types/queryables.py +42 -31
- eodag/rest/types/stac_search.py +15 -25
- eodag/rest/utils/__init__.py +14 -21
- eodag/rest/utils/cql_evaluate.py +6 -6
- eodag/rest/utils/rfc3339.py +2 -2
- eodag/types/__init__.py +141 -32
- eodag/types/bbox.py +2 -2
- eodag/types/download_args.py +3 -3
- eodag/types/queryables.py +183 -72
- eodag/types/search_args.py +4 -4
- eodag/types/whoosh.py +127 -3
- eodag/utils/__init__.py +153 -51
- eodag/utils/exceptions.py +28 -21
- eodag/utils/import_system.py +2 -2
- eodag/utils/repr.py +65 -6
- eodag/utils/requests.py +13 -13
- eodag/utils/rest.py +2 -2
- eodag/utils/s3.py +231 -0
- eodag/utils/stac_reader.py +10 -10
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/METADATA +77 -76
- eodag-3.1.0.dist-info/RECORD +113 -0
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +4 -2
- eodag/utils/constraints.py +0 -244
- eodag-3.0.1.dist-info/RECORD +0 -109
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/LICENSE +0 -0
- {eodag-3.0.1.dist-info → eodag-3.1.0.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",
|
|
@@ -95,6 +97,7 @@ IGNORED_ITEM_PROPERTIES = [
|
|
|
95
97
|
"qs",
|
|
96
98
|
"defaultGeometry",
|
|
97
99
|
"_date",
|
|
100
|
+
"productType",
|
|
98
101
|
]
|
|
99
102
|
|
|
100
103
|
|
|
@@ -118,7 +121,7 @@ class StacCommon:
|
|
|
118
121
|
def __init__(
|
|
119
122
|
self,
|
|
120
123
|
url: str,
|
|
121
|
-
stac_config:
|
|
124
|
+
stac_config: dict[str, Any],
|
|
122
125
|
provider: Optional[str],
|
|
123
126
|
eodag_api: EODataAccessGateway,
|
|
124
127
|
root: str = "/",
|
|
@@ -129,9 +132,9 @@ class StacCommon:
|
|
|
129
132
|
self.eodag_api = eodag_api
|
|
130
133
|
self.root = root.rstrip("/") if len(root) > 1 else root
|
|
131
134
|
|
|
132
|
-
self.data:
|
|
135
|
+
self.data: dict[str, Any] = {}
|
|
133
136
|
|
|
134
|
-
def update_data(self, data:
|
|
137
|
+
def update_data(self, data: dict[str, Any]) -> None:
|
|
135
138
|
"""Updates data using given input STAC dict data
|
|
136
139
|
|
|
137
140
|
:param data: Catalog data (parsed STAC dict)
|
|
@@ -165,8 +168,8 @@ class StacCommon:
|
|
|
165
168
|
|
|
166
169
|
@staticmethod
|
|
167
170
|
def get_stac_extension(
|
|
168
|
-
url: str, stac_config:
|
|
169
|
-
) ->
|
|
171
|
+
url: str, stac_config: dict[str, Any], extension: str, **kwargs: Any
|
|
172
|
+
) -> dict[str, str]:
|
|
170
173
|
"""Parse STAC extension from config and return as dict
|
|
171
174
|
|
|
172
175
|
:param url: Requested URL
|
|
@@ -185,7 +188,7 @@ class StacCommon:
|
|
|
185
188
|
}
|
|
186
189
|
return format_dict_items(extension_model, **format_args)
|
|
187
190
|
|
|
188
|
-
def get_provider_dict(self, provider: str) ->
|
|
191
|
+
def get_provider_dict(self, provider: str) -> dict[str, Any]:
|
|
189
192
|
"""Generate STAC provider dict"""
|
|
190
193
|
provider_config = next(
|
|
191
194
|
p
|
|
@@ -214,7 +217,7 @@ class StacItem(StacCommon):
|
|
|
214
217
|
def __init__(
|
|
215
218
|
self,
|
|
216
219
|
url: str,
|
|
217
|
-
stac_config:
|
|
220
|
+
stac_config: dict[str, Any],
|
|
218
221
|
provider: Optional[str],
|
|
219
222
|
eodag_api: EODataAccessGateway,
|
|
220
223
|
root: str = "/",
|
|
@@ -228,8 +231,8 @@ class StacItem(StacCommon):
|
|
|
228
231
|
)
|
|
229
232
|
|
|
230
233
|
def __get_item_list(
|
|
231
|
-
self, search_results: SearchResult, catalog:
|
|
232
|
-
) ->
|
|
234
|
+
self, search_results: SearchResult, catalog: dict[str, Any]
|
|
235
|
+
) -> list[dict[str, Any]]:
|
|
233
236
|
"""Build STAC items list from EODAG search results
|
|
234
237
|
|
|
235
238
|
:param search_results: EODAG search results
|
|
@@ -244,7 +247,7 @@ class StacItem(StacCommon):
|
|
|
244
247
|
)
|
|
245
248
|
|
|
246
249
|
# check if some items need to be converted
|
|
247
|
-
need_conversion:
|
|
250
|
+
need_conversion: dict[str, Any] = {}
|
|
248
251
|
for k, v in item_model["properties"].items():
|
|
249
252
|
if isinstance(v, str):
|
|
250
253
|
conversion, item_model["properties"][k] = get_metadata_path(
|
|
@@ -264,11 +267,11 @@ class StacItem(StacCommon):
|
|
|
264
267
|
]
|
|
265
268
|
ignored_props = COLLECTION_PROPERTIES + item_props + IGNORED_ITEM_PROPERTIES
|
|
266
269
|
|
|
267
|
-
item_list:
|
|
270
|
+
item_list: list[dict[str, Any]] = []
|
|
268
271
|
for product in search_results:
|
|
269
272
|
product_dict = deepcopy(product.__dict__)
|
|
270
273
|
|
|
271
|
-
product_item:
|
|
274
|
+
product_item: dict[str, Any] = jsonpath_parse_dict_items(
|
|
272
275
|
item_model,
|
|
273
276
|
{
|
|
274
277
|
"product": product_dict,
|
|
@@ -370,10 +373,10 @@ class StacItem(StacCommon):
|
|
|
370
373
|
product: EOProduct,
|
|
371
374
|
downloadlink_href: str,
|
|
372
375
|
without_arg_url: str,
|
|
373
|
-
query_dict: Optional[
|
|
376
|
+
query_dict: Optional[dict[str, Any]] = None,
|
|
374
377
|
_dc_qs: Optional[str] = None,
|
|
375
|
-
) ->
|
|
376
|
-
assets:
|
|
378
|
+
) -> dict[str, Any]:
|
|
379
|
+
assets: dict[str, Any] = {}
|
|
377
380
|
settings = Settings.from_environment()
|
|
378
381
|
|
|
379
382
|
if _dc_qs:
|
|
@@ -452,9 +455,9 @@ class StacItem(StacCommon):
|
|
|
452
455
|
self,
|
|
453
456
|
search_results: SearchResult,
|
|
454
457
|
total: int,
|
|
455
|
-
catalog:
|
|
456
|
-
next_link: Optional[
|
|
457
|
-
) ->
|
|
458
|
+
catalog: dict[str, Any],
|
|
459
|
+
next_link: Optional[dict[str, Any]],
|
|
460
|
+
) -> dict[str, Any]:
|
|
458
461
|
"""Build STAC items from EODAG search results
|
|
459
462
|
|
|
460
463
|
:param search_results: EODAG search results
|
|
@@ -503,8 +506,8 @@ class StacItem(StacCommon):
|
|
|
503
506
|
return self.data
|
|
504
507
|
|
|
505
508
|
def __filter_item_model_properties(
|
|
506
|
-
self, item_model:
|
|
507
|
-
) ->
|
|
509
|
+
self, item_model: dict[str, Any], product_type: str
|
|
510
|
+
) -> dict[str, Any]:
|
|
508
511
|
"""Filter item model depending on product type metadata and its extensions.
|
|
509
512
|
Removes not needed parameters, and adds supplementary ones as
|
|
510
513
|
part of oseo extension.
|
|
@@ -568,13 +571,13 @@ class StacItem(StacCommon):
|
|
|
568
571
|
|
|
569
572
|
return result_item_model
|
|
570
573
|
|
|
571
|
-
def __filter_item_properties_values(self, item:
|
|
574
|
+
def __filter_item_properties_values(self, item: dict[str, Any]) -> dict[str, Any]:
|
|
572
575
|
"""Removes empty properties, unused extensions, and add missing extensions
|
|
573
576
|
|
|
574
577
|
:param item: STAC item data
|
|
575
578
|
:returns: Filtered item model
|
|
576
579
|
"""
|
|
577
|
-
all_extensions_dict:
|
|
580
|
+
all_extensions_dict: dict[str, str] = deepcopy(
|
|
578
581
|
self.stac_config["stac_extensions"]
|
|
579
582
|
)
|
|
580
583
|
# parse f-strings with root
|
|
@@ -599,7 +602,7 @@ class StacItem(StacCommon):
|
|
|
599
602
|
|
|
600
603
|
return item
|
|
601
604
|
|
|
602
|
-
def get_stac_item_from_product(self, product: EOProduct) ->
|
|
605
|
+
def get_stac_item_from_product(self, product: EOProduct) -> dict[str, Any]:
|
|
603
606
|
"""Build STAC item from EODAG product
|
|
604
607
|
|
|
605
608
|
:param product: EODAG product
|
|
@@ -659,7 +662,7 @@ class StacCollection(StacCommon):
|
|
|
659
662
|
"""
|
|
660
663
|
|
|
661
664
|
# External STAC collections
|
|
662
|
-
ext_stac_collections:
|
|
665
|
+
ext_stac_collections: dict[str, dict[str, Any]] = dict()
|
|
663
666
|
|
|
664
667
|
@classmethod
|
|
665
668
|
def fetch_external_stac_collections(cls, eodag_api: EODataAccessGateway) -> None:
|
|
@@ -688,7 +691,7 @@ class StacCollection(StacCommon):
|
|
|
688
691
|
def __init__(
|
|
689
692
|
self,
|
|
690
693
|
url: str,
|
|
691
|
-
stac_config:
|
|
694
|
+
stac_config: dict[str, Any],
|
|
692
695
|
provider: Optional[str],
|
|
693
696
|
eodag_api: EODataAccessGateway,
|
|
694
697
|
root: str = "/",
|
|
@@ -701,7 +704,7 @@ class StacCollection(StacCommon):
|
|
|
701
704
|
root=root,
|
|
702
705
|
)
|
|
703
706
|
|
|
704
|
-
def __list_product_type_providers(self, product_type:
|
|
707
|
+
def __list_product_type_providers(self, product_type: dict[str, Any]) -> list[str]:
|
|
705
708
|
"""Retrieve a list of providers for a given product type.
|
|
706
709
|
|
|
707
710
|
:param product_type: Dictionary containing information about the product type.
|
|
@@ -718,8 +721,8 @@ class StacCollection(StacCommon):
|
|
|
718
721
|
]
|
|
719
722
|
|
|
720
723
|
def __generate_stac_collection(
|
|
721
|
-
self, collection_model: Any, product_type:
|
|
722
|
-
) ->
|
|
724
|
+
self, collection_model: Any, product_type: dict[str, Any]
|
|
725
|
+
) -> dict[str, Any]:
|
|
723
726
|
"""Generate a STAC collection dictionary for a given product type.
|
|
724
727
|
|
|
725
728
|
:param collection_model: The base model for the STAC collection.
|
|
@@ -728,7 +731,7 @@ class StacCollection(StacCommon):
|
|
|
728
731
|
"""
|
|
729
732
|
providers = self.__list_product_type_providers(product_type)
|
|
730
733
|
|
|
731
|
-
providers_dict:
|
|
734
|
+
providers_dict: dict[str, dict[str, Any]] = {}
|
|
732
735
|
for provider in providers:
|
|
733
736
|
p_dict = self.get_provider_dict(provider)
|
|
734
737
|
providers_dict.setdefault(p_dict["name"], p_dict)
|
|
@@ -815,7 +818,7 @@ class StacCollection(StacCommon):
|
|
|
815
818
|
instrument: Optional[str] = None,
|
|
816
819
|
constellation: Optional[str] = None,
|
|
817
820
|
datetime: Optional[str] = None,
|
|
818
|
-
) ->
|
|
821
|
+
) -> list[dict[str, Any]]:
|
|
819
822
|
"""Build STAC collections list
|
|
820
823
|
|
|
821
824
|
:param filters: (optional) Additional filters for collections search
|
|
@@ -850,7 +853,7 @@ class StacCollection(StacCommon):
|
|
|
850
853
|
product_types = all_pt
|
|
851
854
|
|
|
852
855
|
# list product types with all metadata using guessed ids
|
|
853
|
-
collection_list:
|
|
856
|
+
collection_list: list[dict[str, Any]] = []
|
|
854
857
|
for product_type in product_types:
|
|
855
858
|
stac_collection = self.__generate_stac_collection(
|
|
856
859
|
collection_model, product_type
|
|
@@ -874,7 +877,7 @@ class StacCatalog(StacCommon):
|
|
|
874
877
|
def __init__(
|
|
875
878
|
self,
|
|
876
879
|
url: str,
|
|
877
|
-
stac_config:
|
|
880
|
+
stac_config: dict[str, Any],
|
|
878
881
|
provider: Optional[str],
|
|
879
882
|
eodag_api: EODataAccessGateway,
|
|
880
883
|
root: str = "/",
|
|
@@ -890,8 +893,8 @@ class StacCatalog(StacCommon):
|
|
|
890
893
|
self.data = {}
|
|
891
894
|
|
|
892
895
|
self.shp_location_config = eodag_api.locations_config
|
|
893
|
-
self.search_args:
|
|
894
|
-
self.children:
|
|
896
|
+
self.search_args: dict[str, Any] = {}
|
|
897
|
+
self.children: list[dict[str, Any]] = []
|
|
895
898
|
|
|
896
899
|
self.catalog_config = deepcopy(stac_config["catalog"])
|
|
897
900
|
|
|
@@ -907,7 +910,7 @@ class StacCatalog(StacCommon):
|
|
|
907
910
|
# build catalog
|
|
908
911
|
self.__build_stac_catalog(collection)
|
|
909
912
|
|
|
910
|
-
def __update_data_from_catalog_config(self, catalog_config:
|
|
913
|
+
def __update_data_from_catalog_config(self, catalog_config: dict[str, Any]) -> bool:
|
|
911
914
|
"""Updates configuration and data using given input catalog config
|
|
912
915
|
|
|
913
916
|
:param catalog_config: Catalog config, from yml stac_config[catalogs]
|
|
@@ -959,7 +962,7 @@ class StacCatalog(StacCommon):
|
|
|
959
962
|
|
|
960
963
|
def set_stac_product_type_by_id(
|
|
961
964
|
self, product_type: str, **_: Any
|
|
962
|
-
) ->
|
|
965
|
+
) -> dict[str, Any]:
|
|
963
966
|
"""Updates catalog with given product_type
|
|
964
967
|
|
|
965
968
|
:param product_type: Product type
|
|
@@ -991,7 +994,7 @@ class StacCatalog(StacCommon):
|
|
|
991
994
|
format_args["catalog"] = defaultdict(str, **self.data)
|
|
992
995
|
format_args["collection"] = collections[0]
|
|
993
996
|
try:
|
|
994
|
-
parsed_dict:
|
|
997
|
+
parsed_dict: dict[str, Any] = format_dict_items(cat_model, **format_args)
|
|
995
998
|
except Exception:
|
|
996
999
|
logger.error("Could not format product_type catalog")
|
|
997
1000
|
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,10 +363,16 @@ 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"""
|
|
370
|
+
# quick fix. TODO: refactor of EODAGSearch.
|
|
371
|
+
if field_name in ("productType", "id", "start_datetime", "end_datetime"):
|
|
372
|
+
return field_name
|
|
373
|
+
# another quick fix to handle different names of geometry
|
|
374
|
+
if field_name == "geometry":
|
|
375
|
+
field_name = "geom"
|
|
370
376
|
field = cls.model_fields.get(field_name)
|
|
371
377
|
if field is not None and field.alias is not None:
|
|
372
378
|
return field.alias
|
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,
|
|
@@ -25,8 +25,9 @@ from pydantic import (
|
|
|
25
25
|
Field,
|
|
26
26
|
SerializationInfo,
|
|
27
27
|
SerializerFunctionWrapHandler,
|
|
28
|
-
|
|
28
|
+
field_validator,
|
|
29
29
|
model_serializer,
|
|
30
|
+
model_validator,
|
|
30
31
|
)
|
|
31
32
|
|
|
32
33
|
from eodag.rest.types.eodag_search import EODAGSearch
|
|
@@ -35,6 +36,7 @@ from eodag.types import python_field_definition_to_json
|
|
|
35
36
|
|
|
36
37
|
if TYPE_CHECKING:
|
|
37
38
|
from pydantic.fields import FieldInfo
|
|
39
|
+
from typing_extensions import Self
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
class QueryablesGetParams(BaseModel):
|
|
@@ -42,29 +44,39 @@ class QueryablesGetParams(BaseModel):
|
|
|
42
44
|
|
|
43
45
|
collection: Optional[str] = Field(default=None, serialization_alias="productType")
|
|
44
46
|
datetime: Optional[str] = Field(default=None)
|
|
47
|
+
start_datetime: Optional[str] = Field(default=None)
|
|
48
|
+
end_datetime: Optional[str] = Field(default=None)
|
|
45
49
|
|
|
46
50
|
model_config = ConfigDict(extra="allow", frozen=True)
|
|
47
51
|
|
|
48
52
|
@model_serializer(mode="wrap")
|
|
49
|
-
def _serialize(self, handler: SerializerFunctionWrapHandler) ->
|
|
50
|
-
dumped:
|
|
53
|
+
def _serialize(self, handler: SerializerFunctionWrapHandler) -> dict[str, Any]:
|
|
54
|
+
dumped: dict[str, Any] = handler(self)
|
|
51
55
|
return {EODAGSearch.to_eodag(k): v for k, v in dumped.items()}
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
start = str_to_interval(self.datetime)[0]
|
|
59
|
-
return start.strftime("%Y-%m-%dT%H:%M:%SZ") if start else None
|
|
57
|
+
@field_validator("datetime", "start_datetime", "end_datetime", mode="before")
|
|
58
|
+
def validate_datetime(cls, value: Any) -> Optional[str]:
|
|
59
|
+
"""datetime, start_datetime and end_datetime must be a string"""
|
|
60
|
+
if isinstance(value, list):
|
|
61
|
+
return value[0]
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
@
|
|
64
|
-
def
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
return value
|
|
64
|
+
|
|
65
|
+
@model_validator(mode="after")
|
|
66
|
+
def compute_datetimes(self: Self) -> Self:
|
|
67
|
+
"""Start datetime must be a string"""
|
|
68
|
+
if not self.datetime:
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
start, end = str_to_interval(self.datetime)
|
|
72
|
+
|
|
73
|
+
if not self.start_datetime and start:
|
|
74
|
+
self.start_datetime = start.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
75
|
+
|
|
76
|
+
if not self.end_datetime and end:
|
|
77
|
+
self.end_datetime = end.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
78
|
+
|
|
79
|
+
return self
|
|
68
80
|
|
|
69
81
|
|
|
70
82
|
class StacQueryableProperty(BaseModel):
|
|
@@ -77,12 +89,12 @@ class StacQueryableProperty(BaseModel):
|
|
|
77
89
|
|
|
78
90
|
description: str
|
|
79
91
|
ref: Optional[str] = Field(default=None, serialization_alias="$ref")
|
|
80
|
-
type: Optional[Union[str,
|
|
81
|
-
enum: Optional[
|
|
92
|
+
type: Optional[Union[str, list[str]]] = None
|
|
93
|
+
enum: Optional[list[Any]] = None
|
|
82
94
|
value: Optional[Any] = None
|
|
83
|
-
min: Optional[Union[int,
|
|
84
|
-
max: Optional[Union[int,
|
|
85
|
-
oneOf: Optional[
|
|
95
|
+
min: Optional[Union[int, list[Union[int, None]]]] = None
|
|
96
|
+
max: Optional[Union[int, list[Union[int, None]]]] = None
|
|
97
|
+
oneOf: Optional[list[Any]] = None
|
|
86
98
|
items: Optional[Any] = None
|
|
87
99
|
|
|
88
100
|
@classmethod
|
|
@@ -104,7 +116,7 @@ class StacQueryableProperty(BaseModel):
|
|
|
104
116
|
_: SerializationInfo,
|
|
105
117
|
):
|
|
106
118
|
"""Remove none value property fields during serialization"""
|
|
107
|
-
props:
|
|
119
|
+
props: dict[str, Any] = handler(self)
|
|
108
120
|
return {k: v for k, v in props.items() if v is not None}
|
|
109
121
|
|
|
110
122
|
|
|
@@ -130,15 +142,13 @@ class StacQueryables(BaseModel):
|
|
|
130
142
|
description: str = Field(
|
|
131
143
|
default="Queryable names for the EODAG STAC API Item Search filter."
|
|
132
144
|
)
|
|
133
|
-
default_properties: ClassVar[
|
|
134
|
-
"id": StacQueryableProperty(
|
|
135
|
-
description="ID",
|
|
136
|
-
ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/id",
|
|
137
|
-
),
|
|
145
|
+
default_properties: ClassVar[dict[str, StacQueryableProperty]] = {
|
|
138
146
|
"collection": StacQueryableProperty(
|
|
139
147
|
description="Collection",
|
|
140
148
|
ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/collection",
|
|
141
|
-
)
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
possible_properties: ClassVar[dict[str, StacQueryableProperty]] = {
|
|
142
152
|
"geometry": StacQueryableProperty(
|
|
143
153
|
description="Geometry",
|
|
144
154
|
ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/geometry",
|
|
@@ -154,7 +164,8 @@ class StacQueryables(BaseModel):
|
|
|
154
164
|
items={"type": "number"},
|
|
155
165
|
),
|
|
156
166
|
}
|
|
157
|
-
properties:
|
|
167
|
+
properties: dict[str, Any] = Field()
|
|
168
|
+
required: Optional[list[str]] = Field(None)
|
|
158
169
|
additional_properties: bool = Field(
|
|
159
170
|
default=True, serialization_alias="additionalProperties"
|
|
160
171
|
)
|