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.
Files changed (77) hide show
  1. eodag/api/core.py +292 -198
  2. eodag/api/product/_assets.py +6 -6
  3. eodag/api/product/_product.py +18 -18
  4. eodag/api/product/metadata_mapping.py +51 -14
  5. eodag/api/search_result.py +29 -3
  6. eodag/cli.py +57 -20
  7. eodag/config.py +413 -117
  8. eodag/plugins/apis/base.py +10 -4
  9. eodag/plugins/apis/ecmwf.py +49 -16
  10. eodag/plugins/apis/usgs.py +30 -7
  11. eodag/plugins/authentication/aws_auth.py +14 -5
  12. eodag/plugins/authentication/base.py +10 -1
  13. eodag/plugins/authentication/generic.py +14 -3
  14. eodag/plugins/authentication/header.py +12 -4
  15. eodag/plugins/authentication/keycloak.py +41 -22
  16. eodag/plugins/authentication/oauth.py +11 -1
  17. eodag/plugins/authentication/openid_connect.py +178 -163
  18. eodag/plugins/authentication/qsauth.py +12 -4
  19. eodag/plugins/authentication/sas_auth.py +19 -2
  20. eodag/plugins/authentication/token.py +93 -15
  21. eodag/plugins/authentication/token_exchange.py +19 -19
  22. eodag/plugins/crunch/base.py +4 -1
  23. eodag/plugins/crunch/filter_date.py +5 -2
  24. eodag/plugins/crunch/filter_latest_intersect.py +5 -4
  25. eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
  26. eodag/plugins/crunch/filter_overlap.py +5 -7
  27. eodag/plugins/crunch/filter_property.py +6 -6
  28. eodag/plugins/download/aws.py +50 -34
  29. eodag/plugins/download/base.py +41 -50
  30. eodag/plugins/download/creodias_s3.py +40 -2
  31. eodag/plugins/download/http.py +221 -195
  32. eodag/plugins/download/s3rest.py +25 -25
  33. eodag/plugins/manager.py +168 -23
  34. eodag/plugins/search/base.py +106 -39
  35. eodag/plugins/search/build_search_result.py +1065 -324
  36. eodag/plugins/search/cop_marine.py +112 -29
  37. eodag/plugins/search/creodias_s3.py +45 -24
  38. eodag/plugins/search/csw.py +41 -1
  39. eodag/plugins/search/data_request_search.py +109 -9
  40. eodag/plugins/search/qssearch.py +549 -257
  41. eodag/plugins/search/static_stac_search.py +20 -21
  42. eodag/resources/ext_product_types.json +1 -1
  43. eodag/resources/product_types.yml +577 -87
  44. eodag/resources/providers.yml +1619 -2776
  45. eodag/resources/stac.yml +3 -163
  46. eodag/resources/user_conf_template.yml +112 -97
  47. eodag/rest/config.py +1 -2
  48. eodag/rest/constants.py +0 -1
  49. eodag/rest/core.py +138 -98
  50. eodag/rest/errors.py +181 -0
  51. eodag/rest/server.py +55 -329
  52. eodag/rest/stac.py +93 -544
  53. eodag/rest/types/eodag_search.py +19 -8
  54. eodag/rest/types/queryables.py +6 -8
  55. eodag/rest/types/stac_search.py +11 -2
  56. eodag/rest/utils/__init__.py +3 -0
  57. eodag/types/__init__.py +71 -18
  58. eodag/types/download_args.py +3 -3
  59. eodag/types/queryables.py +180 -73
  60. eodag/types/search_args.py +3 -3
  61. eodag/types/whoosh.py +126 -0
  62. eodag/utils/__init__.py +147 -66
  63. eodag/utils/exceptions.py +47 -26
  64. eodag/utils/logging.py +37 -77
  65. eodag/utils/repr.py +65 -6
  66. eodag/utils/requests.py +11 -13
  67. eodag/utils/stac_reader.py +1 -1
  68. {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/METADATA +80 -81
  69. eodag-3.1.0b1.dist-info/RECORD +108 -0
  70. {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/WHEEL +1 -1
  71. {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/entry_points.txt +4 -2
  72. eodag/resources/constraints/climate-dt.json +0 -13
  73. eodag/resources/constraints/extremes-dt.json +0 -8
  74. eodag/utils/constraints.py +0 -244
  75. eodag-3.0.0b3.dist-info/RECORD +0 -110
  76. {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/LICENSE +0 -0
  77. {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/top_level.txt +0 -0
@@ -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(cls, field_name: str) -> str:
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
@@ -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, StacQueryableProperty] = Field()
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
  )
@@ -19,7 +19,17 @@
19
19
 
20
20
  from __future__ import annotations
21
21
 
22
- from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple, Union
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
@@ -55,6 +55,9 @@ __all__ = ["get_date", "get_datetime"]
55
55
 
56
56
  logger = logging.getLogger("eodag.rest.utils")
57
57
 
58
+ # Path of the liveness endpoint
59
+ LIVENESS_PROBE_PATH = "/_mgmt/ping"
60
+
58
61
 
59
62
  class Cruncher(NamedTuple):
60
63
  """Type hinted Cruncher namedTuple"""
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 Any, Dict, List, Literal, Optional, Tuple, TypedDict, Union
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 Annotated, copy_deepcopy, get_args, get_origin
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
- checks if the value from an Annotated object is a minimum or maximum
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
- retrieves type information from an annotated object
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
- if get_origin(python_type) is Union:
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 get_origin(python_type) == Annotated:
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
- if "enum" in json_field_definition and (
167
- isinstance(json_field_definition["enum"], (list, set))
168
- ):
169
- python_type = Literal[tuple(sorted(json_field_definition["enum"]))] # type: ignore
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 eodag.utils import Annotated
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
@@ -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 output_prefix: where to store downloaded products, as an absolute file path
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 typing import Optional
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 typing_extensions import Annotated
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
- uid: Annotated[Optional[str], Field(None)]
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[Optional[str], Field(None)]
60
- platform: Annotated[Optional[str], Field(None)]
61
- platformSerialIdentifier: Annotated[Optional[str], Field(None)]
62
- instrument: Annotated[Optional[str], Field(None)]
63
- sensorType: Annotated[Optional[str], Field(None)]
64
- compositeType: Annotated[Optional[str], Field(None)]
65
- processingLevel: Annotated[Optional[str], Field(None)]
66
- orbitType: Annotated[Optional[str], Field(None)]
67
- spectralRange: Annotated[Optional[str], Field(None)]
68
- wavelengths: Annotated[Optional[str], Field(None)]
69
- hasSecurityConstraints: Annotated[Optional[str], Field(None)]
70
- dissemination: Annotated[Optional[str], Field(None)]
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[Optional[str], Field(None)]
73
- topicCategory: Annotated[Optional[str], Field(None)]
74
- keyword: Annotated[Optional[str], Field(None)]
75
- abstract: Annotated[Optional[str], Field(None)]
76
- resolution: Annotated[Optional[int], Field(None)]
77
- organisationName: Annotated[Optional[str], Field(None)]
78
- organisationRole: Annotated[Optional[str], Field(None)]
79
- publicationDate: Annotated[Optional[str], Field(None)]
80
- lineage: Annotated[Optional[str], Field(None)]
81
- useLimitation: Annotated[Optional[str], Field(None)]
82
- accessConstraint: Annotated[Optional[str], Field(None)]
83
- otherConstraint: Annotated[Optional[str], Field(None)]
84
- classification: Annotated[Optional[str], Field(None)]
85
- language: Annotated[Optional[str], Field(None)]
86
- specification: Annotated[Optional[str], Field(None)]
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[Optional[str], Field(None)]
89
- productionStatus: Annotated[Optional[str], Field(None)]
90
- acquisitionType: Annotated[Optional[str], Field(None)]
91
- orbitNumber: Annotated[Optional[int], Field(None)]
92
- orbitDirection: Annotated[Optional[str], Field(None)]
93
- track: Annotated[Optional[str], Field(None)]
94
- frame: Annotated[Optional[str], Field(None)]
95
- swathIdentifier: Annotated[Optional[str], Field(None)]
96
- cloudCover: Annotated[Optional[Percentage], Field(None)]
97
- snowCover: Annotated[Optional[Percentage], Field(None)]
98
- lowestLocation: Annotated[Optional[str], Field(None)]
99
- highestLocation: Annotated[Optional[str], Field(None)]
100
- productVersion: Annotated[Optional[str], Field(None)]
101
- productQualityStatus: Annotated[Optional[str], Field(None)]
102
- productQualityDegradationTag: Annotated[Optional[str], Field(None)]
103
- processorName: Annotated[Optional[str], Field(None)]
104
- processingCenter: Annotated[Optional[str], Field(None)]
105
- creationDate: Annotated[Optional[str], Field(None)]
106
- modificationDate: Annotated[Optional[str], Field(None)]
107
- processingDate: Annotated[Optional[str], Field(None)]
108
- sensorMode: Annotated[Optional[str], Field(None)]
109
- archivingCenter: Annotated[Optional[str], Field(None)]
110
- processingMode: Annotated[Optional[str], Field(None)]
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[Optional[str], Field(None)]
113
- acquisitionStation: Annotated[Optional[str], Field(None)]
114
- acquisitionSubType: Annotated[Optional[str], Field(None)]
115
- illuminationAzimuthAngle: Annotated[Optional[str], Field(None)]
116
- illuminationZenithAngle: Annotated[Optional[str], Field(None)]
117
- illuminationElevationAngle: Annotated[Optional[str], Field(None)]
118
- polarizationMode: Annotated[Optional[str], Field(None)]
119
- polarizationChannels: Annotated[Optional[str], Field(None)]
120
- antennaLookDirection: Annotated[Optional[str], Field(None)]
121
- minimumIncidenceAngle: Annotated[Optional[float], Field(None)]
122
- maximumIncidenceAngle: Annotated[Optional[float], Field(None)]
123
- dopplerFrequency: Annotated[Optional[float], Field(None)]
124
- incidenceAngleVariation: Annotated[Optional[float], Field(None)]
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__}&ensp;({len(self)})&ensp;-&ensp;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>:&ensp;
193
+ typing.Annotated[{
194
+ "<span style='color: black'>" + shorter_type_repr(v.__args__[0]) + "</span>,&ensp;"
195
+ }
196
+ FieldInfo({"'default': '<span style='color: black'>"
197
+ + str(v.__metadata__[0].get_default()) + "</span>',&ensp;"
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)
@@ -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, Annotated
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(set(sort_by_arg)) # type: ignore
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,