eodag 3.1.0b1__py3-none-any.whl → 3.2.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 (85) hide show
  1. eodag/api/core.py +69 -63
  2. eodag/api/product/_assets.py +49 -13
  3. eodag/api/product/_product.py +41 -30
  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 +85 -79
  10. eodag/api/search_result.py +13 -23
  11. eodag/cli.py +4 -4
  12. eodag/config.py +77 -80
  13. eodag/plugins/apis/base.py +1 -1
  14. eodag/plugins/apis/ecmwf.py +12 -15
  15. eodag/plugins/apis/usgs.py +12 -11
  16. eodag/plugins/authentication/aws_auth.py +16 -13
  17. eodag/plugins/authentication/base.py +5 -3
  18. eodag/plugins/authentication/header.py +3 -3
  19. eodag/plugins/authentication/keycloak.py +4 -4
  20. eodag/plugins/authentication/oauth.py +7 -3
  21. eodag/plugins/authentication/openid_connect.py +20 -14
  22. eodag/plugins/authentication/sas_auth.py +4 -4
  23. eodag/plugins/authentication/token.py +7 -7
  24. eodag/plugins/authentication/token_exchange.py +1 -1
  25. eodag/plugins/base.py +4 -4
  26. eodag/plugins/crunch/base.py +4 -4
  27. eodag/plugins/crunch/filter_date.py +4 -4
  28. eodag/plugins/crunch/filter_latest_intersect.py +6 -6
  29. eodag/plugins/crunch/filter_latest_tpl_name.py +7 -7
  30. eodag/plugins/crunch/filter_overlap.py +4 -4
  31. eodag/plugins/crunch/filter_property.py +4 -4
  32. eodag/plugins/download/aws.py +137 -77
  33. eodag/plugins/download/base.py +8 -17
  34. eodag/plugins/download/creodias_s3.py +2 -2
  35. eodag/plugins/download/http.py +30 -32
  36. eodag/plugins/download/s3rest.py +5 -4
  37. eodag/plugins/manager.py +10 -20
  38. eodag/plugins/search/__init__.py +6 -5
  39. eodag/plugins/search/base.py +38 -42
  40. eodag/plugins/search/build_search_result.py +286 -336
  41. eodag/plugins/search/cop_marine.py +22 -12
  42. eodag/plugins/search/creodias_s3.py +8 -78
  43. eodag/plugins/search/csw.py +11 -11
  44. eodag/plugins/search/data_request_search.py +19 -18
  45. eodag/plugins/search/qssearch.py +84 -151
  46. eodag/plugins/search/stac_list_assets.py +85 -0
  47. eodag/plugins/search/static_stac_search.py +4 -4
  48. eodag/resources/ext_product_types.json +1 -1
  49. eodag/resources/product_types.yml +848 -398
  50. eodag/resources/providers.yml +1038 -1115
  51. eodag/resources/stac_api.yml +2 -2
  52. eodag/resources/user_conf_template.yml +10 -9
  53. eodag/rest/cache.py +2 -2
  54. eodag/rest/config.py +3 -3
  55. eodag/rest/core.py +24 -24
  56. eodag/rest/errors.py +5 -5
  57. eodag/rest/server.py +3 -11
  58. eodag/rest/stac.py +41 -38
  59. eodag/rest/types/collections_search.py +3 -3
  60. eodag/rest/types/eodag_search.py +23 -23
  61. eodag/rest/types/queryables.py +40 -28
  62. eodag/rest/types/stac_search.py +15 -25
  63. eodag/rest/utils/__init__.py +11 -21
  64. eodag/rest/utils/cql_evaluate.py +6 -6
  65. eodag/rest/utils/rfc3339.py +2 -2
  66. eodag/types/__init__.py +97 -29
  67. eodag/types/bbox.py +2 -2
  68. eodag/types/download_args.py +2 -2
  69. eodag/types/queryables.py +5 -2
  70. eodag/types/search_args.py +4 -4
  71. eodag/types/whoosh.py +1 -3
  72. eodag/utils/__init__.py +82 -41
  73. eodag/utils/exceptions.py +2 -2
  74. eodag/utils/import_system.py +2 -2
  75. eodag/utils/requests.py +2 -2
  76. eodag/utils/rest.py +2 -2
  77. eodag/utils/s3.py +231 -0
  78. eodag/utils/stac_reader.py +10 -10
  79. {eodag-3.1.0b1.dist-info → eodag-3.2.0.dist-info}/METADATA +12 -10
  80. eodag-3.2.0.dist-info/RECORD +113 -0
  81. {eodag-3.1.0b1.dist-info → eodag-3.2.0.dist-info}/WHEEL +1 -1
  82. {eodag-3.1.0b1.dist-info → eodag-3.2.0.dist-info}/entry_points.txt +1 -0
  83. eodag-3.1.0b1.dist-info/RECORD +0 -108
  84. {eodag-3.1.0b1.dist-info → eodag-3.2.0.dist-info/licenses}/LICENSE +0 -0
  85. {eodag-3.1.0b1.dist-info → eodag-3.2.0.dist-info}/top_level.txt +0 -0
