eodag 3.0.0b3__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 +347 -247
- 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 +129 -93
- eodag/api/search_result.py +28 -12
- eodag/cli.py +61 -24
- eodag/config.py +457 -167
- eodag/plugins/apis/base.py +10 -4
- eodag/plugins/apis/ecmwf.py +53 -23
- eodag/plugins/apis/usgs.py +41 -17
- eodag/plugins/authentication/aws_auth.py +30 -18
- eodag/plugins/authentication/base.py +14 -3
- eodag/plugins/authentication/generic.py +14 -3
- eodag/plugins/authentication/header.py +14 -6
- eodag/plugins/authentication/keycloak.py +44 -25
- eodag/plugins/authentication/oauth.py +18 -4
- eodag/plugins/authentication/openid_connect.py +192 -171
- eodag/plugins/authentication/qsauth.py +12 -4
- eodag/plugins/authentication/sas_auth.py +22 -5
- eodag/plugins/authentication/token.py +95 -17
- eodag/plugins/authentication/token_exchange.py +19 -19
- eodag/plugins/base.py +4 -4
- eodag/plugins/crunch/base.py +8 -5
- eodag/plugins/crunch/filter_date.py +9 -6
- eodag/plugins/crunch/filter_latest_intersect.py +9 -8
- eodag/plugins/crunch/filter_latest_tpl_name.py +8 -8
- eodag/plugins/crunch/filter_overlap.py +9 -11
- eodag/plugins/crunch/filter_property.py +10 -10
- eodag/plugins/download/aws.py +181 -105
- eodag/plugins/download/base.py +49 -67
- eodag/plugins/download/creodias_s3.py +40 -2
- eodag/plugins/download/http.py +247 -223
- eodag/plugins/download/s3rest.py +29 -28
- eodag/plugins/manager.py +176 -41
- eodag/plugins/search/__init__.py +6 -5
- eodag/plugins/search/base.py +123 -60
- eodag/plugins/search/build_search_result.py +1046 -355
- eodag/plugins/search/cop_marine.py +132 -39
- eodag/plugins/search/creodias_s3.py +19 -68
- eodag/plugins/search/csw.py +48 -8
- eodag/plugins/search/data_request_search.py +124 -23
- eodag/plugins/search/qssearch.py +531 -310
- eodag/plugins/search/stac_list_assets.py +85 -0
- eodag/plugins/search/static_stac_search.py +23 -24
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +1295 -355
- eodag/resources/providers.yml +1819 -3010
- eodag/resources/stac.yml +3 -163
- eodag/resources/stac_api.yml +2 -2
- eodag/resources/user_conf_template.yml +115 -99
- eodag/rest/cache.py +2 -2
- eodag/rest/config.py +3 -4
- eodag/rest/constants.py +0 -1
- eodag/rest/core.py +157 -117
- eodag/rest/errors.py +181 -0
- eodag/rest/server.py +57 -339
- eodag/rest/stac.py +133 -581
- eodag/rest/types/collections_search.py +3 -3
- eodag/rest/types/eodag_search.py +41 -30
- eodag/rest/types/queryables.py +42 -32
- eodag/rest/types/stac_search.py +15 -16
- 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 +153 -32
- eodag/types/bbox.py +2 -2
- eodag/types/download_args.py +4 -4
- eodag/types/queryables.py +183 -73
- eodag/types/search_args.py +6 -6
- eodag/types/whoosh.py +127 -3
- eodag/utils/__init__.py +228 -106
- eodag/utils/exceptions.py +47 -26
- eodag/utils/import_system.py +2 -2
- eodag/utils/logging.py +37 -77
- eodag/utils/repr.py +65 -6
- eodag/utils/requests.py +13 -15
- eodag/utils/rest.py +2 -2
- eodag/utils/s3.py +231 -0
- eodag/utils/stac_reader.py +11 -11
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/METADATA +81 -81
- eodag-3.1.0.dist-info/RECORD +113 -0
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +5 -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.0.dist-info}/LICENSE +0 -0
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/top_level.txt +0 -0
eodag/types/__init__.py
CHANGED
|
@@ -16,20 +16,34 @@
|
|
|
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
|
+
Literal,
|
|
26
|
+
Optional,
|
|
27
|
+
Type,
|
|
28
|
+
TypedDict,
|
|
29
|
+
Union,
|
|
30
|
+
get_args,
|
|
31
|
+
get_origin,
|
|
32
|
+
)
|
|
22
33
|
|
|
23
34
|
from annotated_types import Gt, Lt
|
|
24
|
-
from pydantic import Field
|
|
35
|
+
from pydantic import BaseModel, ConfigDict, Field, create_model
|
|
36
|
+
from pydantic.annotated_handlers import GetJsonSchemaHandler
|
|
25
37
|
from pydantic.fields import FieldInfo
|
|
38
|
+
from pydantic.json_schema import JsonSchemaValue
|
|
39
|
+
from pydantic_core import CoreSchema
|
|
26
40
|
|
|
27
|
-
from eodag.utils import
|
|
41
|
+
from eodag.utils import copy_deepcopy
|
|
28
42
|
from eodag.utils.exceptions import ValidationError
|
|
29
43
|
|
|
30
44
|
# Types mapping from JSON Schema and OpenAPI 3.1.0 specifications to Python
|
|
31
45
|
# See https://spec.openapis.org/oas/v3.1.0#data-types
|
|
32
|
-
JSON_TYPES_MAPPING:
|
|
46
|
+
JSON_TYPES_MAPPING: dict[str, type] = {
|
|
33
47
|
"boolean": bool,
|
|
34
48
|
"integer": int,
|
|
35
49
|
"number": float,
|
|
@@ -40,7 +54,7 @@ JSON_TYPES_MAPPING: Dict[str, type] = {
|
|
|
40
54
|
}
|
|
41
55
|
|
|
42
56
|
|
|
43
|
-
def json_type_to_python(json_type: Union[str,
|
|
57
|
+
def json_type_to_python(json_type: Union[str, list[str]]) -> type:
|
|
44
58
|
"""Get python type from json type https://spec.openapis.org/oas/v3.1.0#data-types
|
|
45
59
|
|
|
46
60
|
>>> json_type_to_python("number")
|
|
@@ -57,9 +71,9 @@ def json_type_to_python(json_type: Union[str, List[str]]) -> type:
|
|
|
57
71
|
return type(None)
|
|
58
72
|
|
|
59
73
|
|
|
60
|
-
def _get_min_or_max(type_info: Union[Lt, Gt, Any]) ->
|
|
61
|
-
"""
|
|
62
|
-
|
|
74
|
+
def _get_min_or_max(type_info: Union[Lt, Gt, Any]) -> tuple[str, Any]:
|
|
75
|
+
"""Checks if the value from an Annotated object is a minimum or maximum
|
|
76
|
+
|
|
63
77
|
:param type_info: info from Annotated
|
|
64
78
|
:return: "min" or "max"
|
|
65
79
|
"""
|
|
@@ -71,10 +85,10 @@ def _get_min_or_max(type_info: Union[Lt, Gt, Any]) -> Tuple[str, Any]:
|
|
|
71
85
|
|
|
72
86
|
|
|
73
87
|
def _get_type_info_from_annotated(
|
|
74
|
-
annotated_type: Annotated[type, Any]
|
|
75
|
-
) ->
|
|
76
|
-
"""
|
|
77
|
-
|
|
88
|
+
annotated_type: Annotated[type, Any],
|
|
89
|
+
) -> dict[str, Any]:
|
|
90
|
+
"""Retrieves type information from an annotated object
|
|
91
|
+
|
|
78
92
|
:param annotated_type: annotated object
|
|
79
93
|
:return: dict containing type and min/max if available
|
|
80
94
|
"""
|
|
@@ -95,7 +109,7 @@ def _get_type_info_from_annotated(
|
|
|
95
109
|
|
|
96
110
|
def python_type_to_json(
|
|
97
111
|
python_type: type,
|
|
98
|
-
) -> Optional[Union[str,
|
|
112
|
+
) -> Optional[Union[str, list[dict[str, Any]]]]:
|
|
99
113
|
"""Get json type from python https://spec.openapis.org/oas/v3.1.0#data-types
|
|
100
114
|
|
|
101
115
|
>>> python_type_to_json(int)
|
|
@@ -106,7 +120,8 @@ def python_type_to_json(
|
|
|
106
120
|
:param python_type: the python type
|
|
107
121
|
:returns: the json type
|
|
108
122
|
"""
|
|
109
|
-
|
|
123
|
+
origin = get_origin(python_type)
|
|
124
|
+
if origin is Union:
|
|
110
125
|
json_type = list()
|
|
111
126
|
for single_python_type in get_args(python_type):
|
|
112
127
|
type_data = {}
|
|
@@ -126,14 +141,16 @@ def python_type_to_json(
|
|
|
126
141
|
return list(JSON_TYPES_MAPPING.keys())[
|
|
127
142
|
list(JSON_TYPES_MAPPING.values()).index(python_type)
|
|
128
143
|
]
|
|
129
|
-
elif
|
|
144
|
+
elif origin is Annotated:
|
|
130
145
|
return [_get_type_info_from_annotated(python_type)]
|
|
146
|
+
elif origin is list:
|
|
147
|
+
raise NotImplementedError("Never completed")
|
|
131
148
|
else:
|
|
132
149
|
return None
|
|
133
150
|
|
|
134
151
|
|
|
135
152
|
def json_field_definition_to_python(
|
|
136
|
-
json_field_definition:
|
|
153
|
+
json_field_definition: dict[str, Any],
|
|
137
154
|
default_value: Optional[Any] = None,
|
|
138
155
|
required: Optional[bool] = False,
|
|
139
156
|
) -> Annotated[Any, FieldInfo]:
|
|
@@ -161,29 +178,52 @@ def json_field_definition_to_python(
|
|
|
161
178
|
title=json_field_definition.get("title", None),
|
|
162
179
|
description=json_field_definition.get("description", None),
|
|
163
180
|
pattern=json_field_definition.get("pattern", None),
|
|
181
|
+
le=json_field_definition.get("maximum"),
|
|
182
|
+
ge=json_field_definition.get("minimum"),
|
|
164
183
|
)
|
|
165
184
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
):
|
|
169
|
-
|
|
185
|
+
enum = json_field_definition.get("enum")
|
|
186
|
+
|
|
187
|
+
if python_type in (list, set):
|
|
188
|
+
items = json_field_definition.get("items", None)
|
|
189
|
+
if isinstance(items, list):
|
|
190
|
+
python_type = tuple[ # type: ignore
|
|
191
|
+
tuple(
|
|
192
|
+
json_field_definition_to_python(item, required=required)
|
|
193
|
+
for item in items
|
|
194
|
+
)
|
|
195
|
+
]
|
|
196
|
+
elif isinstance(items, dict):
|
|
197
|
+
enum = items.get("enum")
|
|
198
|
+
|
|
199
|
+
if enum:
|
|
200
|
+
literal = Literal[tuple(sorted(enum))] # type: ignore
|
|
201
|
+
python_type = list[literal] if python_type in (list, set) else literal # type: ignore
|
|
170
202
|
|
|
171
203
|
if "$ref" in json_field_definition:
|
|
172
204
|
field_type_kwargs["json_schema_extra"] = {"$ref": json_field_definition["$ref"]}
|
|
173
205
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
206
|
+
metadata = [
|
|
207
|
+
python_type,
|
|
208
|
+
Field(
|
|
209
|
+
default_value if not required or default_value is not None else ...,
|
|
210
|
+
**field_type_kwargs,
|
|
211
|
+
),
|
|
212
|
+
]
|
|
213
|
+
|
|
214
|
+
if required:
|
|
215
|
+
metadata.append("json_schema_required")
|
|
216
|
+
|
|
217
|
+
return Annotated[tuple(metadata)]
|
|
178
218
|
|
|
179
219
|
|
|
180
220
|
def python_field_definition_to_json(
|
|
181
|
-
python_field_definition: Annotated[Any, FieldInfo]
|
|
182
|
-
) ->
|
|
221
|
+
python_field_definition: Annotated[Any, FieldInfo],
|
|
222
|
+
) -> dict[str, Any]:
|
|
183
223
|
"""Get json field definition from python `typing.Annotated`
|
|
184
224
|
|
|
185
225
|
>>> from pydantic import Field
|
|
186
|
-
>>> from
|
|
226
|
+
>>> from typing import Annotated
|
|
187
227
|
>>> python_field_definition_to_json(
|
|
188
228
|
... Annotated[
|
|
189
229
|
... Optional[str],
|
|
@@ -200,7 +240,7 @@ def python_field_definition_to_json(
|
|
|
200
240
|
"%s must be an instance of Annotated" % python_field_definition
|
|
201
241
|
)
|
|
202
242
|
|
|
203
|
-
json_field_definition:
|
|
243
|
+
json_field_definition: dict[str, Any] = dict()
|
|
204
244
|
|
|
205
245
|
python_field_args = get_args(python_field_definition)
|
|
206
246
|
|
|
@@ -240,6 +280,7 @@ def python_field_definition_to_json(
|
|
|
240
280
|
json_field_definition["max"] = [
|
|
241
281
|
row["max"] if "max" in row else None for row in field_type
|
|
242
282
|
]
|
|
283
|
+
|
|
243
284
|
if "min" in json_field_definition and json_field_definition["min"].count(
|
|
244
285
|
None
|
|
245
286
|
) == len(json_field_definition["min"]):
|
|
@@ -279,8 +320,8 @@ def python_field_definition_to_json(
|
|
|
279
320
|
|
|
280
321
|
|
|
281
322
|
def model_fields_to_annotated(
|
|
282
|
-
model_fields:
|
|
283
|
-
) ->
|
|
323
|
+
model_fields: dict[str, FieldInfo],
|
|
324
|
+
) -> dict[str, Annotated[Any, FieldInfo]]:
|
|
284
325
|
"""Convert BaseModel.model_fields from FieldInfo to Annotated
|
|
285
326
|
|
|
286
327
|
>>> from pydantic import create_model
|
|
@@ -294,7 +335,7 @@ def model_fields_to_annotated(
|
|
|
294
335
|
:param model_fields: BaseModel.model_fields to convert
|
|
295
336
|
:returns: Annotated tuple usable as create_model argument
|
|
296
337
|
"""
|
|
297
|
-
annotated_model_fields = dict()
|
|
338
|
+
annotated_model_fields: dict[str, Annotated[Any, FieldInfo]] = dict()
|
|
298
339
|
for param, field_info in model_fields.items():
|
|
299
340
|
field_type = field_info.annotation or type(None)
|
|
300
341
|
new_field_info = copy_deepcopy(field_info)
|
|
@@ -303,6 +344,77 @@ def model_fields_to_annotated(
|
|
|
303
344
|
return annotated_model_fields
|
|
304
345
|
|
|
305
346
|
|
|
347
|
+
class BaseModelCustomJsonSchema(BaseModel):
|
|
348
|
+
"""Base class for generated models with custom json schema."""
|
|
349
|
+
|
|
350
|
+
@classmethod
|
|
351
|
+
def __get_pydantic_json_schema__(
|
|
352
|
+
cls: Type[BaseModel], core_schema: CoreSchema, handler: GetJsonSchemaHandler
|
|
353
|
+
) -> JsonSchemaValue:
|
|
354
|
+
"""
|
|
355
|
+
Custom get json schema method to handle required fields with default values.
|
|
356
|
+
This is not supported by Pydantic by default.
|
|
357
|
+
It requires the field to be marked with the key "json_schema_required" in the metadata dict.
|
|
358
|
+
Example: Annotated[str, "json_schema_required"]
|
|
359
|
+
"""
|
|
360
|
+
json_schema = handler.resolve_ref_schema(handler(core_schema))
|
|
361
|
+
|
|
362
|
+
json_schema["required"] = [
|
|
363
|
+
key
|
|
364
|
+
for key, field_info in cls.model_fields.items()
|
|
365
|
+
if "json_schema_required" in field_info.metadata
|
|
366
|
+
]
|
|
367
|
+
|
|
368
|
+
return json_schema
|
|
369
|
+
|
|
370
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def annotated_dict_to_model(
|
|
374
|
+
model_name: str, annotated_fields: dict[str, Annotated[Any, FieldInfo]]
|
|
375
|
+
) -> BaseModel:
|
|
376
|
+
"""Convert a dictionary of Annotated values to a Pydantic BaseModel.
|
|
377
|
+
|
|
378
|
+
>>> from pydantic import Field
|
|
379
|
+
>>> annotated_fields = {
|
|
380
|
+
... "field1": Annotated[str, Field(description="A string field"), "json_schema_required"],
|
|
381
|
+
... "field2": Annotated[int, Field(default=42, description="An integer field")],
|
|
382
|
+
... }
|
|
383
|
+
>>> model = annotated_dict_to_model("TestModel", annotated_fields)
|
|
384
|
+
>>> json_schema = model.model_json_schema()
|
|
385
|
+
>>> json_schema["required"]
|
|
386
|
+
['field1']
|
|
387
|
+
>>> json_schema["properties"]["field1"]
|
|
388
|
+
{'description': 'A string field', 'title': 'Field1', 'type': 'string'}
|
|
389
|
+
>>> json_schema["properties"]["field2"]
|
|
390
|
+
{'default': 42, 'description': 'An integer field', 'title': 'Field2', 'type': 'integer'}
|
|
391
|
+
>>> json_schema["title"]
|
|
392
|
+
'TestModel'
|
|
393
|
+
>>> json_schema["type"]
|
|
394
|
+
'object'
|
|
395
|
+
|
|
396
|
+
:param model_name: name of the model to be created
|
|
397
|
+
:param annotated_fields: dict containing the parameters and annotated values that should become
|
|
398
|
+
the properties of the model
|
|
399
|
+
:returns: pydantic model
|
|
400
|
+
"""
|
|
401
|
+
fields = {}
|
|
402
|
+
for name, field in annotated_fields.items():
|
|
403
|
+
base_type, field_info, *metadata = get_args(field)
|
|
404
|
+
fields[name] = (
|
|
405
|
+
Annotated[tuple([base_type] + metadata)] if metadata else base_type,
|
|
406
|
+
field_info,
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
custom_model = create_model(
|
|
410
|
+
model_name,
|
|
411
|
+
__base__=BaseModelCustomJsonSchema,
|
|
412
|
+
**fields, # type: ignore
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
return custom_model
|
|
416
|
+
|
|
417
|
+
|
|
306
418
|
class ProviderSortables(TypedDict):
|
|
307
419
|
"""A class representing sortable parameter(s) of a provider and the allowed
|
|
308
420
|
maximum number of used sortable(s) in a search request with the provider
|
|
@@ -311,5 +423,14 @@ class ProviderSortables(TypedDict):
|
|
|
311
423
|
:param max_sort_params: (optional) The allowed maximum number of sortable(s) in a search request with the provider
|
|
312
424
|
"""
|
|
313
425
|
|
|
314
|
-
sortables:
|
|
426
|
+
sortables: list[str]
|
|
315
427
|
max_sort_params: Annotated[Optional[int], Gt(0)]
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
class S3SessionKwargs(TypedDict, total=False):
|
|
431
|
+
"""A class representing available keyword arguments to pass to :class:`boto3.session.Session` for authentication"""
|
|
432
|
+
|
|
433
|
+
aws_access_key_id: Optional[str]
|
|
434
|
+
aws_secret_access_key: Optional[str]
|
|
435
|
+
aws_session_token: Optional[str]
|
|
436
|
+
profile_name: Optional[str]
|
eodag/types/bbox.py
CHANGED
|
@@ -15,14 +15,14 @@
|
|
|
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
|
|
18
|
+
from typing import Union
|
|
19
19
|
|
|
20
20
|
from pydantic import BaseModel, ValidationInfo, field_validator
|
|
21
21
|
from shapely.geometry.polygon import Polygon
|
|
22
22
|
|
|
23
23
|
NumType = Union[float, int]
|
|
24
24
|
BBoxArgs = Union[
|
|
25
|
-
|
|
25
|
+
list[NumType], tuple[NumType, NumType, NumType, NumType], dict[str, NumType]
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
|
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
|
|
20
|
+
from typing import 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,8 +33,8 @@ 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
|
-
dl_url_params:
|
|
38
|
+
dl_url_params: dict[str, str]
|
|
39
39
|
delete_archive: bool
|
|
40
40
|
asset: Optional[str]
|
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,183 @@ 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[str, Any]):
|
|
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,
|
|
164
|
+
additional_properties: bool = True,
|
|
165
|
+
additional_information: str = "",
|
|
166
|
+
**kwargs: Any,
|
|
167
|
+
):
|
|
168
|
+
self.additional_properties = additional_properties
|
|
169
|
+
self.additional_information = additional_information
|
|
170
|
+
super().__init__(kwargs)
|
|
171
|
+
|
|
172
|
+
def _repr_html_(self, embedded: bool = False) -> str:
|
|
173
|
+
add_info = (
|
|
174
|
+
f" additional_information={self.additional_information}"
|
|
175
|
+
if self.additional_information
|
|
176
|
+
else ""
|
|
177
|
+
)
|
|
178
|
+
thead = (
|
|
179
|
+
f"""<thead><tr><td style='text-align: left; color: grey;'>
|
|
180
|
+
{type(self).__name__} ({len(self)}) - additional_properties={
|
|
181
|
+
self.additional_properties}
|
|
182
|
+
"""
|
|
183
|
+
+ add_info
|
|
184
|
+
+ "</td></tr></thead>"
|
|
185
|
+
if not embedded
|
|
186
|
+
else ""
|
|
187
|
+
)
|
|
188
|
+
tr_style = "style='background-color: transparent;'" if embedded else ""
|
|
189
|
+
return (
|
|
190
|
+
f"<table>{thead}<tbody>"
|
|
191
|
+
+ "".join(
|
|
192
|
+
[
|
|
193
|
+
f"""<tr {tr_style}><td style='text-align: left;'>
|
|
194
|
+
<details><summary style='color: grey;'>
|
|
195
|
+
<span style='color: black'>'{k}'</span>: 
|
|
196
|
+
typing.Annotated[{
|
|
197
|
+
"<span style='color: black'>" + shorter_type_repr(v.__args__[0]) + "</span>, "
|
|
198
|
+
}
|
|
199
|
+
FieldInfo({"'default': '<span style='color: black'>"
|
|
200
|
+
+ str(v.__metadata__[0].get_default()) + "</span>', "
|
|
201
|
+
if v.__metadata__[0].get_default()
|
|
202
|
+
and v.__metadata__[0].get_default() != PydanticUndefined else ""}
|
|
203
|
+
{"'required': <span style='color: black'>"
|
|
204
|
+
+ str(v.__metadata__[0].is_required()) + "</span>,"}
|
|
205
|
+
...
|
|
206
|
+
)]
|
|
207
|
+
</summary>
|
|
208
|
+
<span style='color: grey'>typing.Annotated[</span><table style='margin: 0;'>
|
|
209
|
+
<tr style='background-color: transparent;'>
|
|
210
|
+
<td style='padding: 5px 0 0 10px; text-align: left; vertical-align:top;'>
|
|
211
|
+
{remove_class_repr(str(v.__args__[0]))},</td>
|
|
212
|
+
</tr><tr style='background-color: transparent;'>
|
|
213
|
+
<td style='padding: 5px 0 0 10px; text-align: left; vertical-align:top;'>
|
|
214
|
+
{v.__metadata__[0].__repr__()}</td>
|
|
215
|
+
</tr>
|
|
216
|
+
</table><span style='color: grey'>]</span>
|
|
217
|
+
</details>
|
|
218
|
+
</td></tr>
|
|
219
|
+
"""
|
|
220
|
+
for k, v in self.items()
|
|
221
|
+
]
|
|
222
|
+
)
|
|
223
|
+
+ "</tbody></table>"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def get_model(self, model_name: str = "Queryables") -> BaseModel:
|
|
227
|
+
"""
|
|
228
|
+
Converts object from :class:`eodag.api.product.QueryablesDict` to :class:`pydantic.BaseModel`
|
|
229
|
+
so that validation can be performed
|
|
230
|
+
|
|
231
|
+
:param model_name: name used for :class:`pydantic.BaseModel` creation
|
|
232
|
+
:return: pydantic BaseModel of the queryables dict
|
|
233
|
+
"""
|
|
234
|
+
return annotated_dict_to_model(model_name, self.data)
|