eodag 3.10.1__py3-none-any.whl → 4.0.0a2__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 (75) hide show
  1. eodag/__init__.py +6 -1
  2. eodag/api/collection.py +353 -0
  3. eodag/api/core.py +606 -641
  4. eodag/api/product/__init__.py +3 -3
  5. eodag/api/product/_product.py +74 -56
  6. eodag/api/product/drivers/__init__.py +4 -46
  7. eodag/api/product/drivers/base.py +0 -28
  8. eodag/api/product/metadata_mapping.py +178 -216
  9. eodag/api/search_result.py +156 -15
  10. eodag/cli.py +83 -403
  11. eodag/config.py +81 -51
  12. eodag/plugins/apis/base.py +2 -2
  13. eodag/plugins/apis/ecmwf.py +36 -25
  14. eodag/plugins/apis/usgs.py +55 -40
  15. eodag/plugins/authentication/base.py +1 -3
  16. eodag/plugins/crunch/filter_date.py +3 -3
  17. eodag/plugins/crunch/filter_latest_intersect.py +2 -2
  18. eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
  19. eodag/plugins/download/aws.py +46 -42
  20. eodag/plugins/download/base.py +13 -14
  21. eodag/plugins/download/http.py +65 -65
  22. eodag/plugins/manager.py +28 -29
  23. eodag/plugins/search/__init__.py +6 -4
  24. eodag/plugins/search/base.py +131 -80
  25. eodag/plugins/search/build_search_result.py +245 -173
  26. eodag/plugins/search/cop_marine.py +87 -56
  27. eodag/plugins/search/csw.py +47 -37
  28. eodag/plugins/search/qssearch.py +653 -429
  29. eodag/plugins/search/stac_list_assets.py +1 -1
  30. eodag/plugins/search/static_stac_search.py +43 -44
  31. eodag/resources/{product_types.yml → collections.yml} +2594 -2453
  32. eodag/resources/ext_collections.json +1 -1
  33. eodag/resources/ext_product_types.json +1 -1
  34. eodag/resources/providers.yml +2706 -2733
  35. eodag/resources/stac_provider.yml +50 -92
  36. eodag/resources/user_conf_template.yml +9 -0
  37. eodag/types/__init__.py +2 -0
  38. eodag/types/queryables.py +70 -91
  39. eodag/types/search_args.py +1 -1
  40. eodag/utils/__init__.py +97 -21
  41. eodag/utils/dates.py +0 -12
  42. eodag/utils/exceptions.py +6 -6
  43. eodag/utils/free_text_search.py +3 -3
  44. eodag/utils/repr.py +2 -0
  45. {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/METADATA +13 -99
  46. eodag-4.0.0a2.dist-info/RECORD +93 -0
  47. {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/entry_points.txt +0 -4
  48. eodag/plugins/authentication/oauth.py +0 -60
  49. eodag/plugins/download/creodias_s3.py +0 -71
  50. eodag/plugins/download/s3rest.py +0 -351
  51. eodag/plugins/search/data_request_search.py +0 -565
  52. eodag/resources/stac.yml +0 -294
  53. eodag/resources/stac_api.yml +0 -2105
  54. eodag/rest/__init__.py +0 -24
  55. eodag/rest/cache.py +0 -70
  56. eodag/rest/config.py +0 -67
  57. eodag/rest/constants.py +0 -26
  58. eodag/rest/core.py +0 -764
  59. eodag/rest/errors.py +0 -210
  60. eodag/rest/server.py +0 -604
  61. eodag/rest/server.wsgi +0 -6
  62. eodag/rest/stac.py +0 -1032
  63. eodag/rest/templates/README +0 -1
  64. eodag/rest/types/__init__.py +0 -18
  65. eodag/rest/types/collections_search.py +0 -44
  66. eodag/rest/types/eodag_search.py +0 -386
  67. eodag/rest/types/queryables.py +0 -174
  68. eodag/rest/types/stac_search.py +0 -272
  69. eodag/rest/utils/__init__.py +0 -207
  70. eodag/rest/utils/cql_evaluate.py +0 -119
  71. eodag/rest/utils/rfc3339.py +0 -64
  72. eodag-3.10.1.dist-info/RECORD +0 -116
  73. {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/WHEEL +0 -0
  74. {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/licenses/LICENSE +0 -0
  75. {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/top_level.txt +0 -0
@@ -19,11 +19,13 @@ search:
19
19
  type: StacSearch
20
20
  results_entry: features
21
21
  pagination:
22
- next_page_query_obj: '{{"limit":{items_per_page},"page":{page}}}'
22
+ next_page_query_obj: '{{"limit":{items_per_page},"{next_page_token_key}":"{next_page_token}"}}'
23
23
  total_items_nb_key_path: '$.numberMatched'
24
24
  next_page_url_key_path: '$.links[?(@.rel="next")].href'
25
25
  next_page_query_obj_key_path: '$.links[?(@.rel="next")].body'
26
26
  next_page_merge_key_path: '$.links[?(@.rel="next")].merge'
27
+ # do not use 'page' as default key, guess from provider response
28
+ next_page_token_key: null
27
29
  sort:
28
30
  sort_by_tpl: '{{"sortby": [ {{"field": "{sort_param}", "direction": "{sort_order}" }} ] }}'
29
31
  sort_order_mapping:
@@ -37,27 +39,27 @@ search:
37
39
  - filter
38
40
  - query
39
41
  metadata_path: '$.properties.*'
40
- discover_product_types:
42
+ discover_collections:
41
43
  fetch_url: '{api_endpoint}/../collections'
42
44
  result_type: json
43
45
  results_entry: '$.collections[*]'
44
- generic_product_type_id: '$.id'
45
- generic_product_type_parsable_properties:
46
- productType: '$.id'
47
- generic_product_type_parsable_metadata:
48
- abstract: '$.description'
49
- instrument: '{$.summaries.instruments#csv_list}'
50
- platform: '{$.summaries.constellation#csv_list}'
51
- platformSerialIdentifier: '{$.summaries.platform#csv_list}'
52
- processingLevel: '{$.summaries."processing:level"#csv_list}'
53
- keywords: '{$.keywords#csv_list}'
46
+ generic_collection_id: '$.id'
47
+ generic_collection_parsable_properties:
48
+ _collection: '$.id'
49
+ generic_collection_parsable_metadata:
50
+ description: '$.description'
51
+ instruments: '$.summaries.instruments'
52
+ constellation: '{$.summaries.constellation#csv_list}'
53
+ platform: '{$.summaries.platform#csv_list}'
54
+ processing:level: '{$.summaries."processing:level"#csv_list}'
55
+ keywords: '$.keywords'
54
56
  license: '$.license'
55
57
  title: '$.title'
56
- missionStartDate: '$.extent.temporal.interval[0][0]'
58
+ extent: '$.extent'
57
59
  metadata_path: '$.properties.*'
58
60
  discover_queryables:
59
61
  fetch_url: '{api_endpoint}/../queryables'
60
- product_type_fetch_url: '{api_endpoint}/../collections/{provider_product_type}/queryables'
62
+ collection_fetch_url: '{api_endpoint}/../collections/{provider_collection}/queryables'
61
63
  result_type: json
62
64
  results_entry: '$.properties[*]'
63
65
  queryable_parsable_metadata:
@@ -67,94 +69,50 @@ search:
67
69
  pattern: '$.pattern'
68
70
  common_metadata_mapping_path: '$'
69
71
  metadata_mapping:
70
- # OpenSearch Parameters for Collection Search (Table 3)
71
- productType:
72
- - '{{"collections":["{productType}"]}}'
72
+ productType: '$.null'
73
+ _collection:
74
+ - '{{"collections":["{_collection}"]}}'
73
75
  - '$.collection'
74
- doi:
75
- - '{{"query":{{"sci:doi":{{"eq":"{doi}"}}}}}}'
76
- - '$.properties."sci:doi"'
77
- processingLevel:
78
- - '{{"query":{{"processing:level":{{"eq":"{processingLevel}"}}}}}}'
79
- - '$.properties."processing:level"'
80
- platform:
81
- - '{{"query":{{"constellation":{{"eq":"{platform}"}}}}}}'
76
+ # common metadata
77
+ gsd:
78
+ - '{{"query":{{"gsd":{{"eq":"{gsd}"}}}}}}'
79
+ - '$.properties.gsd'
80
+ constellation:
81
+ - '{{"query":{{"constellation":{{"eq":"{constellation}"}}}}}}'
82
82
  - '$.properties.constellation'
83
- platformSerialIdentifier:
84
- - '{{"query":{{"platform":{{"eq":"{platformSerialIdentifier}"}}}}}}'
83
+ platform:
84
+ - '{{"query":{{"platform":{{"eq":"{platform}"}}}}}}'
85
85
  - '$.properties.platform'
86
- instrument:
87
- # to test
88
- - '{{"query":{{"instruments":{{"eq":"{instrument}"}}}}}}'
89
- - '{$.properties.instruments#csv_list}'
90
- # INSPIRE obligated OpenSearch Parameters for Collection Search (Table 4)
86
+ instruments:
87
+ - '{{"query":{{"instruments":{{"eq":{instruments}}}}}}}'
88
+ - '$.properties.instruments'
91
89
  title: '$.id'
92
- abstract: '$.properties.description'
93
- resolution:
94
- - '{{"query":{{"gsd":{{"eq":"{resolution}"}}}}}}'
95
- - '$.properties.gsd'
96
- publicationDate:
97
- - '{{"query":{{"published":{{"eq":"{publicationDate}"}}}}}}'
98
- - '$.properties.published'
99
- # OpenSearch Parameters for Product Search (Table 5)
100
- orbitNumber:
101
- - '{{"query":{{"sat:relative_orbit":{{"eq":"{orbitNumber}"}}}}}}'
102
- - '$.properties."sat:relative_orbit"'
103
- orbitDirection:
104
- - '{{"query":{{"sat:orbit_state":{{"eq":"{orbitDirection}"}}}}}}'
105
- - '{$.properties."sat:orbit_state"#to_lower}'
106
- cloudCover:
107
- - '{{"query":{{"eo:cloud_cover":{{"lte":"{cloudCover}"}}}}}}'
108
- - '$.properties."eo:cloud_cover"'
109
- sensorMode:
110
- - '{{"query":{{"sar:instrument_mode":{{"eq":"{sensorMode}"}}}}}}'
111
- - '$.properties."sar:instrument_mode"'
112
- creationDate:
113
- - '{{"query":{{"created":{{"eq":"{creationDate}"}}}}}}'
114
- - '$.properties.created'
115
- modificationDate:
116
- - '{{"query":{{"updated":{{"eq":"{modificationDate}"}}}}}}'
117
- - '$.properties.updated'
118
- productVersion:
119
- - '{{"query":{{"version":{{"eq":"{productVersion}"}}}}}}'
120
- - '$.properties.version'
121
- # OpenSearch Parameters for Acquistion Parameters Search (Table 6)
122
- availabilityTime:
123
- - '{{"query":{{"availabilityTime":{{"eq":"{availabilityTime}"}}}}}}'
124
- - '$.properties.availabilityTime'
125
- acquisitionStation:
126
- - '{{"query":{{"acquisitionStation":{{"eq":"{acquisitionStation}"}}}}}}'
127
- - '$.properties.acquisitionStation'
128
- acquisitionSubType:
129
- - '{{"query":{{"acquisitionSubType":{{"eq":"{acquisitionSubType}"}}}}}}'
130
- - '$.properties.acquisitionSubType'
131
- startTimeFromAscendingNode: '$.properties.datetime'
132
- completionTimeFromAscendingNode:
133
- - '{{"datetime":"{startTimeFromAscendingNode#to_iso_utc_datetime}/{completionTimeFromAscendingNode#to_iso_utc_datetime}"}}'
90
+ datetime: '$.properties.datetime'
91
+ start_datetime: '$.properties.start_datetime'
92
+ end_datetime:
93
+ - '{{"datetime":"{start_datetime#to_iso_utc_datetime}/{end_datetime#to_iso_utc_datetime}"}}'
134
94
  - '$.properties.end_datetime'
135
- illuminationAzimuthAngle:
136
- - '{{"query":{{"view:sun_azimuth":{{"eq":"{illuminationAzimuthAngle}"}}}}}}'
137
- - '$.properties."view:sun_azimuth"'
138
- illuminationElevationAngle:
139
- - '{{"query":{{"view:sun_elevation":{{"eq":"{illuminationElevationAngle}"}}}}}}'
140
- - '$.properties."view:sun_elevation"'
141
- polarizationChannels:
142
- - '{{"query":{{"sar:polarizations":{{"eq":"{polarizationChannels}"}}}}}}'
143
- - '{$.properties.sar:polarizations#csv_list(+)}'
144
- dopplerFrequency:
145
- - '{{"query":{{"sar:frequency_band":{{"eq":"{dopplerFrequency}"}}}}}}'
146
- - '$.properties."sar:frequency_band"'
147
- # Custom parameters (not defined in the base document referenced above)
148
95
  id:
149
96
  - '{{"ids":["{id}"]}}'
150
97
  - '$.id'
151
98
  geometry:
152
99
  - '{{"intersects":{geometry#to_geojson}}}'
153
100
  - '($.geometry.`str()`.`sub(/^None$/, POLYGON((180 -90, 180 90, -180 90, -180 -90, 180 -90)))`)|($.geometry[*])'
154
- downloadLink: '$.links[?(@.rel="self")].href'
155
- quicklook: '$.assets.quicklook.href'
156
- thumbnail: '$.assets.thumbnail.href'
157
- # storageStatus set to ONLINE for consistency between providers
158
- storageStatus: '{$.null#replace_str("Not Available","ONLINE")}'
101
+ # extensions parameters having specific mapping
102
+ sar:polarizations:
103
+ - '{{"query":{{"sar:polarizations":{{"eq":{sar:polarizations}}}}}}}'
104
+ - '$.properties.sar:polarizations'
105
+ sar:beam_ids:
106
+ - '{{"query":{{"sar:beam_ids":{{"eq":{sar:beam_ids}}}}}}}'
107
+ - '$.properties.sar:beam_ids'
108
+ eo:cloud_cover:
109
+ - '{{"query":{{"eo:cloud_cover":{{"lte":{eo:cloud_cover}}}}}}}'
110
+ - '$.properties."eo:cloud_cover"'
111
+ # eodag metadata
112
+ eodag:download_link: '$.links[?(@.rel="self")].href'
113
+ eodag:quicklook: '$.assets.quicklook.href'
114
+ eodag:thumbnail: '$.assets.thumbnail.href'
115
+ # order:status set to succeeded for consistency between providers
116
+ order:status: '{$.null#replace_str("Not Available","succeeded")}'
159
117
  # Normalization code moves assets from properties to product's attr
160
118
  assets: '$.assets'
@@ -107,6 +107,15 @@ dedt_lumi:
107
107
  credentials:
108
108
  username:
109
109
  password:
110
+ dedt_mn5:
111
+ priority: # Lower value means lower priority (Default: 0)
112
+ search:
113
+ download:
114
+ output_dir:
115
+ auth:
116
+ credentials:
117
+ username:
118
+ password:
110
119
  earth_search:
111
120
  priority: # Lower value means lower priority (Default: 0)
112
121
  search: # Search parameters configuration
eodag/types/__init__.py CHANGED
@@ -153,6 +153,7 @@ def json_field_definition_to_python(
153
153
  json_field_definition: dict[str, Any],
154
154
  default_value: Optional[Any] = None,
155
155
  required: Optional[bool] = False,
156
+ alias: Optional[str] = None,
156
157
  ) -> Annotated[Any, FieldInfo]:
157
158
  """Get python field definition from json object
158
159
 
@@ -180,6 +181,7 @@ def json_field_definition_to_python(
180
181
  pattern=json_field_definition.get("pattern", PydanticUndefined),
181
182
  le=json_field_definition.get("maximum", PydanticUndefined),
182
183
  ge=json_field_definition.get("minimum", PydanticUndefined),
184
+ alias=alias or PydanticUndefined,
183
185
  )
184
186
 
185
187
  enum = json_field_definition.get("enum")
eodag/types/queryables.py CHANGED
@@ -1,20 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
- # Copyright 2024, CS GROUP - France, https://www.csgroup.eu/
3
- #
4
- # This file is part of EODAG project
5
- # https://www.github.com/CS-SI/EODAG
6
- #
7
- # Licensed under the Apache License, Version 2.0 (the "License");
8
- # you may not use this file except in compliance with the License.
9
- # You may obtain a copy of the License at
10
- #
11
- # http://www.apache.org/licenses/LICENSE-2.0
12
- #
13
- # Unless required by applicable law or agreed to in writing, software
14
- # distributed under the License is distributed on an "AS IS" BASIS,
15
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
- # See the License for the specific language governing permissions and
17
- # limitations under the License.
18
1
  from __future__ import annotations
19
2
 
20
3
  from collections import UserDict
@@ -36,14 +19,14 @@ Percentage = Annotated[PositiveInt, Lt(100)]
36
19
  class CommonQueryables(BaseModel):
37
20
  """A class representing search common queryable properties."""
38
21
 
39
- productType: Annotated[str, Field()]
22
+ collection: Annotated[str, Field()]
40
23
 
41
24
  @classmethod
42
25
  def get_queryable_from_alias(cls, value: str) -> str:
43
26
  """Get queryable parameter from alias
44
27
 
45
- >>> CommonQueryables.get_queryable_from_alias('productType')
46
- 'productType'
28
+ >>> CommonQueryables.get_queryable_from_alias('collection')
29
+ 'collection'
47
30
  """
48
31
  alias_map = {
49
32
  field_info.alias: name
@@ -75,7 +58,7 @@ class Queryables(CommonQueryables):
75
58
  str,
76
59
  Field(
77
60
  None,
78
- alias="startTimeFromAscendingNode",
61
+ alias="start_datetime",
79
62
  description="Date/time as string in ISO 8601 format (e.g. '2024-06-10T12:00:00Z')",
80
63
  ),
81
64
  ]
@@ -83,7 +66,7 @@ class Queryables(CommonQueryables):
83
66
  str,
84
67
  Field(
85
68
  None,
86
- alias="completionTimeFromAscendingNode",
69
+ alias="end_datetime",
87
70
  description="Date/time as string in ISO 8601 format (e.g. '2024-06-10T12:00:00Z')",
88
71
  ),
89
72
  ]
@@ -95,77 +78,57 @@ class Queryables(CommonQueryables):
95
78
  description="Read EODAG documentation for all supported geometry format.",
96
79
  ),
97
80
  ]
98
- uid: Annotated[str, Field(None)]
99
- # OpenSearch Parameters for Collection Search (Table 3)
100
- doi: Annotated[str, Field(None)]
81
+ # common metadata
82
+ constellation: Annotated[str, Field(None)]
83
+ created: Annotated[str, Field(None)]
84
+ description: Annotated[str, Field(None)]
85
+ gsd: Annotated[int, Field(None)]
86
+ id: Annotated[str, Field(None)]
87
+ instruments: Annotated[str, Field(None)]
88
+ keywords: Annotated[str, Field(None)]
89
+ license: Annotated[str, Field(None)]
101
90
  platform: Annotated[str, Field(None)]
102
- platformSerialIdentifier: Annotated[str, Field(None)]
103
- instrument: Annotated[str, Field(None)]
104
- sensorType: Annotated[str, Field(None)]
105
- compositeType: Annotated[str, Field(None)]
106
- processingLevel: Annotated[str, Field(None)]
107
- orbitType: Annotated[str, Field(None)]
108
- spectralRange: Annotated[str, Field(None)]
109
- wavelengths: Annotated[str, Field(None)]
110
- hasSecurityConstraints: Annotated[str, Field(None)]
111
- dissemination: Annotated[str, Field(None)]
112
- # INSPIRE obligated OpenSearch Parameters for Collection Search (Table 4)
91
+ providers: Annotated[str, Field(None)]
113
92
  title: Annotated[str, Field(None)]
114
- topicCategory: Annotated[str, Field(None)]
115
- keyword: Annotated[str, Field(None)]
116
- abstract: Annotated[str, Field(None)]
117
- resolution: Annotated[int, Field(None)]
118
- organisationName: Annotated[str, Field(None)]
119
- organisationRole: Annotated[str, Field(None)]
120
- publicationDate: Annotated[str, Field(None)]
121
- lineage: Annotated[str, Field(None)]
122
- useLimitation: Annotated[str, Field(None)]
123
- accessConstraint: Annotated[str, Field(None)]
124
- otherConstraint: Annotated[str, Field(None)]
125
- classification: Annotated[str, Field(None)]
126
- language: Annotated[str, Field(None)]
127
- specification: Annotated[str, Field(None)]
128
- # OpenSearch Parameters for Product Search (Table 5)
129
- parentIdentifier: Annotated[str, Field(None)]
130
- productionStatus: Annotated[str, Field(None)]
131
- acquisitionType: Annotated[str, Field(None)]
132
- orbitNumber: Annotated[int, Field(None)]
133
- orbitDirection: Annotated[str, Field(None)]
134
- track: Annotated[str, Field(None)]
135
- frame: Annotated[str, Field(None)]
136
- swathIdentifier: Annotated[str, Field(None)]
137
- cloudCover: Annotated[Percentage, Field(None)]
138
- snowCover: Annotated[Percentage, Field(None)]
139
- lowestLocation: Annotated[str, Field(None)]
140
- highestLocation: Annotated[str, Field(None)]
141
- productVersion: Annotated[str, Field(None)]
142
- productQualityStatus: Annotated[str, Field(None)]
143
- productQualityDegradationTag: Annotated[str, Field(None)]
144
- processorName: Annotated[str, Field(None)]
145
- processingCenter: Annotated[str, Field(None)]
146
- creationDate: Annotated[str, Field(None)]
147
- modificationDate: Annotated[str, Field(None)]
148
- processingDate: Annotated[str, Field(None)]
149
- sensorMode: Annotated[str, Field(None)]
150
- archivingCenter: Annotated[str, Field(None)]
151
- processingMode: Annotated[str, Field(None)]
152
- # OpenSearch Parameters for Acquistion Parameters Search (Table 6)
153
- availabilityTime: Annotated[str, Field(None)]
154
- acquisitionStation: Annotated[str, Field(None)]
155
- acquisitionSubType: Annotated[str, Field(None)]
156
- illuminationAzimuthAngle: Annotated[str, Field(None)]
157
- illuminationZenithAngle: Annotated[str, Field(None)]
158
- illuminationElevationAngle: Annotated[str, Field(None)]
159
- polarizationMode: Annotated[str, Field(None)]
160
- polarizationChannels: Annotated[str, Field(None)]
161
- antennaLookDirection: Annotated[str, Field(None)]
162
- minimumIncidenceAngle: Annotated[float, Field(None)]
163
- maximumIncidenceAngle: Annotated[float, Field(None)]
164
- dopplerFrequency: Annotated[float, Field(None)]
165
- incidenceAngleVariation: Annotated[float, Field(None)]
166
- # Custom parameters (not defined in the base document referenced above)
167
- id: Annotated[str, Field(None)]
168
- tileIdentifier: Annotated[str, Field(None, pattern=r"[0-9]{2}[A-Z]{3}")]
93
+ uid: Annotated[str, Field(None)]
94
+ updated: Annotated[str, Field(None)]
95
+ # eo extension
96
+ eo_cloud_cover: Annotated[Percentage, Field(None, alias="eo:cloud_cover")]
97
+ eo_snow_cover: Annotated[Percentage, Field(None, alias="eo:snow_cover")]
98
+ # grid extension
99
+ grid_code: Annotated[
100
+ str, Field(None, alias="grid:code", pattern=r"^[A-Z0-9]+-[-_.A-Za-z0-9]+$")
101
+ ]
102
+ # mgrs extension
103
+ mgrs_grid_square: Annotated[str, Field(None, alias="mgrs:grid_square")]
104
+ mgrs_latitude_band: Annotated[str, Field(None, alias="mgrs:latitude_band")]
105
+ mgrs_utm_zone: Annotated[str, Field(None, alias="mgrs:utm_zone")]
106
+ # order extension
107
+ order_status: Annotated[str, Field(None, alias="order:status")]
108
+ # processing extension
109
+ processing_level: Annotated[str, Field(None, alias="processing:level")]
110
+ # product extension
111
+ product_acquisition_type: Annotated[
112
+ str, Field(None, alias="product:acquisition_type")
113
+ ]
114
+ product_timeliness: Annotated[str, Field(None, alias="product:timeliness")]
115
+ product_type: Annotated[str, Field(None, alias="product:type")]
116
+ # sar extension
117
+ sar_beam_ids: Annotated[str, Field(None, alias="sar:beam_ids")]
118
+ sar_frequency_band: Annotated[float, Field(None, alias="sar:frequency_band")]
119
+ sar_instrument_mode: Annotated[str, Field(None, alias="sar:instrument_mode")]
120
+ sar_polarizations: Annotated[list[str], Field(None, alias="sar:polarizations")]
121
+ # sat extension
122
+ sat_absolute_orbit: Annotated[int, Field(None, alias="sat:absolute_orbit")]
123
+ sat_orbit_cycle: Annotated[int, Field(None, alias="sat:orbit_cycle")]
124
+ sat_orbit_state: Annotated[str, Field(None, alias="sat:orbit_state")]
125
+ sat_relative_orbit: Annotated[int, Field(None, alias="sat:relative_orbit")]
126
+ # sci extension
127
+ sci_doi: Annotated[str, Field(None, alias="sci:doi")]
128
+ # view extension
129
+ view_incidence_angle: Annotated[str, Field(None, alias="view:incidence_angle")]
130
+ view_sun_azimuth: Annotated[str, Field(None, alias="view:sun_azimuth")]
131
+ view_sun_elevation: Annotated[str, Field(None, alias="view:sun_elevation")]
169
132
 
170
133
  model_config = ConfigDict(arbitrary_types_allowed=True)
171
134
 
@@ -190,6 +153,22 @@ class QueryablesDict(UserDict[str, Any]):
190
153
  self.additional_properties = additional_properties
191
154
  self.additional_information = additional_information
192
155
  super().__init__(kwargs)
156
+ # sort queryables: first without then with extension prefix
157
+ no_prefix_queryables = {
158
+ key: self.data[key]
159
+ for key in sorted(self.data)
160
+ if ":"
161
+ not in str(
162
+ getattr(self.data[key], "__metadata__", [Field()])[0].alias or key
163
+ )
164
+ }
165
+ with_prefix_queryables = {
166
+ key: self.data[key]
167
+ for key in sorted(self.data)
168
+ if ":"
169
+ in str(getattr(self.data[key], "__metadata__", [Field()])[0].alias or key)
170
+ }
171
+ self.data = no_prefix_queryables | with_prefix_queryables
193
172
 
194
173
  def _repr_html_(self, embedded: bool = False) -> str:
195
174
  add_info = (
@@ -43,7 +43,7 @@ class SearchArgs(BaseModel):
43
43
  model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
44
44
 
45
45
  provider: Optional[str] = Field(None)
46
- productType: str = Field()
46
+ collection: str = Field()
47
47
  id: Optional[str] = Field(None)
48
48
  start: Optional[str] = Field(None)
49
49
  end: Optional[str] = Field(None)
eodag/utils/__init__.py CHANGED
@@ -63,6 +63,8 @@ from typing import (
63
63
  from urllib.parse import urlparse, urlsplit
64
64
  from urllib.request import url2pathname
65
65
 
66
+ from pydantic import ValidationError as PydanticValidationError
67
+
66
68
  if sys.version_info >= (3, 12):
67
69
  from typing import Unpack # type: ignore # noqa
68
70
  else:
@@ -94,7 +96,7 @@ logger = py_logging.getLogger("eodag.utils")
94
96
 
95
97
  DEFAULT_PROJ = "EPSG:4326"
96
98
 
97
- GENERIC_PRODUCT_TYPE = "GENERIC_PRODUCT_TYPE"
99
+ GENERIC_COLLECTION = "GENERIC_COLLECTION"
98
100
  GENERIC_STAC_PROVIDER = "generic_stac_provider"
99
101
 
100
102
  STAC_SEARCH_PLUGINS = [
@@ -129,7 +131,7 @@ DEFAULT_ITEMS_PER_PAGE = 20
129
131
  # (DEFAULT_ITEMS_PER_PAGE) to increase it to the known and current minimum value (mundi)
130
132
  DEFAULT_MAX_ITEMS_PER_PAGE = 50
131
133
 
132
- # default product-types start date
134
+ # default collections start date
133
135
  DEFAULT_MISSION_START_DATE = "2015-01-01T00:00:00.000Z"
134
136
 
135
137
  # default geometry / whole world bounding box
@@ -138,6 +140,9 @@ DEFAULT_SHAPELY_GEOMETRY = box(-180, -90, 180, 90)
138
140
  # default token expiration margin in seconds
139
141
  DEFAULT_TOKEN_EXPIRATION_MARGIN = 60
140
142
 
143
+ # knwown next page token keys used to guess key in STAC providers next link responses
144
+ KNOWN_NEXT_PAGE_TOKEN_KEYS = ["token", "next", "page", "skip"]
145
+
141
146
  # update missing mimetypes
142
147
  mimetypes.add_type("text/xml", ".xsd")
143
148
  mimetypes.add_type("application/x-grib", ".grib")
@@ -340,11 +345,11 @@ def merge_mappings(mapping1: dict[Any, Any], mapping2: dict[Any, Any]) -> None:
340
345
 
341
346
  Do its best to detect the key in ``mapping1`` to override. For example:
342
347
 
343
- >>> mapping2 = {"keya": "new"}
344
- >>> mapping1 = {"keyA": "obsolete"}
348
+ >>> mapping2 = {"ext_keya": "new"}
349
+ >>> mapping1 = {"ext:keyA": "obsolete"}
345
350
  >>> merge_mappings(mapping1, mapping2)
346
351
  >>> mapping1
347
- {'keyA': 'new'}
352
+ {'ext:keyA': 'new'}
348
353
 
349
354
  If ``mapping2`` has a key that cannot be detected in ``mapping1``, this new key is
350
355
  added to ``mapping1`` as is.
@@ -353,7 +358,7 @@ def merge_mappings(mapping1: dict[Any, Any], mapping2: dict[Any, Any]) -> None:
353
358
  :param mapping2: The mapping containing values that will override the first mapping
354
359
  """
355
360
  # A mapping between mapping1 keys as lowercase strings and original mapping1 keys
356
- m1_keys_lowercase = {key.lower(): key for key in mapping1}
361
+ m1_keys_lowercase = {key.lower().replace(":", "_"): key for key in mapping1}
357
362
  for key, value in mapping2.items():
358
363
  if isinstance(value, dict):
359
364
  try:
@@ -478,20 +483,6 @@ class ProgressCallback(tqdm):
478
483
  return ProgressCallback(*args, **dict(self.kwargs, **kwargs))
479
484
 
480
485
 
481
- @_deprecated(reason="Use ProgressCallback class instead", version="2.2.1")
482
- class NotebookProgressCallback(tqdm):
483
- """A custom progress bar to be used inside Jupyter notebooks"""
484
-
485
- pass
486
-
487
-
488
- @_deprecated(reason="Use ProgressCallback class instead", version="2.2.1")
489
- def get_progress_callback() -> tqdm:
490
- """Get progress_callback"""
491
-
492
- return tqdm()
493
-
494
-
495
486
  def repeatfunc(func: Callable[..., Any], n: int, *args: Any) -> starmap:
496
487
  """Call ``func`` ``n`` times with ``args``"""
497
488
  return starmap(func, repeat(args, n))
@@ -975,7 +966,9 @@ def string_to_jsonpath(*args: Any, force: bool = False) -> Union[str, JSONPath]:
975
966
  return path_str
976
967
 
977
968
 
978
- def format_string(key: str, str_to_format: Any, **format_variables: Any) -> Any:
969
+ def format_string(
970
+ key: Optional[str], str_to_format: Any, **format_variables: Any
971
+ ) -> Any:
979
972
  """Format ``"{foo}"``-like string
980
973
 
981
974
  >>> format_string(None, "foo {bar}, {baz} ?", **{"bar": "qux", "baz": "quux"})
@@ -999,6 +992,27 @@ def format_string(key: str, str_to_format: Any, **format_variables: Any) -> Any:
999
992
  # defaultdict usage will return "" for missing keys in format_args
1000
993
  try:
1001
994
  result = str_to_format.format_map(defaultdict(str, **format_variables))
995
+ except (ValueError, TypeError) as e:
996
+ if not re.search(r"{[\w-]*:[\w-]*}", str_to_format):
997
+ raise MisconfiguredError(
998
+ f"Unable to format str={str_to_format} using {str(format_variables)}: {str(e)}"
999
+ )
1000
+ # retry parsing colons
1001
+ try:
1002
+ str_without_colons = re.sub(
1003
+ r"{([\w-]*):([\w-]*)}",
1004
+ r"{\1_COLON_\2}",
1005
+ str_to_format,
1006
+ )
1007
+ result = str_without_colons.format_map(
1008
+ defaultdict(
1009
+ str,
1010
+ **{
1011
+ k.replace(":", "_COLON_"): v
1012
+ for k, v in format_variables.items()
1013
+ },
1014
+ )
1015
+ )
1002
1016
  except (ValueError, TypeError) as e:
1003
1017
  raise MisconfiguredError(
1004
1018
  f"Unable to format str={str_to_format} using {str(format_variables)}: {str(e)}"
@@ -1643,3 +1657,65 @@ def parse_le_uint16(data: bytes) -> int:
1643
1657
  65535
1644
1658
  """
1645
1659
  return struct.unpack("<H", data)[0]
1660
+
1661
+
1662
+ def format_pydantic_error(e: PydanticValidationError) -> str:
1663
+ """Format Pydantic ValidationError
1664
+
1665
+ :param e: A Pydantic ValidationError object
1666
+ :type e: PydanticValidationError
1667
+ """
1668
+ error_header = f"{e.error_count()} error(s). "
1669
+
1670
+ error_messages = [
1671
+ f'{err["loc"][0]}: {err["msg"]}' if err["loc"] else err["msg"]
1672
+ for err in e.errors()
1673
+ ]
1674
+ return error_header + "; ".join(set(error_messages))
1675
+
1676
+
1677
+ def get_collection_dates(
1678
+ collection_dict: dict[str, Any]
1679
+ ) -> tuple[Optional[str], Optional[str]]:
1680
+ """Extract mission start and end dates from collection configuration.
1681
+
1682
+ Extracts dates from the extent.temporal.interval structure.
1683
+
1684
+ :param collection_dict: Collection configuration dictionary
1685
+ :returns: Tuple of (mission_start_date, mission_end_date) as ISO strings or None
1686
+
1687
+ Example:
1688
+ >>> get_collection_dates({
1689
+ ... "extent": {"temporal": {"interval": [["2017-10-13T00:00:00Z", "2023-12-31T23:59:59Z"]]}}
1690
+ ... })
1691
+ ('2017-10-13T00:00:00Z', '2023-12-31T23:59:59Z')
1692
+
1693
+ >>> get_collection_dates({
1694
+ ... "extent": {"temporal": {"interval": [["2017-10-13T00:00:00Z", None]]}}
1695
+ ... })
1696
+ ('2017-10-13T00:00:00Z', None)
1697
+
1698
+ >>> get_collection_dates({})
1699
+ (None, None)
1700
+ """
1701
+ extent_interval = (
1702
+ collection_dict.get("extent", {})
1703
+ .get("temporal", {})
1704
+ .get("interval", [[None, None]])
1705
+ )
1706
+
1707
+ if not extent_interval or len(extent_interval) == 0:
1708
+ return None, None
1709
+
1710
+ mission_start = (
1711
+ extent_interval[0][0]
1712
+ if len(extent_interval) > 0 and len(extent_interval[0]) > 0
1713
+ else None
1714
+ )
1715
+ mission_end = (
1716
+ extent_interval[0][1]
1717
+ if len(extent_interval) > 0 and len(extent_interval[0]) > 1
1718
+ else None
1719
+ )
1720
+
1721
+ return mission_start, mission_end
eodag/utils/dates.py CHANGED
@@ -35,18 +35,6 @@ RFC3339_PATTERN = (
35
35
  r"(Z|([+-])(\d{2}):(\d{2}))?)?$"
36
36
  )
37
37
 
38
- # yyyy-mm-dd
39
- DATE_PATTERN = r"\d{4}-(0[1-9]|1[1,2])-(0[1-9]|[12][0-9]|3[01])"
40
-
41
- # yyyymmdd
42
- COMPACT_DATE_PATTERN = r"\d{4}(0[1-9]|1[1,2])(0[1-9]|[12][0-9]|3[01])"
43
-
44
- # yyyy-mm-dd/yyyy-mm-dd, yyyy-mm-dd/to/yyyy-mm-dd
45
- DATE_RANGE_PATTERN = DATE_PATTERN + r"(/to/|/)" + DATE_PATTERN
46
-
47
- # yyyymmdd/yyyymmdd, yyyymmdd/to/yyyymmdd
48
- COMPACT_DATE_RANGE_PATTERN = COMPACT_DATE_PATTERN + r"(/to/|/)" + COMPACT_DATE_PATTERN
49
-
50
38
 
51
39
  def get_timestamp(date_time: str) -> float:
52
40
  """Return the Unix timestamp of an ISO8601 date/datetime in seconds.