eodag/types/__init__.py CHANGED
@@ -22,11 +22,9 @@ from __future__ import annotations
22
22
  from typing import (
23
23
  Annotated,
24
24
  Any,
25
- Dict,
26
- List,
27
25
  Literal,
28
26
  Optional,
29
- Tuple,
27
+ Type,
30
28
  TypedDict,
31
29
  Union,
32
30
  get_args,
@@ -34,15 +32,18 @@ from typing import (
34
32
  )
35
33
 
36
34
  from annotated_types import Gt, Lt
37
- from pydantic import BaseModel, Field, create_model
35
+ from pydantic import BaseModel, ConfigDict, Field, create_model
36
+ from pydantic.annotated_handlers import GetJsonSchemaHandler
38
37
  from pydantic.fields import FieldInfo
38
+ from pydantic.json_schema import JsonSchemaValue
39
+ from pydantic_core import CoreSchema
39
40
 
40
41
  from eodag.utils import copy_deepcopy
41
42
  from eodag.utils.exceptions import ValidationError
42
43
 
43
44
  # Types mapping from JSON Schema and OpenAPI 3.1.0 specifications to Python
44
45
  # See https://spec.openapis.org/oas/v3.1.0#data-types
45
- JSON_TYPES_MAPPING: Dict[str, type] = {
46
+ JSON_TYPES_MAPPING: dict[str, type] = {
46
47
  "boolean": bool,
47
48
  "integer": int,
48
49
  "number": float,
@@ -53,7 +54,7 @@ JSON_TYPES_MAPPING: Dict[str, type] = {
53
54
  }
54
55
 
55
56
 
56
- def json_type_to_python(json_type: Union[str, List[str]]) -> type:
57
+ def json_type_to_python(json_type: Union[str, list[str]]) -> type:
57
58
  """Get python type from json type https://spec.openapis.org/oas/v3.1.0#data-types
58
59
 
59
60
  >>> json_type_to_python("number")
@@ -70,7 +71,7 @@ def json_type_to_python(json_type: Union[str, List[str]]) -> type:
70
71
  return type(None)
71
72
 
72
73
 
73
- def _get_min_or_max(type_info: Union[Lt, Gt, Any]) -> Tuple[str, Any]:
74
+ def _get_min_or_max(type_info: Union[Lt, Gt, Any]) -> tuple[str, Any]:
74
75
  """Checks if the value from an Annotated object is a minimum or maximum
75
76
 
76
77
  :param type_info: info from Annotated
@@ -85,7 +86,7 @@ def _get_min_or_max(type_info: Union[Lt, Gt, Any]) -> Tuple[str, Any]:
85
86
 
86
87
  def _get_type_info_from_annotated(
87
88
  annotated_type: Annotated[type, Any],
88
- ) -> Dict[str, Any]:
89
+ ) -> dict[str, Any]:
89
90
  """Retrieves type information from an annotated object
90
91
 
91
92
  :param annotated_type: annotated object
@@ -108,7 +109,7 @@ def _get_type_info_from_annotated(
108
109
 
109
110
  def python_type_to_json(
110
111
  python_type: type,
111
- ) -> Optional[Union[str, List[Dict[str, Any]]]]:
112
+ ) -> Optional[Union[str, list[dict[str, Any]]]]:
112
113
  """Get json type from python https://spec.openapis.org/oas/v3.1.0#data-types
113
114
 
114
115
  >>> python_type_to_json(int)
@@ -149,7 +150,7 @@ def python_type_to_json(
149
150
 
150
151
 
151
152
  def json_field_definition_to_python(
152
- json_field_definition: Dict[str, Any],
153
+ json_field_definition: dict[str, Any],
153
154
  default_value: Optional[Any] = None,
154
155
  required: Optional[bool] = False,
155
156
  ) -> Annotated[Any, FieldInfo]:
@@ -186,7 +187,7 @@ def json_field_definition_to_python(
186
187
  if python_type in (list, set):
187
188
  items = json_field_definition.get("items", None)
188
189
  if isinstance(items, list):
189
- python_type = Tuple[ # type: ignore
190
+ python_type = tuple[ # type: ignore
190
191
  tuple(
191
192
  json_field_definition_to_python(item, required=required)
192
193
  for item in items
@@ -197,20 +198,28 @@ def json_field_definition_to_python(
197
198
 
198
199
  if enum:
199
200
  literal = Literal[tuple(sorted(enum))] # type: ignore
200
- python_type = List[literal] if python_type in (list, set) else literal # type: ignore
201
+ python_type = list[literal] if python_type in (list, set) else literal # type: ignore
201
202
 
202
203
  if "$ref" in json_field_definition:
203
204
  field_type_kwargs["json_schema_extra"] = {"$ref": json_field_definition["$ref"]}
204
205
 
205
- if not required or default_value:
206
- return Annotated[python_type, Field(default=default_value, **field_type_kwargs)]
207
- else:
208
- return Annotated[python_type, Field(..., **field_type_kwargs)]
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)]
209
218
 
210
219
 
211
220
  def python_field_definition_to_json(
212
221
  python_field_definition: Annotated[Any, FieldInfo],
213
- ) -> Dict[str, Any]:
222
+ ) -> dict[str, Any]:
214
223
  """Get json field definition from python `typing.Annotated`
215
224
 
216
225
  >>> from pydantic import Field
@@ -231,7 +240,7 @@ def python_field_definition_to_json(
231
240
  "%s must be an instance of Annotated" % python_field_definition
232
241
  )
233
242
 
234
- json_field_definition: Dict[str, Any] = dict()
243
+ json_field_definition: dict[str, Any] = dict()
235
244
 
236
245
  python_field_args = get_args(python_field_definition)
237
246
 
@@ -311,8 +320,8 @@ def python_field_definition_to_json(
311
320
 
312
321
 
313
322
  def model_fields_to_annotated(
314
- model_fields: Dict[str, FieldInfo],
315
- ) -> Dict[str, Annotated[Any, FieldInfo]]:
323
+ model_fields: dict[str, FieldInfo],
324
+ ) -> dict[str, Annotated[Any, FieldInfo]]:
316
325
  """Convert BaseModel.model_fields from FieldInfo to Annotated
317
326
 
318
327
  >>> from pydantic import create_model
@@ -326,7 +335,7 @@ def model_fields_to_annotated(
326
335
  :param model_fields: BaseModel.model_fields to convert
327
336
  :returns: Annotated tuple usable as create_model argument
328
337
  """
329
- annotated_model_fields: Dict[str, Annotated[Any, FieldInfo]] = dict()
338
+ annotated_model_fields: dict[str, Annotated[Any, FieldInfo]] = dict()
330
339
  for param, field_info in model_fields.items():
331
340
  field_type = field_info.annotation or type(None)
332
341
  new_field_info = copy_deepcopy(field_info)
@@ -335,26 +344,76 @@ def model_fields_to_annotated(
335
344
  return annotated_model_fields
336
345
 
337
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
+
338
373
  def annotated_dict_to_model(
339
- model_name: str, annotated_fields: Dict[str, Annotated[Any, FieldInfo]]
374
+ model_name: str, annotated_fields: dict[str, Annotated[Any, FieldInfo]]
340
375
  ) -> BaseModel:
341
376
  """Convert a dictionary of Annotated values to a Pydantic BaseModel.
342
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
+
343
396
  :param model_name: name of the model to be created
344
397
  :param annotated_fields: dict containing the parameters and annotated values that should become
345
398
  the properties of the model
346
399
  :returns: pydantic model
347
400
  """
348
- fields = {
349
- name: (field.__args__[0], field.__metadata__[0])
350
- for name, field in annotated_fields.items()
351
- }
352
- return create_model(
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(
353
410
  model_name,
411
+ __base__=BaseModelCustomJsonSchema,
354
412
  **fields, # type: ignore
355
- __config__={"arbitrary_types_allowed": True},
356
413
  )
357
414
 
415
+ return custom_model
416
+
358
417
 
359
418
  class ProviderSortables(TypedDict):
360
419
  """A class representing sortable parameter(s) of a provider and the allowed
@@ -364,5 +423,14 @@ class ProviderSortables(TypedDict):
364
423
  :param max_sort_params: (optional) The allowed maximum number of sortable(s) in a search request with the provider
365
424
  """
366
425
 
367
- sortables: List[str]
426
+ sortables: list[str]
368
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 Dict, List, Tuple, Union
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
- List[NumType], Tuple[NumType, NumType, NumType, NumType], Dict[str, NumType]
25
+ list[NumType], tuple[NumType, NumType, NumType, NumType], dict[str, NumType]
26
26
  ]
27
27
 
28
28
 
@@ -17,7 +17,7 @@
17
17
  # limitations under the License.
18
18
  from __future__ import annotations
19
19
 
20
- from typing import Dict, Optional, TypedDict, Union
20
+ from typing import Optional, TypedDict, Union
21
21
 
22
22
 
23
23
  class DownloadConf(TypedDict, total=False):
@@ -35,6 +35,6 @@ class DownloadConf(TypedDict, total=False):
35
35
  output_dir: str
36
36
  output_extension: Union[str, None]
37
37
  extract: bool
38
- dl_url_params: Dict[str, str]
38
+ dl_url_params: dict[str, str]
39
39
  delete_archive: bool
40
40
  asset: Optional[str]
eodag/types/queryables.py CHANGED
@@ -148,7 +148,7 @@ class Queryables(CommonQueryables):
148
148
  incidenceAngleVariation: Annotated[float, Field(None)]
149
149
 
150
150
 
151
- class QueryablesDict(UserDict):
151
+ class QueryablesDict(UserDict[str, Any]):
152
152
  """Class inheriting from UserDict which contains queryables with their annotated type;
153
153
 
154
154
  :param additional_properties: if additional properties (properties not given in EODAG config)
@@ -160,7 +160,10 @@ class QueryablesDict(UserDict):
160
160
  additional_information: str = Field("")
161
161
 
162
162
  def __init__(
163
- self, additional_properties: bool, additional_information: str = "", **kwargs
163
+ self,
164
+ additional_properties: bool = True,
165
+ additional_information: str = "",
166
+ **kwargs: Any,
164
167
  ):
165
168
  self.additional_properties = additional_properties
166
169
  self.additional_information = additional_information
@@ -17,7 +17,7 @@
17
17
  # limitations under the License.
18
18
  import re
19
19
  from datetime import datetime
20
- from typing import Annotated, Any, Dict, List, Optional, Tuple, Union, cast
20
+ from typing import Annotated, Any, Optional, Union, cast
21
21
 
22
22
  from annotated_types import MinLen
23
23
  from pydantic import BaseModel, ConfigDict, Field, conint, field_validator
@@ -31,10 +31,10 @@ 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]
34
- GeomArgs = Union[List[NumType], Tuple[NumType], Dict[str, NumType], str, BaseGeometry]
34
+ GeomArgs = Union[list[NumType], tuple[NumType], dict[str, NumType], str, BaseGeometry]
35
35
 
36
36
  PositiveInt = conint(gt=0)
37
- SortByList = Annotated[List[Tuple[str, str]], MinLen(1)]
37
+ SortByList = Annotated[list[tuple[str, str]], MinLen(1)]
38
38
 
39
39
 
40
40
  class SearchArgs(BaseModel):
@@ -48,7 +48,7 @@ class SearchArgs(BaseModel):
48
48
  start: Optional[str] = Field(None)
49
49
  end: Optional[str] = Field(None)
50
50
  geom: Optional[BaseGeometry] = Field(None)
51
- locations: Optional[Dict[str, str]] = Field(None)
51
+ locations: Optional[dict[str, str]] = Field(None)
52
52
  page: Optional[int] = Field(DEFAULT_PAGE, gt=0) # type: ignore
53
53
  items_per_page: Optional[PositiveInt] = Field(DEFAULT_ITEMS_PER_PAGE) # type: ignore
54
54
  sort_by: Optional[SortByList] = Field(None) # type: ignore
eodag/types/whoosh.py CHANGED
@@ -15,8 +15,6 @@
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 List
19
-
20
18
  from whoosh.fields import Schema
21
19
  from whoosh.index import _DEF_INDEX_NAME, FileIndex
22
20
  from whoosh.matching import NullMatcher
@@ -52,7 +50,7 @@ class EODAGQueryParser(QueryParser):
52
50
 
53
51
  def __init__(
54
52
  self,
55
- filters: List[str],
53
+ filters: list[str],
56
54
  schema: Schema,
57
55
  ):
58
56
  """