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.
Files changed (94) hide show
  1. eodag/api/core.py +347 -247
  2. eodag/api/product/_assets.py +44 -15
  3. eodag/api/product/_product.py +58 -47
  4. eodag/api/product/drivers/__init__.py +81 -4
  5. eodag/api/product/drivers/base.py +65 -4
  6. eodag/api/product/drivers/generic.py +65 -0
  7. eodag/api/product/drivers/sentinel1.py +97 -0
  8. eodag/api/product/drivers/sentinel2.py +95 -0
  9. eodag/api/product/metadata_mapping.py +129 -93
  10. eodag/api/search_result.py +28 -12
  11. eodag/cli.py +61 -24
  12. eodag/config.py +457 -167
  13. eodag/plugins/apis/base.py +10 -4
  14. eodag/plugins/apis/ecmwf.py +53 -23
  15. eodag/plugins/apis/usgs.py +41 -17
  16. eodag/plugins/authentication/aws_auth.py +30 -18
  17. eodag/plugins/authentication/base.py +14 -3
  18. eodag/plugins/authentication/generic.py +14 -3
  19. eodag/plugins/authentication/header.py +14 -6
  20. eodag/plugins/authentication/keycloak.py +44 -25
  21. eodag/plugins/authentication/oauth.py +18 -4
  22. eodag/plugins/authentication/openid_connect.py +192 -171
  23. eodag/plugins/authentication/qsauth.py +12 -4
  24. eodag/plugins/authentication/sas_auth.py +22 -5
  25. eodag/plugins/authentication/token.py +95 -17
  26. eodag/plugins/authentication/token_exchange.py +19 -19
  27. eodag/plugins/base.py +4 -4
  28. eodag/plugins/crunch/base.py +8 -5
  29. eodag/plugins/crunch/filter_date.py +9 -6
  30. eodag/plugins/crunch/filter_latest_intersect.py +9 -8
  31. eodag/plugins/crunch/filter_latest_tpl_name.py +8 -8
  32. eodag/plugins/crunch/filter_overlap.py +9 -11
  33. eodag/plugins/crunch/filter_property.py +10 -10
  34. eodag/plugins/download/aws.py +181 -105
  35. eodag/plugins/download/base.py +49 -67
  36. eodag/plugins/download/creodias_s3.py +40 -2
  37. eodag/plugins/download/http.py +247 -223
  38. eodag/plugins/download/s3rest.py +29 -28
  39. eodag/plugins/manager.py +176 -41
  40. eodag/plugins/search/__init__.py +6 -5
  41. eodag/plugins/search/base.py +123 -60
  42. eodag/plugins/search/build_search_result.py +1046 -355
  43. eodag/plugins/search/cop_marine.py +132 -39
  44. eodag/plugins/search/creodias_s3.py +19 -68
  45. eodag/plugins/search/csw.py +48 -8
  46. eodag/plugins/search/data_request_search.py +124 -23
  47. eodag/plugins/search/qssearch.py +531 -310
  48. eodag/plugins/search/stac_list_assets.py +85 -0
  49. eodag/plugins/search/static_stac_search.py +23 -24
  50. eodag/resources/ext_product_types.json +1 -1
  51. eodag/resources/product_types.yml +1295 -355
  52. eodag/resources/providers.yml +1819 -3010
  53. eodag/resources/stac.yml +3 -163
  54. eodag/resources/stac_api.yml +2 -2
  55. eodag/resources/user_conf_template.yml +115 -99
  56. eodag/rest/cache.py +2 -2
  57. eodag/rest/config.py +3 -4
  58. eodag/rest/constants.py +0 -1
  59. eodag/rest/core.py +157 -117
  60. eodag/rest/errors.py +181 -0
  61. eodag/rest/server.py +57 -339
  62. eodag/rest/stac.py +133 -581
  63. eodag/rest/types/collections_search.py +3 -3
  64. eodag/rest/types/eodag_search.py +41 -30
  65. eodag/rest/types/queryables.py +42 -32
  66. eodag/rest/types/stac_search.py +15 -16
  67. eodag/rest/utils/__init__.py +14 -21
  68. eodag/rest/utils/cql_evaluate.py +6 -6
  69. eodag/rest/utils/rfc3339.py +2 -2
  70. eodag/types/__init__.py +153 -32
  71. eodag/types/bbox.py +2 -2
  72. eodag/types/download_args.py +4 -4
  73. eodag/types/queryables.py +183 -73
  74. eodag/types/search_args.py +6 -6
  75. eodag/types/whoosh.py +127 -3
  76. eodag/utils/__init__.py +228 -106
  77. eodag/utils/exceptions.py +47 -26
  78. eodag/utils/import_system.py +2 -2
  79. eodag/utils/logging.py +37 -77
  80. eodag/utils/repr.py +65 -6
  81. eodag/utils/requests.py +13 -15
  82. eodag/utils/rest.py +2 -2
  83. eodag/utils/s3.py +231 -0
  84. eodag/utils/stac_reader.py +11 -11
  85. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/METADATA +81 -81
  86. eodag-3.1.0.dist-info/RECORD +113 -0
  87. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
  88. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +5 -2
  89. eodag/resources/constraints/climate-dt.json +0 -13
  90. eodag/resources/constraints/extremes-dt.json +0 -8
  91. eodag/utils/constraints.py +0 -244
  92. eodag-3.0.0b3.dist-info/RECORD +0 -110
  93. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/LICENSE +0 -0
  94. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/top_level.txt +0 -0
