eodag 3.0.0b3__py3-none-any.whl → 3.1.0b1__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 +292 -198
- eodag/api/product/_assets.py +6 -6
- eodag/api/product/_product.py +18 -18
- eodag/api/product/metadata_mapping.py +51 -14
- eodag/api/search_result.py +29 -3
- eodag/cli.py +57 -20
- eodag/config.py +413 -117
- eodag/plugins/apis/base.py +10 -4
- eodag/plugins/apis/ecmwf.py +49 -16
- eodag/plugins/apis/usgs.py +30 -7
- eodag/plugins/authentication/aws_auth.py +14 -5
- eodag/plugins/authentication/base.py +10 -1
- eodag/plugins/authentication/generic.py +14 -3
- eodag/plugins/authentication/header.py +12 -4
- eodag/plugins/authentication/keycloak.py +41 -22
- eodag/plugins/authentication/oauth.py +11 -1
- eodag/plugins/authentication/openid_connect.py +178 -163
- eodag/plugins/authentication/qsauth.py +12 -4
- eodag/plugins/authentication/sas_auth.py +19 -2
- eodag/plugins/authentication/token.py +93 -15
- eodag/plugins/authentication/token_exchange.py +19 -19
- eodag/plugins/crunch/base.py +4 -1
- eodag/plugins/crunch/filter_date.py +5 -2
- eodag/plugins/crunch/filter_latest_intersect.py +5 -4
- eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
- eodag/plugins/crunch/filter_overlap.py +5 -7
- eodag/plugins/crunch/filter_property.py +6 -6
- eodag/plugins/download/aws.py +50 -34
- eodag/plugins/download/base.py +41 -50
- eodag/plugins/download/creodias_s3.py +40 -2
- eodag/plugins/download/http.py +221 -195
- eodag/plugins/download/s3rest.py +25 -25
- eodag/plugins/manager.py +168 -23
- eodag/plugins/search/base.py +106 -39
- eodag/plugins/search/build_search_result.py +1065 -324
- eodag/plugins/search/cop_marine.py +112 -29
- eodag/plugins/search/creodias_s3.py +45 -24
- eodag/plugins/search/csw.py +41 -1
- eodag/plugins/search/data_request_search.py +109 -9
- eodag/plugins/search/qssearch.py +549 -257
- eodag/plugins/search/static_stac_search.py +20 -21
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +577 -87
- eodag/resources/providers.yml +1619 -2776
- eodag/resources/stac.yml +3 -163
- eodag/resources/user_conf_template.yml +112 -97
- eodag/rest/config.py +1 -2
- eodag/rest/constants.py +0 -1
- eodag/rest/core.py +138 -98
- eodag/rest/errors.py +181 -0
- eodag/rest/server.py +55 -329
- eodag/rest/stac.py +93 -544
- eodag/rest/types/eodag_search.py +19 -8
- eodag/rest/types/queryables.py +6 -8
- eodag/rest/types/stac_search.py +11 -2
- eodag/rest/utils/__init__.py +3 -0
- eodag/types/__init__.py +71 -18
- eodag/types/download_args.py +3 -3
- eodag/types/queryables.py +180 -73
- eodag/types/search_args.py +3 -3
- eodag/types/whoosh.py +126 -0
- eodag/utils/__init__.py +147 -66
- eodag/utils/exceptions.py +47 -26
- eodag/utils/logging.py +37 -77
- eodag/utils/repr.py +65 -6
- eodag/utils/requests.py +11 -13
- eodag/utils/stac_reader.py +1 -1
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/METADATA +80 -81
- eodag-3.1.0b1.dist-info/RECORD +108 -0
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/WHEEL +1 -1
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/entry_points.txt +4 -2
- eodag/resources/constraints/climate-dt.json +0 -13
- eodag/resources/constraints/extremes-dt.json +0 -8
- eodag/utils/constraints.py +0 -244
- eodag-3.0.0b3.dist-info/RECORD +0 -110
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/LICENSE +0 -0
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/top_level.txt +0 -0
eodag/rest/types/eodag_search.py
CHANGED
|
@@ -115,13 +115,6 @@ class EODAGSearch(BaseModel):
|
|
|
115
115
|
|
|
116
116
|
_to_eodag_map: Dict[str, str]
|
|
117
117
|
|
|
118
|
-
@model_validator(mode="after")
|
|
119
|
-
def set_raise_errors(self) -> Self:
|
|
120
|
-
"""Set raise_errors to True if provider is set"""
|
|
121
|
-
if self.provider:
|
|
122
|
-
self.raise_errors = True
|
|
123
|
-
return self
|
|
124
|
-
|
|
125
118
|
@model_validator(mode="after")
|
|
126
119
|
def remove_timeFromAscendingNode(self) -> Self: # pylint: disable=invalid-name
|
|
127
120
|
"""TimeFromAscendingNode are just used for translation and not for search"""
|
|
@@ -367,9 +360,27 @@ class EODAGSearch(BaseModel):
|
|
|
367
360
|
return cls._to_eodag_map.get(value, value)
|
|
368
361
|
|
|
369
362
|
@classmethod
|
|
370
|
-
def to_stac(
|
|
363
|
+
def to_stac(
|
|
364
|
+
cls,
|
|
365
|
+
field_name: str,
|
|
366
|
+
stac_item_properties: Optional[List[str]] = None,
|
|
367
|
+
provider: Optional[str] = None,
|
|
368
|
+
) -> str:
|
|
371
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"
|
|
372
376
|
field = cls.model_fields.get(field_name)
|
|
373
377
|
if field is not None and field.alias is not None:
|
|
374
378
|
return field.alias
|
|
379
|
+
if (
|
|
380
|
+
provider
|
|
381
|
+
and ":" not in field_name
|
|
382
|
+
and stac_item_properties
|
|
383
|
+
and field_name not in stac_item_properties
|
|
384
|
+
):
|
|
385
|
+
return f"{provider}:{field_name}"
|
|
375
386
|
return field_name
|
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, Any, ClassVar, Dict, List, Optional, Union
|
|
20
|
+
from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Dict, List, Optional, Union
|
|
21
21
|
|
|
22
22
|
from pydantic import (
|
|
23
23
|
BaseModel,
|
|
@@ -32,7 +32,6 @@ from pydantic import (
|
|
|
32
32
|
from eodag.rest.types.eodag_search import EODAGSearch
|
|
33
33
|
from eodag.rest.utils.rfc3339 import str_to_interval
|
|
34
34
|
from eodag.types import python_field_definition_to_json
|
|
35
|
-
from eodag.utils import Annotated
|
|
36
35
|
|
|
37
36
|
if TYPE_CHECKING:
|
|
38
37
|
from pydantic.fields import FieldInfo
|
|
@@ -132,14 +131,12 @@ class StacQueryables(BaseModel):
|
|
|
132
131
|
default="Queryable names for the EODAG STAC API Item Search filter."
|
|
133
132
|
)
|
|
134
133
|
default_properties: ClassVar[Dict[str, StacQueryableProperty]] = {
|
|
135
|
-
"id": StacQueryableProperty(
|
|
136
|
-
description="ID",
|
|
137
|
-
ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/id",
|
|
138
|
-
),
|
|
139
134
|
"collection": StacQueryableProperty(
|
|
140
135
|
description="Collection",
|
|
141
136
|
ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/collection",
|
|
142
|
-
)
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
possible_properties: ClassVar[Dict[str, StacQueryableProperty]] = {
|
|
143
140
|
"geometry": StacQueryableProperty(
|
|
144
141
|
description="Geometry",
|
|
145
142
|
ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/geometry",
|
|
@@ -155,7 +152,8 @@ class StacQueryables(BaseModel):
|
|
|
155
152
|
items={"type": "number"},
|
|
156
153
|
),
|
|
157
154
|
}
|
|
158
|
-
properties: Dict[str,
|
|
155
|
+
properties: Dict[str, Any] = Field()
|
|
156
|
+
required: Optional[List[str]] = Field(None)
|
|
159
157
|
additional_properties: bool = Field(
|
|
160
158
|
default=True, serialization_alias="additionalProperties"
|
|
161
159
|
)
|
eodag/rest/types/stac_search.py
CHANGED
|
@@ -19,7 +19,17 @@
|
|
|
19
19
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
|
-
from typing import
|
|
22
|
+
from typing import (
|
|
23
|
+
TYPE_CHECKING,
|
|
24
|
+
Annotated,
|
|
25
|
+
Any,
|
|
26
|
+
Dict,
|
|
27
|
+
List,
|
|
28
|
+
Literal,
|
|
29
|
+
Optional,
|
|
30
|
+
Tuple,
|
|
31
|
+
Union,
|
|
32
|
+
)
|
|
23
33
|
|
|
24
34
|
import geojson
|
|
25
35
|
from pydantic import (
|
|
@@ -43,7 +53,6 @@ from shapely.geometry import (
|
|
|
43
53
|
shape,
|
|
44
54
|
)
|
|
45
55
|
from shapely.geometry.base import GEOMETRY_TYPES, BaseGeometry
|
|
46
|
-
from typing_extensions import Annotated
|
|
47
56
|
|
|
48
57
|
from eodag.rest.utils.rfc3339 import rfc3339_str_to_datetime, str_to_interval
|
|
49
58
|
from eodag.utils.exceptions import ValidationError
|
eodag/rest/utils/__init__.py
CHANGED
eodag/types/__init__.py
CHANGED
|
@@ -16,15 +16,28 @@
|
|
|
16
16
|
# See the License for the specific language governing permissions and
|
|
17
17
|
# limitations under the License.
|
|
18
18
|
"""EODAG types"""
|
|
19
|
+
|
|
19
20
|
from __future__ import annotations
|
|
20
21
|
|
|
21
|
-
from typing import
|
|
22
|
+
from typing import (
|
|
23
|
+
Annotated,
|
|
24
|
+
Any,
|
|
25
|
+
Dict,
|
|
26
|
+
List,
|
|
27
|
+
Literal,
|
|
28
|
+
Optional,
|
|
29
|
+
Tuple,
|
|
30
|
+
TypedDict,
|
|
31
|
+
Union,
|
|
32
|
+
get_args,
|
|
33
|
+
get_origin,
|
|
34
|
+
)
|
|
22
35
|
|
|
23
36
|
from annotated_types import Gt, Lt
|
|
24
|
-
from pydantic import Field
|
|
37
|
+
from pydantic import BaseModel, Field, create_model
|
|
25
38
|
from pydantic.fields import FieldInfo
|
|
26
39
|
|
|
27
|
-
from eodag.utils import
|
|
40
|
+
from eodag.utils import copy_deepcopy
|
|
28
41
|
from eodag.utils.exceptions import ValidationError
|
|
29
42
|
|
|
30
43
|
# Types mapping from JSON Schema and OpenAPI 3.1.0 specifications to Python
|
|
@@ -58,8 +71,8 @@ def json_type_to_python(json_type: Union[str, List[str]]) -> type:
|
|
|
58
71
|
|
|
59
72
|
|
|
60
73
|
def _get_min_or_max(type_info: Union[Lt, Gt, Any]) -> Tuple[str, Any]:
|
|
61
|
-
"""
|
|
62
|
-
|
|
74
|
+
"""Checks if the value from an Annotated object is a minimum or maximum
|
|
75
|
+
|
|
63
76
|
:param type_info: info from Annotated
|
|
64
77
|
:return: "min" or "max"
|
|
65
78
|
"""
|
|
@@ -71,10 +84,10 @@ def _get_min_or_max(type_info: Union[Lt, Gt, Any]) -> Tuple[str, Any]:
|
|
|
71
84
|
|
|
72
85
|
|
|
73
86
|
def _get_type_info_from_annotated(
|
|
74
|
-
annotated_type: Annotated[type, Any]
|
|
87
|
+
annotated_type: Annotated[type, Any],
|
|
75
88
|
) -> Dict[str, Any]:
|
|
76
|
-
"""
|
|
77
|
-
|
|
89
|
+
"""Retrieves type information from an annotated object
|
|
90
|
+
|
|
78
91
|
:param annotated_type: annotated object
|
|
79
92
|
:return: dict containing type and min/max if available
|
|
80
93
|
"""
|
|
@@ -106,7 +119,8 @@ def python_type_to_json(
|
|
|
106
119
|
:param python_type: the python type
|
|
107
120
|
:returns: the json type
|
|
108
121
|
"""
|
|
109
|
-
|
|
122
|
+
origin = get_origin(python_type)
|
|
123
|
+
if origin is Union:
|
|
110
124
|
json_type = list()
|
|
111
125
|
for single_python_type in get_args(python_type):
|
|
112
126
|
type_data = {}
|
|
@@ -126,8 +140,10 @@ def python_type_to_json(
|
|
|
126
140
|
return list(JSON_TYPES_MAPPING.keys())[
|
|
127
141
|
list(JSON_TYPES_MAPPING.values()).index(python_type)
|
|
128
142
|
]
|
|
129
|
-
elif
|
|
143
|
+
elif origin is Annotated:
|
|
130
144
|
return [_get_type_info_from_annotated(python_type)]
|
|
145
|
+
elif origin is list:
|
|
146
|
+
raise NotImplementedError("Never completed")
|
|
131
147
|
else:
|
|
132
148
|
return None
|
|
133
149
|
|
|
@@ -161,12 +177,27 @@ def json_field_definition_to_python(
|
|
|
161
177
|
title=json_field_definition.get("title", None),
|
|
162
178
|
description=json_field_definition.get("description", None),
|
|
163
179
|
pattern=json_field_definition.get("pattern", None),
|
|
180
|
+
le=json_field_definition.get("maximum"),
|
|
181
|
+
ge=json_field_definition.get("minimum"),
|
|
164
182
|
)
|
|
165
183
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
):
|
|
169
|
-
|
|
184
|
+
enum = json_field_definition.get("enum")
|
|
185
|
+
|
|
186
|
+
if python_type in (list, set):
|
|
187
|
+
items = json_field_definition.get("items", None)
|
|
188
|
+
if isinstance(items, list):
|
|
189
|
+
python_type = Tuple[ # type: ignore
|
|
190
|
+
tuple(
|
|
191
|
+
json_field_definition_to_python(item, required=required)
|
|
192
|
+
for item in items
|
|
193
|
+
)
|
|
194
|
+
]
|
|
195
|
+
elif isinstance(items, dict):
|
|
196
|
+
enum = items.get("enum")
|
|
197
|
+
|
|
198
|
+
if enum:
|
|
199
|
+
literal = Literal[tuple(sorted(enum))] # type: ignore
|
|
200
|
+
python_type = List[literal] if python_type in (list, set) else literal # type: ignore
|
|
170
201
|
|
|
171
202
|
if "$ref" in json_field_definition:
|
|
172
203
|
field_type_kwargs["json_schema_extra"] = {"$ref": json_field_definition["$ref"]}
|
|
@@ -178,12 +209,12 @@ def json_field_definition_to_python(
|
|
|
178
209
|
|
|
179
210
|
|
|
180
211
|
def python_field_definition_to_json(
|
|
181
|
-
python_field_definition: Annotated[Any, FieldInfo]
|
|
212
|
+
python_field_definition: Annotated[Any, FieldInfo],
|
|
182
213
|
) -> Dict[str, Any]:
|
|
183
214
|
"""Get json field definition from python `typing.Annotated`
|
|
184
215
|
|
|
185
216
|
>>> from pydantic import Field
|
|
186
|
-
>>> from
|
|
217
|
+
>>> from typing import Annotated
|
|
187
218
|
>>> python_field_definition_to_json(
|
|
188
219
|
... Annotated[
|
|
189
220
|
... Optional[str],
|
|
@@ -240,6 +271,7 @@ def python_field_definition_to_json(
|
|
|
240
271
|
json_field_definition["max"] = [
|
|
241
272
|
row["max"] if "max" in row else None for row in field_type
|
|
242
273
|
]
|
|
274
|
+
|
|
243
275
|
if "min" in json_field_definition and json_field_definition["min"].count(
|
|
244
276
|
None
|
|
245
277
|
) == len(json_field_definition["min"]):
|
|
@@ -279,7 +311,7 @@ def python_field_definition_to_json(
|
|
|
279
311
|
|
|
280
312
|
|
|
281
313
|
def model_fields_to_annotated(
|
|
282
|
-
model_fields: Dict[str, FieldInfo]
|
|
314
|
+
model_fields: Dict[str, FieldInfo],
|
|
283
315
|
) -> Dict[str, Annotated[Any, FieldInfo]]:
|
|
284
316
|
"""Convert BaseModel.model_fields from FieldInfo to Annotated
|
|
285
317
|
|
|
@@ -294,7 +326,7 @@ def model_fields_to_annotated(
|
|
|
294
326
|
:param model_fields: BaseModel.model_fields to convert
|
|
295
327
|
:returns: Annotated tuple usable as create_model argument
|
|
296
328
|
"""
|
|
297
|
-
annotated_model_fields = dict()
|
|
329
|
+
annotated_model_fields: Dict[str, Annotated[Any, FieldInfo]] = dict()
|
|
298
330
|
for param, field_info in model_fields.items():
|
|
299
331
|
field_type = field_info.annotation or type(None)
|
|
300
332
|
new_field_info = copy_deepcopy(field_info)
|
|
@@ -303,6 +335,27 @@ def model_fields_to_annotated(
|
|
|
303
335
|
return annotated_model_fields
|
|
304
336
|
|
|
305
337
|
|
|
338
|
+
def annotated_dict_to_model(
|
|
339
|
+
model_name: str, annotated_fields: Dict[str, Annotated[Any, FieldInfo]]
|
|
340
|
+
) -> BaseModel:
|
|
341
|
+
"""Convert a dictionary of Annotated values to a Pydantic BaseModel.
|
|
342
|
+
|
|
343
|
+
:param model_name: name of the model to be created
|
|
344
|
+
:param annotated_fields: dict containing the parameters and annotated values that should become
|
|
345
|
+
the properties of the model
|
|
346
|
+
:returns: pydantic model
|
|
347
|
+
"""
|
|
348
|
+
fields = {
|
|
349
|
+
name: (field.__args__[0], field.__metadata__[0])
|
|
350
|
+
for name, field in annotated_fields.items()
|
|
351
|
+
}
|
|
352
|
+
return create_model(
|
|
353
|
+
model_name,
|
|
354
|
+
**fields, # type: ignore
|
|
355
|
+
__config__={"arbitrary_types_allowed": True},
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
306
359
|
class ProviderSortables(TypedDict):
|
|
307
360
|
"""A class representing sortable parameter(s) of a provider and the allowed
|
|
308
361
|
maximum number of used sortable(s) in a search request with the provider
|
eodag/types/download_args.py
CHANGED
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
# limitations under the License.
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
from typing import Dict, Optional, TypedDict
|
|
20
|
+
from typing import Dict, Optional, TypedDict, Union
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class DownloadConf(TypedDict, total=False):
|
|
24
24
|
"""Download configuration
|
|
25
25
|
|
|
26
|
-
:cvar
|
|
26
|
+
:cvar output_dir: where to store downloaded products, as an absolute file path
|
|
27
27
|
(Default: local temporary directory)
|
|
28
28
|
:cvar output_extension: downloaded file extension
|
|
29
29
|
:cvar extract: whether to extract the downloaded products, only applies to archived products
|
|
@@ -33,7 +33,7 @@ class DownloadConf(TypedDict, total=False):
|
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
35
|
output_dir: str
|
|
36
|
-
output_extension: str
|
|
36
|
+
output_extension: Union[str, None]
|
|
37
37
|
extract: bool
|
|
38
38
|
dl_url_params: Dict[str, str]
|
|
39
39
|
delete_archive: bool
|
eodag/types/queryables.py
CHANGED
|
@@ -15,12 +15,20 @@
|
|
|
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
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from collections import UserDict
|
|
21
|
+
from datetime import date, datetime
|
|
22
|
+
from typing import Annotated, Any, Optional, Union
|
|
19
23
|
|
|
20
24
|
from annotated_types import Lt
|
|
21
25
|
from pydantic import BaseModel, Field
|
|
26
|
+
from pydantic.fields import FieldInfo
|
|
22
27
|
from pydantic.types import PositiveInt
|
|
23
|
-
from
|
|
28
|
+
from pydantic_core import PydanticUndefined
|
|
29
|
+
|
|
30
|
+
from eodag.types import annotated_dict_to_model, model_fields_to_annotated
|
|
31
|
+
from eodag.utils.repr import remove_class_repr, shorter_type_repr
|
|
24
32
|
|
|
25
33
|
Percentage = Annotated[PositiveInt, Lt(100)]
|
|
26
34
|
|
|
@@ -29,17 +37,11 @@ class CommonQueryables(BaseModel):
|
|
|
29
37
|
"""A class representing search common queryable properties."""
|
|
30
38
|
|
|
31
39
|
productType: Annotated[str, Field()]
|
|
32
|
-
id: Annotated[Optional[str], Field(None)]
|
|
33
|
-
start: Annotated[Optional[str], Field(None, alias="startTimeFromAscendingNode")]
|
|
34
|
-
end: Annotated[Optional[str], Field(None, alias="completionTimeFromAscendingNode")]
|
|
35
|
-
geom: Annotated[Optional[str], Field(None, alias="geometry")]
|
|
36
40
|
|
|
37
41
|
@classmethod
|
|
38
42
|
def get_queryable_from_alias(cls, value: str) -> str:
|
|
39
43
|
"""Get queryable parameter from alias
|
|
40
44
|
|
|
41
|
-
>>> CommonQueryables.get_queryable_from_alias('startTimeFromAscendingNode')
|
|
42
|
-
'start'
|
|
43
45
|
>>> CommonQueryables.get_queryable_from_alias('productType')
|
|
44
46
|
'productType'
|
|
45
47
|
"""
|
|
@@ -50,75 +52,180 @@ class CommonQueryables(BaseModel):
|
|
|
50
52
|
}
|
|
51
53
|
return alias_map.get(value, value)
|
|
52
54
|
|
|
55
|
+
@classmethod
|
|
56
|
+
def get_with_default(
|
|
57
|
+
cls, field: str, default: Optional[Any]
|
|
58
|
+
) -> Annotated[Any, FieldInfo]:
|
|
59
|
+
"""Get field and set default value."""
|
|
60
|
+
annotated_fields = model_fields_to_annotated(cls.model_fields)
|
|
61
|
+
f = annotated_fields[field]
|
|
62
|
+
if default is None:
|
|
63
|
+
return f
|
|
64
|
+
f.__metadata__[0].default = default
|
|
65
|
+
return f
|
|
66
|
+
|
|
53
67
|
|
|
54
68
|
class Queryables(CommonQueryables):
|
|
55
|
-
"""A class representing all search queryable properties.
|
|
69
|
+
"""A class representing all search queryable properties.
|
|
56
70
|
|
|
57
|
-
|
|
71
|
+
Parameters default value is set to ``None`` to have them not required.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
start: Annotated[
|
|
75
|
+
Union[datetime, date], Field(None, alias="startTimeFromAscendingNode")
|
|
76
|
+
]
|
|
77
|
+
end: Annotated[
|
|
78
|
+
Union[datetime, date], Field(None, alias="completionTimeFromAscendingNode")
|
|
79
|
+
]
|
|
80
|
+
geom: Annotated[str, Field(None, alias="geometry")]
|
|
81
|
+
uid: Annotated[str, Field(None)]
|
|
58
82
|
# OpenSearch Parameters for Collection Search (Table 3)
|
|
59
|
-
doi: Annotated[
|
|
60
|
-
platform: Annotated[
|
|
61
|
-
platformSerialIdentifier: Annotated[
|
|
62
|
-
instrument: Annotated[
|
|
63
|
-
sensorType: Annotated[
|
|
64
|
-
compositeType: Annotated[
|
|
65
|
-
processingLevel: Annotated[
|
|
66
|
-
orbitType: Annotated[
|
|
67
|
-
spectralRange: Annotated[
|
|
68
|
-
wavelengths: Annotated[
|
|
69
|
-
hasSecurityConstraints: Annotated[
|
|
70
|
-
dissemination: Annotated[
|
|
83
|
+
doi: Annotated[str, Field(None)]
|
|
84
|
+
platform: Annotated[str, Field(None)]
|
|
85
|
+
platformSerialIdentifier: Annotated[str, Field(None)]
|
|
86
|
+
instrument: Annotated[str, Field(None)]
|
|
87
|
+
sensorType: Annotated[str, Field(None)]
|
|
88
|
+
compositeType: Annotated[str, Field(None)]
|
|
89
|
+
processingLevel: Annotated[str, Field(None)]
|
|
90
|
+
orbitType: Annotated[str, Field(None)]
|
|
91
|
+
spectralRange: Annotated[str, Field(None)]
|
|
92
|
+
wavelengths: Annotated[str, Field(None)]
|
|
93
|
+
hasSecurityConstraints: Annotated[str, Field(None)]
|
|
94
|
+
dissemination: Annotated[str, Field(None)]
|
|
71
95
|
# INSPIRE obligated OpenSearch Parameters for Collection Search (Table 4)
|
|
72
|
-
title: Annotated[
|
|
73
|
-
topicCategory: Annotated[
|
|
74
|
-
keyword: Annotated[
|
|
75
|
-
abstract: Annotated[
|
|
76
|
-
resolution: Annotated[
|
|
77
|
-
organisationName: Annotated[
|
|
78
|
-
organisationRole: Annotated[
|
|
79
|
-
publicationDate: Annotated[
|
|
80
|
-
lineage: Annotated[
|
|
81
|
-
useLimitation: Annotated[
|
|
82
|
-
accessConstraint: Annotated[
|
|
83
|
-
otherConstraint: Annotated[
|
|
84
|
-
classification: Annotated[
|
|
85
|
-
language: Annotated[
|
|
86
|
-
specification: Annotated[
|
|
96
|
+
title: Annotated[str, Field(None)]
|
|
97
|
+
topicCategory: Annotated[str, Field(None)]
|
|
98
|
+
keyword: Annotated[str, Field(None)]
|
|
99
|
+
abstract: Annotated[str, Field(None)]
|
|
100
|
+
resolution: Annotated[int, Field(None)]
|
|
101
|
+
organisationName: Annotated[str, Field(None)]
|
|
102
|
+
organisationRole: Annotated[str, Field(None)]
|
|
103
|
+
publicationDate: Annotated[str, Field(None)]
|
|
104
|
+
lineage: Annotated[str, Field(None)]
|
|
105
|
+
useLimitation: Annotated[str, Field(None)]
|
|
106
|
+
accessConstraint: Annotated[str, Field(None)]
|
|
107
|
+
otherConstraint: Annotated[str, Field(None)]
|
|
108
|
+
classification: Annotated[str, Field(None)]
|
|
109
|
+
language: Annotated[str, Field(None)]
|
|
110
|
+
specification: Annotated[str, Field(None)]
|
|
87
111
|
# OpenSearch Parameters for Product Search (Table 5)
|
|
88
|
-
parentIdentifier: Annotated[
|
|
89
|
-
productionStatus: Annotated[
|
|
90
|
-
acquisitionType: Annotated[
|
|
91
|
-
orbitNumber: Annotated[
|
|
92
|
-
orbitDirection: Annotated[
|
|
93
|
-
track: Annotated[
|
|
94
|
-
frame: Annotated[
|
|
95
|
-
swathIdentifier: Annotated[
|
|
96
|
-
cloudCover: Annotated[
|
|
97
|
-
snowCover: Annotated[
|
|
98
|
-
lowestLocation: Annotated[
|
|
99
|
-
highestLocation: Annotated[
|
|
100
|
-
productVersion: Annotated[
|
|
101
|
-
productQualityStatus: Annotated[
|
|
102
|
-
productQualityDegradationTag: Annotated[
|
|
103
|
-
processorName: Annotated[
|
|
104
|
-
processingCenter: Annotated[
|
|
105
|
-
creationDate: Annotated[
|
|
106
|
-
modificationDate: Annotated[
|
|
107
|
-
processingDate: Annotated[
|
|
108
|
-
sensorMode: Annotated[
|
|
109
|
-
archivingCenter: Annotated[
|
|
110
|
-
processingMode: Annotated[
|
|
112
|
+
parentIdentifier: Annotated[str, Field(None)]
|
|
113
|
+
productionStatus: Annotated[str, Field(None)]
|
|
114
|
+
acquisitionType: Annotated[str, Field(None)]
|
|
115
|
+
orbitNumber: Annotated[int, Field(None)]
|
|
116
|
+
orbitDirection: Annotated[str, Field(None)]
|
|
117
|
+
track: Annotated[str, Field(None)]
|
|
118
|
+
frame: Annotated[str, Field(None)]
|
|
119
|
+
swathIdentifier: Annotated[str, Field(None)]
|
|
120
|
+
cloudCover: Annotated[Percentage, Field(None)]
|
|
121
|
+
snowCover: Annotated[Percentage, Field(None)]
|
|
122
|
+
lowestLocation: Annotated[str, Field(None)]
|
|
123
|
+
highestLocation: Annotated[str, Field(None)]
|
|
124
|
+
productVersion: Annotated[str, Field(None)]
|
|
125
|
+
productQualityStatus: Annotated[str, Field(None)]
|
|
126
|
+
productQualityDegradationTag: Annotated[str, Field(None)]
|
|
127
|
+
processorName: Annotated[str, Field(None)]
|
|
128
|
+
processingCenter: Annotated[str, Field(None)]
|
|
129
|
+
creationDate: Annotated[str, Field(None)]
|
|
130
|
+
modificationDate: Annotated[str, Field(None)]
|
|
131
|
+
processingDate: Annotated[str, Field(None)]
|
|
132
|
+
sensorMode: Annotated[str, Field(None)]
|
|
133
|
+
archivingCenter: Annotated[str, Field(None)]
|
|
134
|
+
processingMode: Annotated[str, Field(None)]
|
|
111
135
|
# OpenSearch Parameters for Acquistion Parameters Search (Table 6)
|
|
112
|
-
availabilityTime: Annotated[
|
|
113
|
-
acquisitionStation: Annotated[
|
|
114
|
-
acquisitionSubType: Annotated[
|
|
115
|
-
illuminationAzimuthAngle: Annotated[
|
|
116
|
-
illuminationZenithAngle: Annotated[
|
|
117
|
-
illuminationElevationAngle: Annotated[
|
|
118
|
-
polarizationMode: Annotated[
|
|
119
|
-
polarizationChannels: Annotated[
|
|
120
|
-
antennaLookDirection: Annotated[
|
|
121
|
-
minimumIncidenceAngle: Annotated[
|
|
122
|
-
maximumIncidenceAngle: Annotated[
|
|
123
|
-
dopplerFrequency: Annotated[
|
|
124
|
-
incidenceAngleVariation: Annotated[
|
|
136
|
+
availabilityTime: Annotated[str, Field(None)]
|
|
137
|
+
acquisitionStation: Annotated[str, Field(None)]
|
|
138
|
+
acquisitionSubType: Annotated[str, Field(None)]
|
|
139
|
+
illuminationAzimuthAngle: Annotated[str, Field(None)]
|
|
140
|
+
illuminationZenithAngle: Annotated[str, Field(None)]
|
|
141
|
+
illuminationElevationAngle: Annotated[str, Field(None)]
|
|
142
|
+
polarizationMode: Annotated[str, Field(None)]
|
|
143
|
+
polarizationChannels: Annotated[str, Field(None)]
|
|
144
|
+
antennaLookDirection: Annotated[str, Field(None)]
|
|
145
|
+
minimumIncidenceAngle: Annotated[float, Field(None)]
|
|
146
|
+
maximumIncidenceAngle: Annotated[float, Field(None)]
|
|
147
|
+
dopplerFrequency: Annotated[float, Field(None)]
|
|
148
|
+
incidenceAngleVariation: Annotated[float, Field(None)]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class QueryablesDict(UserDict):
|
|
152
|
+
"""Class inheriting from UserDict which contains queryables with their annotated type;
|
|
153
|
+
|
|
154
|
+
:param additional_properties: if additional properties (properties not given in EODAG config)
|
|
155
|
+
are allowed
|
|
156
|
+
:param kwargs: named arguments to initialise the dict (queryable keys + annotated types)
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
additional_properties: bool = Field(True)
|
|
160
|
+
additional_information: str = Field("")
|
|
161
|
+
|
|
162
|
+
def __init__(
|
|
163
|
+
self, additional_properties: bool, additional_information: str = "", **kwargs
|
|
164
|
+
):
|
|
165
|
+
self.additional_properties = additional_properties
|
|
166
|
+
self.additional_information = additional_information
|
|
167
|
+
super().__init__(kwargs)
|
|
168
|
+
|
|
169
|
+
def _repr_html_(self, embedded: bool = False) -> str:
|
|
170
|
+
add_info = (
|
|
171
|
+
f" additional_information={self.additional_information}"
|
|
172
|
+
if self.additional_information
|
|
173
|
+
else ""
|
|
174
|
+
)
|
|
175
|
+
thead = (
|
|
176
|
+
f"""<thead><tr><td style='text-align: left; color: grey;'>
|
|
177
|
+
{type(self).__name__} ({len(self)}) - additional_properties={
|
|
178
|
+
self.additional_properties}
|
|
179
|
+
"""
|
|
180
|
+
+ add_info
|
|
181
|
+
+ "</td></tr></thead>"
|
|
182
|
+
if not embedded
|
|
183
|
+
else ""
|
|
184
|
+
)
|
|
185
|
+
tr_style = "style='background-color: transparent;'" if embedded else ""
|
|
186
|
+
return (
|
|
187
|
+
f"<table>{thead}<tbody>"
|
|
188
|
+
+ "".join(
|
|
189
|
+
[
|
|
190
|
+
f"""<tr {tr_style}><td style='text-align: left;'>
|
|
191
|
+
<details><summary style='color: grey;'>
|
|
192
|
+
<span style='color: black'>'{k}'</span>: 
|
|
193
|
+
typing.Annotated[{
|
|
194
|
+
"<span style='color: black'>" + shorter_type_repr(v.__args__[0]) + "</span>, "
|
|
195
|
+
}
|
|
196
|
+
FieldInfo({"'default': '<span style='color: black'>"
|
|
197
|
+
+ str(v.__metadata__[0].get_default()) + "</span>', "
|
|
198
|
+
if v.__metadata__[0].get_default()
|
|
199
|
+
and v.__metadata__[0].get_default() != PydanticUndefined else ""}
|
|
200
|
+
{"'required': <span style='color: black'>"
|
|
201
|
+
+ str(v.__metadata__[0].is_required()) + "</span>,"}
|
|
202
|
+
...
|
|
203
|
+
)]
|
|
204
|
+
</summary>
|
|
205
|
+
<span style='color: grey'>typing.Annotated[</span><table style='margin: 0;'>
|
|
206
|
+
<tr style='background-color: transparent;'>
|
|
207
|
+
<td style='padding: 5px 0 0 10px; text-align: left; vertical-align:top;'>
|
|
208
|
+
{remove_class_repr(str(v.__args__[0]))},</td>
|
|
209
|
+
</tr><tr style='background-color: transparent;'>
|
|
210
|
+
<td style='padding: 5px 0 0 10px; text-align: left; vertical-align:top;'>
|
|
211
|
+
{v.__metadata__[0].__repr__()}</td>
|
|
212
|
+
</tr>
|
|
213
|
+
</table><span style='color: grey'>]</span>
|
|
214
|
+
</details>
|
|
215
|
+
</td></tr>
|
|
216
|
+
"""
|
|
217
|
+
for k, v in self.items()
|
|
218
|
+
]
|
|
219
|
+
)
|
|
220
|
+
+ "</tbody></table>"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def get_model(self, model_name: str = "Queryables") -> BaseModel:
|
|
224
|
+
"""
|
|
225
|
+
Converts object from :class:`eodag.api.product.QueryablesDict` to :class:`pydantic.BaseModel`
|
|
226
|
+
so that validation can be performed
|
|
227
|
+
|
|
228
|
+
:param model_name: name used for :class:`pydantic.BaseModel` creation
|
|
229
|
+
:return: pydantic BaseModel of the queryables dict
|
|
230
|
+
"""
|
|
231
|
+
return annotated_dict_to_model(model_name, self.data)
|
eodag/types/search_args.py
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
# limitations under the License.
|
|
18
18
|
import re
|
|
19
19
|
from datetime import datetime
|
|
20
|
-
from typing import Any, Dict, List, Optional, Tuple, Union, cast
|
|
20
|
+
from typing import Annotated, Any, Dict, List, Optional, Tuple, Union, cast
|
|
21
21
|
|
|
22
22
|
from annotated_types import MinLen
|
|
23
23
|
from pydantic import BaseModel, ConfigDict, Field, conint, field_validator
|
|
@@ -27,7 +27,7 @@ from shapely.geometry import Polygon, shape
|
|
|
27
27
|
from shapely.geometry.base import GEOMETRY_TYPES, BaseGeometry
|
|
28
28
|
|
|
29
29
|
from eodag.types.bbox import BBox
|
|
30
|
-
from eodag.utils import DEFAULT_ITEMS_PER_PAGE, DEFAULT_PAGE
|
|
30
|
+
from eodag.utils import DEFAULT_ITEMS_PER_PAGE, DEFAULT_PAGE
|
|
31
31
|
from eodag.utils.exceptions import ValidationError
|
|
32
32
|
|
|
33
33
|
NumType = Union[float, int]
|
|
@@ -119,7 +119,7 @@ class SearchArgs(BaseModel):
|
|
|
119
119
|
)
|
|
120
120
|
sort_by_arg[i] = (sort_param, sort_order[:3])
|
|
121
121
|
# remove duplicates
|
|
122
|
-
pruned_sort_by_arg: SortByList = list(
|
|
122
|
+
pruned_sort_by_arg: SortByList = list(dict.fromkeys(sort_by_arg)) # type: ignore
|
|
123
123
|
for i, sort_by_tuple in enumerate(pruned_sort_by_arg):
|
|
124
124
|
for j, sort_by_tuple_tmp in enumerate(pruned_sort_by_arg):
|
|
125
125
|
# since duplicated tuples or dictionnaries have been removed, if two sorting parameters are equal,
|