eodag 3.10.0__py3-none-any.whl → 4.0.0a1__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 (68) hide show
  1. eodag/api/core.py +378 -419
  2. eodag/api/product/__init__.py +3 -3
  3. eodag/api/product/_product.py +68 -40
  4. eodag/api/product/drivers/__init__.py +3 -5
  5. eodag/api/product/drivers/base.py +1 -18
  6. eodag/api/product/metadata_mapping.py +151 -215
  7. eodag/api/search_result.py +13 -7
  8. eodag/cli.py +72 -395
  9. eodag/config.py +46 -50
  10. eodag/plugins/apis/base.py +2 -2
  11. eodag/plugins/apis/ecmwf.py +20 -21
  12. eodag/plugins/apis/usgs.py +37 -33
  13. eodag/plugins/authentication/base.py +1 -3
  14. eodag/plugins/crunch/filter_date.py +3 -3
  15. eodag/plugins/crunch/filter_latest_intersect.py +2 -2
  16. eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
  17. eodag/plugins/download/aws.py +45 -41
  18. eodag/plugins/download/base.py +13 -14
  19. eodag/plugins/download/http.py +65 -65
  20. eodag/plugins/manager.py +28 -29
  21. eodag/plugins/search/__init__.py +3 -4
  22. eodag/plugins/search/base.py +128 -77
  23. eodag/plugins/search/build_search_result.py +105 -107
  24. eodag/plugins/search/cop_marine.py +44 -47
  25. eodag/plugins/search/csw.py +33 -33
  26. eodag/plugins/search/qssearch.py +335 -354
  27. eodag/plugins/search/stac_list_assets.py +1 -1
  28. eodag/plugins/search/static_stac_search.py +31 -31
  29. eodag/resources/{product_types.yml → collections.yml} +2353 -2429
  30. eodag/resources/ext_collections.json +1 -1
  31. eodag/resources/providers.yml +2427 -2719
  32. eodag/resources/stac_provider.yml +46 -90
  33. eodag/types/queryables.py +55 -91
  34. eodag/types/search_args.py +1 -1
  35. eodag/utils/__init__.py +94 -21
  36. eodag/utils/exceptions.py +6 -6
  37. eodag/utils/free_text_search.py +3 -3
  38. {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/METADATA +10 -87
  39. eodag-4.0.0a1.dist-info/RECORD +92 -0
  40. {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/entry_points.txt +0 -4
  41. eodag/plugins/authentication/oauth.py +0 -60
  42. eodag/plugins/download/creodias_s3.py +0 -71
  43. eodag/plugins/download/s3rest.py +0 -351
  44. eodag/plugins/search/data_request_search.py +0 -565
  45. eodag/resources/stac.yml +0 -294
  46. eodag/resources/stac_api.yml +0 -2105
  47. eodag/rest/__init__.py +0 -24
  48. eodag/rest/cache.py +0 -70
  49. eodag/rest/config.py +0 -67
  50. eodag/rest/constants.py +0 -26
  51. eodag/rest/core.py +0 -764
  52. eodag/rest/errors.py +0 -210
  53. eodag/rest/server.py +0 -604
  54. eodag/rest/server.wsgi +0 -6
  55. eodag/rest/stac.py +0 -1032
  56. eodag/rest/templates/README +0 -1
  57. eodag/rest/types/__init__.py +0 -18
  58. eodag/rest/types/collections_search.py +0 -44
  59. eodag/rest/types/eodag_search.py +0 -386
  60. eodag/rest/types/queryables.py +0 -174
  61. eodag/rest/types/stac_search.py +0 -272
  62. eodag/rest/utils/__init__.py +0 -207
  63. eodag/rest/utils/cql_evaluate.py +0 -119
  64. eodag/rest/utils/rfc3339.py +0 -64
  65. eodag-3.10.0.dist-info/RECORD +0 -116
  66. {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/WHEEL +0 -0
  67. {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/licenses/LICENSE +0 -0
  68. {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/top_level.txt +0 -0
@@ -37,27 +37,27 @@ search:
37
37
  - filter
38
38
  - query
39
39
  metadata_path: '$.properties.*'
40
- discover_product_types:
40
+ discover_collections:
41
41
  fetch_url: '{api_endpoint}/../collections'
42
42
  result_type: json
43
43
  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}'
44
+ generic_collection_id: '$.id'
45
+ generic_collection_parsable_properties:
46
+ _collection: '$.id'
47
+ generic_collection_parsable_metadata:
48
+ description: '$.description'
49
+ instruments: '$.summaries.instruments'
50
+ constellation: '{$.summaries.constellation#csv_list}'
51
+ platform: '{$.summaries.platform#csv_list}'
52
+ processing:level: '{$.summaries."processing:level"#csv_list}'
53
53
  keywords: '{$.keywords#csv_list}'
54
54
  license: '$.license'
55
55
  title: '$.title'
56
- missionStartDate: '$.extent.temporal.interval[0][0]'
56
+ extent: '$.extent'
57
57
  metadata_path: '$.properties.*'
58
58
  discover_queryables:
59
59
  fetch_url: '{api_endpoint}/../queryables'
60
- product_type_fetch_url: '{api_endpoint}/../collections/{provider_product_type}/queryables'
60
+ collection_fetch_url: '{api_endpoint}/../collections/{provider_collection}/queryables'
61
61
  result_type: json
62
62
  results_entry: '$.properties[*]'
63
63
  queryable_parsable_metadata:
@@ -67,94 +67,50 @@ search:
67
67
  pattern: '$.pattern'
68
68
  common_metadata_mapping_path: '$'
69
69
  metadata_mapping:
70
- # OpenSearch Parameters for Collection Search (Table 3)
71
- productType:
72
- - '{{"collections":["{productType}"]}}'
70
+ productType: '$.null'
71
+ _collection:
72
+ - '{{"collections":["{_collection}"]}}'
73
73
  - '$.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}"}}}}}}'
74
+ # common metadata
75
+ gsd:
76
+ - '{{"query":{{"gsd":{{"eq":"{gsd}"}}}}}}'
77
+ - '$.properties.gsd'
78
+ constellation:
79
+ - '{{"query":{{"constellation":{{"eq":"{constellation}"}}}}}}'
82
80
  - '$.properties.constellation'
83
- platformSerialIdentifier:
84
- - '{{"query":{{"platform":{{"eq":"{platformSerialIdentifier}"}}}}}}'
81
+ platform:
82
+ - '{{"query":{{"platform":{{"eq":"{platform}"}}}}}}'
85
83
  - '$.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)
84
+ instruments:
85
+ - '{{"query":{{"instruments":{{"eq":{instruments}}}}}}}'
86
+ - '$.properties.instruments'
91
87
  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}"}}'
88
+ datetime: '$.properties.datetime'
89
+ start_datetime: '$.properties.start_datetime'
90
+ end_datetime:
91
+ - '{{"datetime":"{start_datetime#to_iso_utc_datetime}/{end_datetime#to_iso_utc_datetime}"}}'
134
92
  - '$.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
93
  id:
149
94
  - '{{"ids":["{id}"]}}'
150
95
  - '$.id'
151
96
  geometry:
152
97
  - '{{"intersects":{geometry#to_geojson}}}'
153
98
  - '($.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")}'
99
+ # extensions parameters having specific mapping
100
+ sar:polarizations:
101
+ - '{{"query":{{"sar:polarizations":{{"eq":{sar:polarizations}}}}}}}'
102
+ - '$.properties.sar:polarizations'
103
+ sar:beam_ids:
104
+ - '{{"query":{{"sar:beam_ids":{{"eq":{sar:beam_ids}}}}}}}'
105
+ - '$.properties.sar:beam_ids'
106
+ eo:cloud_cover:
107
+ - '{{"query":{{"eo:cloud_cover":{{"lte":{eo:cloud_cover}}}}}}}'
108
+ - '$.properties."eo:cloud_cover"'
109
+ # eodag metadata
110
+ eodag:download_link: '$.links[?(@.rel="self")].href'
111
+ eodag:quicklook: '$.assets.quicklook.href'
112
+ eodag:thumbnail: '$.assets.thumbnail.href'
113
+ # order:status set to succeeded for consistency between providers
114
+ order:status: '{$.null#replace_str("Not Available","succeeded")}'
159
115
  # Normalization code moves assets from properties to product's attr
160
116
  assets: '$.assets'
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
  ]
@@ -91,80 +74,61 @@ class Queryables(CommonQueryables):
91
74
  Union[str, dict[str, float], BaseGeometry],
92
75
  Field(
93
76
  None,
77
+ alias="geometry",
94
78
  description="Read EODAG documentation for all supported geometry format.",
95
79
  ),
96
80
  ]
97
- uid: Annotated[str, Field(None)]
98
- # OpenSearch Parameters for Collection Search (Table 3)
99
- 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)]
100
90
  platform: Annotated[str, Field(None)]