@@ -15,7 +15,7 @@
15
15
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
- from typing import Any, Dict, Optional
18
+ from typing import Any, Optional
19
19
 
20
20
  from pydantic import (
21
21
  BaseModel,
@@ -39,6 +39,6 @@ class CollectionsSearchRequest(BaseModel):
39
39
  constellation: Optional[str] = Field(default=None)
40
40
 
41
41
  @model_serializer(mode="wrap")
42
- def _serialize(self, handler: SerializerFunctionWrapHandler) -> Dict[str, Any]:
43
- dumped: Dict[str, Any] = handler(self)
42
+ def _serialize(self, handler: SerializerFunctionWrapHandler) -> dict[str, Any]:
43
+ dumped: dict[str, Any] = handler(self)
44
44
  return {EODAGSearch.to_eodag(k): v for k, v in dumped.items()}
@@ -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, Dict, List, Optional, Tuple, Union, cast
20
+ from typing import TYPE_CHECKING, Any, Optional, Union, cast
21
21
 
22
22
  from pydantic import (
23
23
  AliasChoices,
@@ -52,7 +52,7 @@ if TYPE_CHECKING:
52
52
  from typing_extensions import Self
53
53
 
54
54
  Geometry = Union[
55
- Dict[str, Any],
55
+ dict[str, Any],
56
56
  Point,
57
57
  MultiPoint,
58
58
  LineString,
@@ -73,8 +73,8 @@ class EODAGSearch(BaseModel):
73
73
 
74
74
  productType: Optional[str] = Field(None, alias="collections", validate_default=True)
75
75
  provider: Optional[str] = Field(None)
76
- ids: Optional[List[str]] = Field(None)
77
- id: Optional[List[str]] = Field(
76
+ ids: Optional[list[str]] = Field(None)
77
+ id: Optional[list[str]] = Field(
78
78
  None, alias="ids"
79
79
  ) # TODO: remove when updating queryables
80
80
  geom: Optional[Geometry] = Field(None, alias="geometry")
@@ -101,7 +101,7 @@ class EODAGSearch(BaseModel):
101
101
  orbitNumber: Optional[int] = Field(None, alias="sat:absolute_orbit")
102
102
  # TODO: colision in property name. Need to handle "sar:product_type"
103
103
  sensorMode: Optional[str] = Field(None, alias="sar:instrument_mode")
104
- polarizationChannels: Optional[List[str]] = Field(None, alias="sar:polarizations")
104
+ polarizationChannels: Optional[list[str]] = Field(None, alias="sar:polarizations")
105
105
  dopplerFrequency: Optional[str] = Field(None, alias="sar:frequency_band")
106
106
  doi: Optional[str] = Field(None, alias="sci:doi")
107
107
  illuminationElevationAngle: Optional[float] = Field(
@@ -110,17 +110,10 @@ class EODAGSearch(BaseModel):
110
110
  illuminationAzimuthAngle: Optional[float] = Field(None, alias="view:sun_azimuth")
111
111
  page: Optional[int] = Field(1)
112
112
  items_per_page: int = Field(DEFAULT_ITEMS_PER_PAGE, alias="limit")
113
- sort_by: Optional[List[Tuple[str, str]]] = Field(None, alias="sortby")
113
+ sort_by: Optional[list[tuple[str, str]]] = Field(None, alias="sortby")
114
114
  raise_errors: bool = False
115
115
 
116
- _to_eodag_map: Dict[str, str]
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
116
+ _to_eodag_map: dict[str, str]
124
117
 
125
118
  @model_validator(mode="after")
126
119
  def remove_timeFromAscendingNode(self) -> Self: # pylint: disable=invalid-name
@@ -136,7 +129,7 @@ class EODAGSearch(BaseModel):
136
129
  if not self.__pydantic_extra__:
137
130
  return self
138
131
 
139
- keys_to_update: Dict[str, str] = {}
132
+ keys_to_update: dict[str, str] = {}
140
133
  for key in self.__pydantic_extra__.keys():
141
134
  if key.startswith("unk:"):
142
135
  keys_to_update[key] = key[len("unk:") :]
@@ -152,7 +145,7 @@ class EODAGSearch(BaseModel):
152
145
 
153
146
  @model_validator(mode="before")
154
147
  @classmethod
155
- def remove_keys(cls, values: Dict[str, Any]) -> Dict[str, Any]:
148
+ def remove_keys(cls, values: dict[str, Any]) -> dict[str, Any]:
156
149
  """Remove 'datetime', 'crunch', 'intersects', and 'bbox' keys"""
157
150
  for key in ["datetime", "crunch", "intersects", "bbox", "filter_lang"]:
158
151
  values.pop(key, None)
@@ -161,8 +154,8 @@ class EODAGSearch(BaseModel):
161
154
  @model_validator(mode="before")
162
155
  @classmethod
163
156
  def parse_collections(
164
- cls, values: Dict[str, Any], info: ValidationInfo
165
- ) -> Dict[str, Any]:
157
+ cls, values: dict[str, Any], info: ValidationInfo
158
+ ) -> dict[str, Any]:
166
159
  """convert collections to productType"""
167
160
 
168
161
  if collections := values.pop("collections", None):
@@ -179,7 +172,7 @@ class EODAGSearch(BaseModel):
179
172
 
180
173
  @model_validator(mode="before")
181
174
  @classmethod
182
- def parse_query(cls, values: Dict[str, Any]) -> Dict[str, Any]:
175
+ def parse_query(cls, values: dict[str, Any]) -> dict[str, Any]:
183
176
  """
184
177
  Convert a STAC query parameter filter with the "eq" operator to a dict.
185
178
  """
@@ -197,9 +190,9 @@ class EODAGSearch(BaseModel):
197
190
  if not query:
198
191
  return values
199
192
 
200
- query_props: Dict[str, Any] = {}
201
- errors: List[InitErrorDetails] = []
202
- for property_name, conditions in cast(Dict[str, Any], query).items():
193
+ query_props: dict[str, Any] = {}
194
+ errors: list[InitErrorDetails] = []
195
+ for property_name, conditions in cast(dict[str, Any], query).items():
203
196
  # Remove the prefix "properties." if present
204
197
  prop = property_name.replace("properties.", "", 1)
205
198
 
@@ -212,7 +205,7 @@ class EODAGSearch(BaseModel):
212
205
  continue
213
206
 
214
207
  # Retrieve the operator and its value
215
- operator, value = next(iter(cast(Dict[str, Any], conditions).items()))
208
+ operator, value = next(iter(cast(dict[str, Any], conditions).items()))
216
209
 
217
210
  # Validate the operator
218
211
  # only eq, in and lte are allowed
@@ -246,7 +239,7 @@ class EODAGSearch(BaseModel):
246
239
 
247
240
  @model_validator(mode="before")
248
241
  @classmethod
249
- def parse_cql(cls, values: Dict[str, Any]) -> Dict[str, Any]:
242
+ def parse_cql(cls, values: dict[str, Any]) -> dict[str, Any]:
250
243
  """
251
244
  Process cql2 filter
252
245
  """
@@ -263,7 +256,7 @@ class EODAGSearch(BaseModel):
263
256
  if not filter_:
264
257
  return values
265
258
 
266
- errors: List[InitErrorDetails] = []
259
+ errors: list[InitErrorDetails] = []
267
260
  try:
268
261
  parsing_result = EodagEvaluator().evaluate(parse_json(filter_)) # type: ignore
269
262
  except (ValueError, NotImplementedError) as e:
@@ -278,7 +271,7 @@ class EODAGSearch(BaseModel):
278
271
  title=cls.__name__, line_errors=errors
279
272
  )
280
273
 
281
- cql_args: Dict[str, Any] = cast(Dict[str, Any], parsing_result)
274
+ cql_args: dict[str, Any] = cast(dict[str, Any], parsing_result)
282
275
 
283
276
  invalid_keys = {
284
277
  "collections": 'Use "collection" instead of "collections"',
@@ -305,7 +298,7 @@ class EODAGSearch(BaseModel):
305
298
 
306
299
  @field_validator("instrument", mode="before")
307
300
  @classmethod
308
- def join_instruments(cls, v: Union[str, List[str]]) -> str:
301
+ def join_instruments(cls, v: Union[str, list[str]]) -> str:
309
302
  """convert instruments to instrument"""
310
303
  if isinstance(v, list):
311
304
  return ",".join(v)
@@ -315,8 +308,8 @@ class EODAGSearch(BaseModel):
315
308
  @classmethod
316
309
  def parse_sortby(
317
310
  cls,
318
- sortby_post_params: List[Dict[str, str]],
319
- ) -> List[Tuple[str, str]]:
311
+ sortby_post_params: list[dict[str, str]],
312
+ ) -> list[tuple[str, str]]:
320
313
  """
321
314
  Convert STAC POST sortby to EODAG sort_by
322
315
  """
@@ -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, Optional, Union
21
21
 
22
22
  from pydantic import (
23
23
  BaseModel,
@@ -25,17 +25,18 @@ from pydantic import (
25
25
  Field,
26
26
  SerializationInfo,
27
27
  SerializerFunctionWrapHandler,
28
- computed_field,
28
+ field_validator,
29
29
  model_serializer,
30
+ model_validator,
30
31
  )
31
32
 
32
33
  from eodag.rest.types.eodag_search import EODAGSearch
33
34
  from eodag.rest.utils.rfc3339 import str_to_interval
34
35
  from eodag.types import python_field_definition_to_json
35
- from eodag.utils import Annotated
36
36
 
37
37
  if TYPE_CHECKING:
38
38
  from pydantic.fields import FieldInfo
39
+ from typing_extensions import Self
39
40
 
40
41
 
41
42
  class QueryablesGetParams(BaseModel):
@@ -43,29 +44,39 @@ class QueryablesGetParams(BaseModel):
43
44
 
44
45
  collection: Optional[str] = Field(default=None, serialization_alias="productType")
45
46
  datetime: Optional[str] = Field(default=None)
47
+ start_datetime: Optional[str] = Field(default=None)
48
+ end_datetime: Optional[str] = Field(default=None)
46
49
 
47
50
  model_config = ConfigDict(extra="allow", frozen=True)
48
51
 
49
52
  @model_serializer(mode="wrap")
50
- def _serialize(self, handler: SerializerFunctionWrapHandler) -> Dict[str, Any]:
51
- dumped: Dict[str, Any] = handler(self)
53
+ def _serialize(self, handler: SerializerFunctionWrapHandler) -> dict[str, Any]:
54
+ dumped: dict[str, Any] = handler(self)
52
55
  return {EODAGSearch.to_eodag(k): v for k, v in dumped.items()}
53
56
 
54
- # use [prop-decorator] mypy error code when mypy==1.12 is released
55
- @computed_field # type: ignore[misc]
56
- @property
57
- def start_datetime(self) -> Optional[str]:
58
- """Extract start_datetime property from datetime"""
59
- start = str_to_interval(self.datetime)[0]
60
- return start.strftime("%Y-%m-%dT%H:%M:%SZ") if start else None
57
+ @field_validator("datetime", "start_datetime", "end_datetime", mode="before")
58
+ def validate_datetime(cls, value: Any) -> Optional[str]:
59
+ """datetime, start_datetime and end_datetime must be a string"""
60
+ if isinstance(value, list):
61
+ return value[0]
61
62
 
62
- # use [prop-decorator] mypy error code when mypy==1.12 is released
63
- @computed_field # type: ignore[misc]
64
- @property
65
- def end_datetime(self) -> Optional[str]:
66
- """Extract end_datetime property from datetime"""
67
- end = str_to_interval(self.datetime)[1]
68
- return end.strftime("%Y-%m-%dT%H:%M:%SZ") if end else None
63
+ return value
64
+
65
+ @model_validator(mode="after")
66
+ def compute_datetimes(self: Self) -> Self:
67
+ """Start datetime must be a string"""
68
+ if not self.datetime:
69
+ return self
70
+
71
+ start, end = str_to_interval(self.datetime)
72
+
73
+ if not self.start_datetime and start:
74
+ self.start_datetime = start.strftime("%Y-%m-%dT%H:%M:%SZ")
75
+
76
+ if not self.end_datetime and end:
77
+ self.end_datetime = end.strftime("%Y-%m-%dT%H:%M:%SZ")
78
+
79
+ return self
69
80
 
70
81
 
71
82
  class StacQueryableProperty(BaseModel):
@@ -78,12 +89,12 @@ class StacQueryableProperty(BaseModel):
78
89
 
79
90
  description: str
80
91
  ref: Optional[str] = Field(default=None, serialization_alias="$ref")
81
- type: Optional[Union[str, List[str]]] = None
82
- enum: Optional[List[Any]] = None
92
+ type: Optional[Union[str, list[str]]] = None
93
+ enum: Optional[list[Any]] = None
83
94
  value: Optional[Any] = None
84
- min: Optional[Union[int, List[Union[int, None]]]] = None
85
- max: Optional[Union[int, List[Union[int, None]]]] = None
86
- oneOf: Optional[List[Any]] = None
95
+ min: Optional[Union[int, list[Union[int, None]]]] = None
96
+ max: Optional[Union[int, list[Union[int, None]]]] = None
97
+ oneOf: Optional[list[Any]] = None
87
98
  items: Optional[Any] = None
88
99
 
89
100
  @classmethod
@@ -105,7 +116,7 @@ class StacQueryableProperty(BaseModel):
105
116
  _: SerializationInfo,
106
117
  ):
107
118
  """Remove none value property fields during serialization"""
108
- props: Dict[str, Any] = handler(self)
119
+ props: dict[str, Any] = handler(self)
109
120
  return {k: v for k, v in props.items() if v is not None}
110
121
 
111
122
 
@@ -131,15 +142,13 @@ class StacQueryables(BaseModel):
131
142
  description: str = Field(
132
143
  default="Queryable names for the EODAG STAC API Item Search filter."
133
144
  )
134
- 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
- ),
145
+ default_properties: ClassVar[dict[str, StacQueryableProperty]] = {
139
146
  "collection": StacQueryableProperty(
140
147
  description="Collection",
141
148
  ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/collection",
142
- ),
149
+ )
150
+ }
151
+ possible_properties: ClassVar[dict[str, StacQueryableProperty]] = {
143
152
  "geometry": StacQueryableProperty(
144
153
  description="Geometry",
145
154
  ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/geometry",
@@ -155,7 +164,8 @@ class StacQueryables(BaseModel):
155
164
  items={"type": "number"},
156
165
  ),
157
166
  }
158
- properties: Dict[str, StacQueryableProperty] = Field()
167
+ properties: dict[str, Any] = Field()
168
+ required: Optional[list[str]] = Field(None)
159
169
  additional_properties: bool = Field(
160
170
  default=True, serialization_alias="additionalProperties"
161
171
  )
@@ -19,7 +19,7 @@
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 TYPE_CHECKING, Annotated, Any, Literal, Optional, Union
23
23
 
24
24
  import geojson
25
25
  from pydantic import (
@@ -43,7 +43,6 @@ from shapely.geometry import (
43
43
  shape,
44
44
  )
45
45
  from shapely.geometry.base import GEOMETRY_TYPES, BaseGeometry
46
- from typing_extensions import Annotated
47
46
 
48
47
  from eodag.rest.utils.rfc3339 import rfc3339_str_to_datetime, str_to_interval
49
48
  from eodag.utils.exceptions import ValidationError
@@ -54,8 +53,8 @@ if TYPE_CHECKING:
54
53
  NumType = Union[float, int]
55
54
 
56
55
  BBox = Union[
57
- Tuple[NumType, NumType, NumType, NumType],
58
- Tuple[NumType, NumType, NumType, NumType, NumType, NumType],
56
+ tuple[NumType, NumType, NumType, NumType],
57
+ tuple[NumType, NumType, NumType, NumType, NumType, NumType],
59
58
  ]
60
59
 
61
60
  Geometry = Union[
@@ -97,8 +96,8 @@ class SearchPostRequest(BaseModel):
97
96
  model_config = ConfigDict(populate_by_name=True, arbitrary_types_allowed=True)
98
97
 
99
98
  provider: Optional[str] = None
100
- collections: Optional[List[str]] = None
101
- ids: Optional[List[str]] = None
99
+ collections: Optional[list[str]] = None
100
+ ids: Optional[list[str]] = None
102
101
  bbox: Optional[BBox] = None
103
102
  intersects: Optional[Geometry] = None
104
103
  datetime: Optional[str] = None
@@ -108,21 +107,21 @@ class SearchPostRequest(BaseModel):
108
107
  page: Optional[PositiveInt] = Field( # type: ignore
109
108
  default=None, description="Page number, must be a positive integer."
110
109
  )
111
- query: Optional[Dict[str, Any]] = None
112
- filter: Optional[Dict[str, Any]] = None
110
+ query: Optional[dict[str, Any]] = None
111
+ filter: Optional[dict[str, Any]] = None
113
112
  filter_lang: Optional[str] = Field(
114
113
  default=None,
115
114
  alias="filter-lang",
116
115
  description="The language used for filtering.",
117
116
  validate_default=True,
118
117
  )
119
- sortby: Optional[List[SortBy]] = None
118
+ sortby: Optional[list[SortBy]] = None
120
119
  crunch: Optional[str] = None
121
120
 
122
121
  @field_serializer("intersects")
123
122
  def serialize_intersects(
124
123
  self, intersects: Optional[Geometry]
125
- ) -> Optional[Dict[str, Any]]:
124
+ ) -> Optional[dict[str, Any]]:
126
125
  """Serialize intersects from shapely to a proper dict"""
127
126
  if intersects:
128
127
  return geojson.loads(geojson.dumps(intersects)) # type: ignore
@@ -141,7 +140,7 @@ class SearchPostRequest(BaseModel):
141
140
 
142
141
  @model_validator(mode="before")
143
142
  @classmethod
144
- def only_one_spatial(cls, values: Dict[str, Any]) -> Dict[str, Any]:
143
+ def only_one_spatial(cls, values: dict[str, Any]) -> dict[str, Any]:
145
144
  """Check bbox and intersects are not both supplied."""
146
145
  if "intersects" in values and "bbox" in values:
147
146
  raise ValueError("intersects and bbox parameters are mutually exclusive")
@@ -161,7 +160,7 @@ class SearchPostRequest(BaseModel):
161
160
 
162
161
  @field_validator("ids", "collections", mode="before")
163
162
  @classmethod
164
- def str_to_str_list(cls, v: Union[str, List[str]]) -> List[str]:
163
+ def str_to_str_list(cls, v: Union[str, list[str]]) -> list[str]:
165
164
  """Convert ids and collections strings to list of strings"""
166
165
  if isinstance(v, str):
167
166
  return [i.strip() for i in v.split(",")]
@@ -169,7 +168,7 @@ class SearchPostRequest(BaseModel):
169
168
 
170
169
  @field_validator("intersects", mode="before")
171
170
  @classmethod
172
- def validate_intersects(cls, v: Union[Dict[str, Any], Geometry]) -> Geometry:
171
+ def validate_intersects(cls, v: Union[dict[str, Any], Geometry]) -> Geometry:
173
172
  """Verify format of intersects"""
174
173
  if isinstance(v, BaseGeometry):
175
174
  return v
@@ -215,7 +214,7 @@ class SearchPostRequest(BaseModel):
215
214
  # Single date is interpreted as end date
216
215
  values = ["..", v]
217
216
 
218
- dates: List[str] = []
217
+ dates: list[str] = []
219
218
  for value in values:
220
219
  if value == ".." or value == "":
221
220
  dates.append("..")
@@ -258,13 +257,13 @@ class SearchPostRequest(BaseModel):
258
257
 
259
258
  def sortby2list(
260
259
  v: Optional[str],
261
- ) -> Optional[List[SortBy]]:
260
+ ) -> Optional[list[SortBy]]:
262
261
  """
263
262
  Convert sortby filter parameter GET syntax to POST syntax
264
263
  """
265
264
  if not v:
266
265
  return None
267
- sortby: List[SortBy] = []
266
+ sortby: list[SortBy] = []
268
267
  for sortby_param in v.split(","):
269
268
  sortby_param = sortby_param.strip()
270
269
  direction: Direction = "desc" if sortby_param.startswith("-") else "asc"
@@ -23,17 +23,7 @@ import logging
23
23
  import os
24
24
  from io import BufferedReader
25
25
  from shutil import make_archive, rmtree
26
- from typing import (
27
- TYPE_CHECKING,
28
- Any,
29
- Callable,
30
- Dict,
31
- Iterator,
32
- List,
33
- NamedTuple,
34
- Optional,
35
- Union,
36
- )
26
+ from typing import TYPE_CHECKING, Any, Callable, Iterator, NamedTuple, Optional, Union
37
27
  from urllib.parse import unquote_plus, urlencode
38
28
 
39
29
  import orjson
@@ -55,12 +45,15 @@ __all__ = ["get_date", "get_datetime"]
55
45
 
56
46
  logger = logging.getLogger("eodag.rest.utils")
57
47
 
48
+ # Path of the liveness endpoint
49
+ LIVENESS_PROBE_PATH = "/_mgmt/ping"
50
+
58
51
 
59
52
  class Cruncher(NamedTuple):
60
53
  """Type hinted Cruncher namedTuple"""
61
54
 
62
55
  clazz: Callable[..., Any]
63
- config_params: List[str]
56
+ config_params: list[str]
64
57
 
65
58
 
66
59
  crunchers = {
@@ -87,19 +80,19 @@ def format_pydantic_error(e: pydanticValidationError) -> str:
87
80
 
88
81
  def is_dict_str_any(var: Any) -> bool:
89
82
  """Verify whether the variable is of type dict[str, Any]"""
90
- if isinstance(var, Dict):
83
+ if isinstance(var, dict):
91
84
  return all(isinstance(k, str) for k in var.keys()) # type: ignore
92
85
  return False
93
86
 
94
87
 
95
- def str2list(v: Optional[str]) -> Optional[List[str]]:
88
+ def str2list(v: Optional[str]) -> Optional[list[str]]:
96
89
  """Convert string to list base on , delimiter."""
97
90
  if v:
98
91
  return v.split(",")
99
92
  return None
100
93
 
101
94
 
102
- def str2json(k: str, v: Optional[str] = None) -> Optional[Dict[str, Any]]:
95
+ def str2json(k: str, v: Optional[str] = None) -> Optional[dict[str, Any]]:
103
96
  """decoding a URL parameter and then parsing it as JSON."""
104
97
  if not v:
105
98
  return None
@@ -109,25 +102,25 @@ def str2json(k: str, v: Optional[str] = None) -> Optional[Dict[str, Any]]:
109
102
  raise ValidationError(f"{k}: Incorrect JSON object") from e
110
103
 
111
104
 
112
- def flatten_list(nested_list: Union[Any, List[Any]]) -> List[Any]:
105
+ def flatten_list(nested_list: Union[Any, list[Any]]) -> list[Any]:
113
106
  """Flatten a nested list structure into a single list."""
114
107
  if not isinstance(nested_list, list):
115
108
  return [nested_list]
116
109
  else:
117
- flattened: List[Any] = []
110
+ flattened: list[Any] = []
118
111
  for element in nested_list:
119
112
  flattened.extend(flatten_list(element))
120
113
  return flattened
121
114
 
122
115
 
123
- def list_to_str_list(input_list: List[Any]) -> List[str]:
116
+ def list_to_str_list(input_list: list[Any]) -> list[str]:
124
117
  """Attempt to convert a list of any type to a list of strings."""
125
118
  try:
126
119
  # Try to convert each element to a string
127
120
  return [str(element) for element in input_list]
128
121
  except Exception as e:
129
122
  # Raise an exception if any element cannot be converted
130
- raise TypeError(f"Failed to convert to List[str]: {e}") from e
123
+ raise TypeError(f"Failed to convert to list[str]: {e}") from e
131
124
 
132
125
 
133
126
  def get_next_link(
@@ -135,7 +128,7 @@ def get_next_link(
135
128
  search_request: SearchPostRequest,
136
129
  total_results: Optional[int],
137
130
  items_per_page: int,
138
- ) -> Optional[Dict[str, Any]]:
131
+ ) -> Optional[dict[str, Any]]:
139
132
  """Generate next link URL and body"""
140
133
  body = search_request.model_dump(exclude_none=True)
141
134
  if "bbox" in body:
@@ -156,7 +149,7 @@ def get_next_link(
156
149
  params["page"] = str(page + 1)
157
150
  url += f"?{urlencode(params)}"
158
151
 
159
- next: Dict[str, Any] = {
152
+ next: dict[str, Any] = {
160
153
  "rel": "next",
161
154
  "href": url,
162
155
  "title": "Next page",
@@ -16,13 +16,13 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
  from datetime import datetime as dt
19
- from typing import Any, Dict, List, Optional, Tuple, Union
19
+ from typing import Any, Optional, Union
20
20
 
21
21
  from pygeofilter import ast
22
22
  from pygeofilter.backends.evaluator import Evaluator, handle
23
23
  from pygeofilter.values import Geometry, Interval
24
24
 
25
- simpleNode = Union[ast.Attribute, str, int, complex, float, List[Any], Tuple[Any, ...]]
25
+ simpleNode = Union[ast.Attribute, str, int, complex, float, list[Any], tuple[Any, ...]]
26
26
 
27
27
 
28
28
  class EodagEvaluator(Evaluator):
@@ -36,7 +36,7 @@ class EodagEvaluator(Evaluator):
36
36
  return node
37
37
 
38
38
  @handle(Geometry)
39
- def spatial(self, node: Geometry) -> Dict[str, Any]:
39
+ def spatial(self, node: Geometry) -> dict[str, Any]:
40
40
  """handle geometry"""
41
41
  return node.geometry
42
42
 
@@ -46,7 +46,7 @@ class EodagEvaluator(Evaluator):
46
46
  return node.strftime("%Y-%m-%dT%H:%M:%SZ")
47
47
 
48
48
  @handle(Interval)
49
- def interval(self, _, *interval: Any) -> List[Any]:
49
+ def interval(self, _, *interval: Any) -> list[Any]:
50
50
  """handle datetime interval"""
51
51
  return list(interval)
52
52
 
@@ -60,7 +60,7 @@ class EodagEvaluator(Evaluator):
60
60
  )
61
61
  def predicate(
62
62
  self, node: ast.Predicate, lhs: Any, rhs: Any
63
- ) -> Optional[Dict[str, Any]]:
63
+ ) -> Optional[dict[str, Any]]:
64
64
  """
65
65
  Handle predicates
66
66
  Verify the property is first attribute in each predicate
@@ -114,6 +114,6 @@ class EodagEvaluator(Evaluator):
114
114
  return {lhs.name: list(rhs)}
115
115
 
116
116
  @handle(ast.And)
117
- def combination(self, _, lhs: Dict[str, str], rhs: Dict[str, str]):
117
+ def combination(self, _, lhs: dict[str, str], rhs: dict[str, str]):
118
118
  """handle combinations"""
119
119
  return {**lhs, **rhs}
@@ -16,14 +16,14 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
  import datetime
19
- from typing import Optional, Tuple
19
+ from typing import Optional
20
20
 
21
21
  from eodag.utils.rest import rfc3339_str_to_datetime
22
22
 
23
23
 
24
24
  def str_to_interval(
25
25
  interval: Optional[str],
26
- ) -> Tuple[Optional[datetime.datetime], Optional[datetime.datetime]]:
26
+ ) -> tuple[Optional[datetime.datetime], Optional[datetime.datetime]]:
27
27
  """Extract a tuple of datetimes from an interval string.
28
28
 
29
29
  Interval strings are defined by