101
- platformSerialIdentifier: Annotated[str, Field(None)]
102
- instrument: Annotated[str, Field(None)]
103
- sensorType: Annotated[str, Field(None)]
104
- compositeType: Annotated[str, Field(None)]
105
- processingLevel: Annotated[str, Field(None)]
106
- orbitType: Annotated[str, Field(None)]
107
- spectralRange: Annotated[str, Field(None)]
108
- wavelengths: Annotated[str, Field(None)]
109
- hasSecurityConstraints: Annotated[str, Field(None)]
110
- dissemination: Annotated[str, Field(None)]
111
- # INSPIRE obligated OpenSearch Parameters for Collection Search (Table 4)
91
+ providers: Annotated[str, Field(None)]
112
92
  title: Annotated[str, Field(None)]
113
- topicCategory: Annotated[str, Field(None)]
114
- keyword: Annotated[str, Field(None)]
115
- abstract: Annotated[str, Field(None)]
116
- resolution: Annotated[int, Field(None)]
117
- organisationName: Annotated[str, Field(None)]
118
- organisationRole: Annotated[str, Field(None)]
119
- publicationDate: Annotated[str, Field(None)]
120
- lineage: Annotated[str, Field(None)]
121
- useLimitation: Annotated[str, Field(None)]
122
- accessConstraint: Annotated[str, Field(None)]
123
- otherConstraint: Annotated[str, Field(None)]
124
- classification: Annotated[str, Field(None)]
125
- language: Annotated[str, Field(None)]
126
- specification: Annotated[str, Field(None)]
127
- # OpenSearch Parameters for Product Search (Table 5)
128
- parentIdentifier: Annotated[str, Field(None)]
129
- productionStatus: Annotated[str, Field(None)]
130
- acquisitionType: Annotated[str, Field(None)]
131
- orbitNumber: Annotated[int, Field(None)]
132
- orbitDirection: Annotated[str, Field(None)]
133
- track: Annotated[str, Field(None)]
134
- frame: Annotated[str, Field(None)]
135
- swathIdentifier: Annotated[str, Field(None)]
136
- cloudCover: Annotated[Percentage, Field(None)]
137
- snowCover: Annotated[Percentage, Field(None)]
138
- lowestLocation: Annotated[str, Field(None)]
139
- highestLocation: Annotated[str, Field(None)]
140
- productVersion: Annotated[str, Field(None)]
141
- productQualityStatus: Annotated[str, Field(None)]
142
- productQualityDegradationTag: Annotated[str, Field(None)]
143
- processorName: Annotated[str, Field(None)]
144
- processingCenter: Annotated[str, Field(None)]
145
- creationDate: Annotated[str, Field(None)]
146
- modificationDate: Annotated[str, Field(None)]
147
- processingDate: Annotated[str, Field(None)]
148
- sensorMode: Annotated[str, Field(None)]
149
- archivingCenter: Annotated[str, Field(None)]
150
- processingMode: Annotated[str, Field(None)]
151
- # OpenSearch Parameters for Acquistion Parameters Search (Table 6)
152
- availabilityTime: Annotated[str, Field(None)]
153
- acquisitionStation: Annotated[str, Field(None)]
154
- acquisitionSubType: Annotated[str, Field(None)]
155
- illuminationAzimuthAngle: Annotated[str, Field(None)]
156
- illuminationZenithAngle: Annotated[str, Field(None)]
157
- illuminationElevationAngle: Annotated[str, Field(None)]
158
- polarizationMode: Annotated[str, Field(None)]
159
- polarizationChannels: Annotated[str, Field(None)]
160
- antennaLookDirection: Annotated[str, Field(None)]
161
- minimumIncidenceAngle: Annotated[float, Field(None)]
162
- maximumIncidenceAngle: Annotated[float, Field(None)]
163
- dopplerFrequency: Annotated[float, Field(None)]
164
- incidenceAngleVariation: Annotated[float, Field(None)]
165
- # Custom parameters (not defined in the base document referenced above)
166
- id: Annotated[str, Field(None)]
167
- 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")]
168
132
 
169
133
  model_config = ConfigDict(arbitrary_types_allowed=True)
170
134
 
@@ -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 token expiration margin in seconds
@@ -337,11 +339,11 @@ def merge_mappings(mapping1: dict[Any, Any], mapping2: dict[Any, Any]) -> None:
337
339
 
338
340
  Do its best to detect the key in ``mapping1`` to override. For example:
339
341
 
340
- >>> mapping2 = {"keya": "new"}
341
- >>> mapping1 = {"keyA": "obsolete"}
342
+ >>> mapping2 = {"ext_keya": "new"}
343
+ >>> mapping1 = {"ext:keyA": "obsolete"}
342
344
  >>> merge_mappings(mapping1, mapping2)
343
345
  >>> mapping1
344
- {'keyA': 'new'}
346
+ {'ext:keyA': 'new'}
345
347
 
346
348
  If ``mapping2`` has a key that cannot be detected in ``mapping1``, this new key is
347
349
  added to ``mapping1`` as is.
@@ -350,7 +352,7 @@ def merge_mappings(mapping1: dict[Any, Any], mapping2: dict[Any, Any]) -> None:
350
352
  :param mapping2: The mapping containing values that will override the first mapping
351
353
  """
352
354
  # A mapping between mapping1 keys as lowercase strings and original mapping1 keys
353
- m1_keys_lowercase = {key.lower(): key for key in mapping1}
355
+ m1_keys_lowercase = {key.lower().replace(":", "_"): key for key in mapping1}
354
356
  for key, value in mapping2.items():
355
357
  if isinstance(value, dict):
356
358
  try:
@@ -475,20 +477,6 @@ class ProgressCallback(tqdm):
475
477
  return ProgressCallback(*args, **dict(self.kwargs, **kwargs))
476
478
 
477
479
 
478
- @_deprecated(reason="Use ProgressCallback class instead", version="2.2.1")
479
- class NotebookProgressCallback(tqdm):
480
- """A custom progress bar to be used inside Jupyter notebooks"""
481
-
482
- pass
483
-
484
-
485
- @_deprecated(reason="Use ProgressCallback class instead", version="2.2.1")
486
- def get_progress_callback() -> tqdm:
487
- """Get progress_callback"""
488
-
489
- return tqdm()
490
-
491
-
492
480
  def repeatfunc(func: Callable[..., Any], n: int, *args: Any) -> starmap:
493
481
  """Call ``func`` ``n`` times with ``args``"""
494
482
  return starmap(func, repeat(args, n))
@@ -972,7 +960,9 @@ def string_to_jsonpath(*args: Any, force: bool = False) -> Union[str, JSONPath]:
972
960
  return path_str
973
961
 
974
962
 
975
- def format_string(key: str, str_to_format: Any, **format_variables: Any) -> Any:
963
+ def format_string(
964
+ key: Optional[str], str_to_format: Any, **format_variables: Any
965
+ ) -> Any:
976
966
  """Format ``"{foo}"``-like string
977
967
 
978
968
  >>> format_string(None, "foo {bar}, {baz} ?", **{"bar": "qux", "baz": "quux"})
@@ -996,6 +986,27 @@ def format_string(key: str, str_to_format: Any, **format_variables: Any) -> Any:
996
986
  # defaultdict usage will return "" for missing keys in format_args
997
987
  try:
998
988
  result = str_to_format.format_map(defaultdict(str, **format_variables))
989
+ except (ValueError, TypeError) as e:
990
+ if not re.search(r"{[\w-]*:[\w-]*}", str_to_format):
991
+ raise MisconfiguredError(
992
+ f"Unable to format str={str_to_format} using {str(format_variables)}: {str(e)}"
993
+ )
994
+ # retry parsing colons
995
+ try:
996
+ str_without_colons = re.sub(
997
+ r"{([\w-]*):([\w-]*)}",
998
+ r"{\1_COLON_\2}",
999
+ str_to_format,
1000
+ )
1001
+ result = str_without_colons.format_map(
1002
+ defaultdict(
1003
+ str,
1004
+ **{
1005
+ k.replace(":", "_COLON_"): v
1006
+ for k, v in format_variables.items()
1007
+ },
1008
+ )
1009
+ )
999
1010
  except (ValueError, TypeError) as e:
1000
1011
  raise MisconfiguredError(
1001
1012
  f"Unable to format str={str_to_format} using {str(format_variables)}: {str(e)}"
@@ -1601,3 +1612,65 @@ def parse_le_uint16(data: bytes) -> int:
1601
1612
  65535
1602
1613
  """
1603
1614
  return struct.unpack("<H", data)[0]
1615
+
1616
+
1617
+ def format_pydantic_error(e: PydanticValidationError) -> str:
1618
+ """Format Pydantic ValidationError
1619
+
1620
+ :param e: A Pydantic ValidationError object
1621
+ :type e: PydanticValidationError
1622
+ """
1623
+ error_header = f"{e.error_count()} error(s). "
1624
+
1625
+ error_messages = [
1626
+ f'{err["loc"][0]}: {err["msg"]}' if err["loc"] else err["msg"]
1627
+ for err in e.errors()
1628
+ ]
1629
+ return error_header + "; ".join(set(error_messages))
1630
+
1631
+
1632
+ def get_collection_dates(
1633
+ collection_dict: dict[str, Any]
1634
+ ) -> tuple[Optional[str], Optional[str]]:
1635
+ """Extract mission start and end dates from collection configuration.
1636
+
1637
+ Extracts dates from the extent.temporal.interval structure.
1638
+
1639
+ :param collection_dict: Collection configuration dictionary
1640
+ :returns: Tuple of (mission_start_date, mission_end_date) as ISO strings or None
1641
+
1642
+ Example:
1643
+ >>> get_collection_dates({
1644
+ ... "extent": {"temporal": {"interval": [["2017-10-13T00:00:00Z", "2023-12-31T23:59:59Z"]]}}
1645
+ ... })
1646
+ ('2017-10-13T00:00:00Z', '2023-12-31T23:59:59Z')
1647
+
1648
+ >>> get_collection_dates({
1649
+ ... "extent": {"temporal": {"interval": [["2017-10-13T00:00:00Z", None]]}}
1650
+ ... })
1651
+ ('2017-10-13T00:00:00Z', None)
1652
+
1653
+ >>> get_collection_dates({})
1654
+ (None, None)
1655
+ """
1656
+ extent_interval = (
1657
+ collection_dict.get("extent", {})
1658
+ .get("temporal", {})
1659
+ .get("interval", [[None, None]])
1660
+ )
1661
+
1662
+ if not extent_interval or len(extent_interval) == 0:
1663
+ return None, None
1664
+
1665
+ mission_start = (
1666
+ extent_interval[0][0]
1667
+ if len(extent_interval) > 0 and len(extent_interval[0]) > 0
1668
+ else None
1669
+ )
1670
+ mission_end = (
1671
+ extent_interval[0][1]
1672
+ if len(extent_interval) > 0 and len(extent_interval[0]) > 1
1673
+ else None
1674
+ )
1675
+
1676
+ return mission_start, mission_end
eodag/utils/exceptions.py CHANGED
@@ -49,11 +49,11 @@ class UnsupportedProvider(EodagError):
49
49
  """An error indicating that eodag does not support a provider"""
50
50
 
51
51
 
52
- class UnsupportedProductType(EodagError):
53
- """An error indicating that eodag does not support a product type"""
52
+ class UnsupportedCollection(EodagError):
53
+ """An error indicating that eodag does not support a collection"""
54
54
 
55
- def __init__(self, product_type: str) -> None:
56
- self.product_type = product_type
55
+ def __init__(self, collection: str) -> None:
56
+ self.collection = collection
57
57
 
58
58
 
59
59
  class UnsupportedDatasetAddressScheme(EodagError):
@@ -70,8 +70,8 @@ class NotAvailableError(EodagError):
70
70
  """An error indicating that the product is not available for download"""
71
71
 
72
72
 
73
- class NoMatchingProductType(EodagError):
74
- """An error indicating that eodag was unable to derive a product type from a set
73
+ class NoMatchingCollection(EodagError):
74
+ """An error indicating that eodag was unable to derive a collection from a set
75
75
  of search parameters"""
76
76
 
77
77
 
@@ -197,17 +197,17 @@ def compile_free_text_query(query: str) -> Callable[[dict[str, str]], bool]:
197
197
  >>> evaluator = compile_free_text_query('("FooAndBar" OR BAR) AND "FOOBAR collection"')
198
198
  >>> evaluator({
199
199
  ... "title": "titleFOOBAR - Lorem FOOBAR collection",
200
- ... "abstract": "abstract FOOBAR - This is FOOBAR. FooAndBar"
200
+ ... "description": "abstract FOOBAR - This is FOOBAR. FooAndBar"
201
201
  ... })
202
202
  True
203
203
  >>> evaluator({
204
204
  ... "title": "collection FOOBAR",
205
- ... "abstract": "abstract FOOBAR - This is FOOBAR. FooAndBar"
205
+ ... "description": "abstract FOOBAR - This is FOOBAR. FooAndBar"
206
206
  ... })
207
207
  False
208
208
  >>> evaluator({
209
209
  ... "title": "titleFOOBAR - Lorem FOOBAR ",
210
- ... "abstract": "abstract FOOBAR - This is FOOBAR."
210
+ ... "description": "abstract FOOBAR - This is FOOBAR."
211
211
  ... })
212
212
  False
213
213
  >>> evaluator({"title": "Only Bar here"})