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.
- eodag/__init__.py +6 -1
- eodag/api/collection.py +353 -0
- eodag/api/core.py +606 -641
- eodag/api/product/__init__.py +3 -3
- eodag/api/product/_product.py +74 -56
- eodag/api/product/drivers/__init__.py +4 -46
- eodag/api/product/drivers/base.py +0 -28
- eodag/api/product/metadata_mapping.py +178 -216
- eodag/api/search_result.py +156 -15
- eodag/cli.py +83 -403
- eodag/config.py +81 -51
- eodag/plugins/apis/base.py +2 -2
- eodag/plugins/apis/ecmwf.py +36 -25
- eodag/plugins/apis/usgs.py +55 -40
- eodag/plugins/authentication/base.py +1 -3
- eodag/plugins/crunch/filter_date.py +3 -3
- eodag/plugins/crunch/filter_latest_intersect.py +2 -2
- eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
- eodag/plugins/download/aws.py +46 -42
- eodag/plugins/download/base.py +13 -14
- eodag/plugins/download/http.py +65 -65
- eodag/plugins/manager.py +28 -29
- eodag/plugins/search/__init__.py +6 -4
- eodag/plugins/search/base.py +131 -80
- eodag/plugins/search/build_search_result.py +245 -173
- eodag/plugins/search/cop_marine.py +87 -56
- eodag/plugins/search/csw.py +47 -37
- eodag/plugins/search/qssearch.py +653 -429
- eodag/plugins/search/stac_list_assets.py +1 -1
- eodag/plugins/search/static_stac_search.py +43 -44
- eodag/resources/{product_types.yml → collections.yml} +2594 -2453
- eodag/resources/ext_collections.json +1 -1
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/providers.yml +2706 -2733
- eodag/resources/stac_provider.yml +50 -92
- eodag/resources/user_conf_template.yml +9 -0
- eodag/types/__init__.py +2 -0
- eodag/types/queryables.py +70 -91
- eodag/types/search_args.py +1 -1
- eodag/utils/__init__.py +97 -21
- eodag/utils/dates.py +0 -12
- eodag/utils/exceptions.py +6 -6
- eodag/utils/free_text_search.py +3 -3
- eodag/utils/repr.py +2 -0
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/METADATA +13 -99
- eodag-4.0.0a2.dist-info/RECORD +93 -0
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/entry_points.txt +0 -4
- eodag/plugins/authentication/oauth.py +0 -60
- eodag/plugins/download/creodias_s3.py +0 -71
- eodag/plugins/download/s3rest.py +0 -351
- eodag/plugins/search/data_request_search.py +0 -565
- eodag/resources/stac.yml +0 -294
- eodag/resources/stac_api.yml +0 -2105
- eodag/rest/__init__.py +0 -24
- eodag/rest/cache.py +0 -70
- eodag/rest/config.py +0 -67
- eodag/rest/constants.py +0 -26
- eodag/rest/core.py +0 -764
- eodag/rest/errors.py +0 -210
- eodag/rest/server.py +0 -604
- eodag/rest/server.wsgi +0 -6
- eodag/rest/stac.py +0 -1032
- eodag/rest/templates/README +0 -1
- eodag/rest/types/__init__.py +0 -18
- eodag/rest/types/collections_search.py +0 -44
- eodag/rest/types/eodag_search.py +0 -386
- eodag/rest/types/queryables.py +0 -174
- eodag/rest/types/stac_search.py +0 -272
- eodag/rest/utils/__init__.py +0 -207
- eodag/rest/utils/cql_evaluate.py +0 -119
- eodag/rest/utils/rfc3339.py +0 -64
- eodag-3.10.1.dist-info/RECORD +0 -116
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/WHEEL +0 -0
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/licenses/LICENSE +0 -0
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/top_level.txt +0 -0
eodag/rest/templates/README
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Empty templates directory kept for compatibility reasons
|
eodag/rest/types/__init__.py
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
# Copyright 2023, 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
|
-
"""EODAG rest.types package"""
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
# Copyright 2023, 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
|
-
from typing import Any, Optional
|
|
19
|
-
|
|
20
|
-
from pydantic import (
|
|
21
|
-
BaseModel,
|
|
22
|
-
ConfigDict,
|
|
23
|
-
Field,
|
|
24
|
-
SerializerFunctionWrapHandler,
|
|
25
|
-
model_serializer,
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
from eodag.rest.types.eodag_search import EODAGSearch
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class CollectionsSearchRequest(BaseModel):
|
|
32
|
-
"""Search args for GET collections"""
|
|
33
|
-
|
|
34
|
-
model_config = ConfigDict(frozen=True)
|
|
35
|
-
|
|
36
|
-
q: Optional[str] = Field(default=None, serialization_alias="free_text")
|
|
37
|
-
platform: Optional[str] = Field(default=None)
|
|
38
|
-
instrument: Optional[str] = Field(default=None)
|
|
39
|
-
constellation: Optional[str] = Field(default=None)
|
|
40
|
-
|
|
41
|
-
@model_serializer(mode="wrap")
|
|
42
|
-
def _serialize(self, handler: SerializerFunctionWrapHandler) -> dict[str, Any]:
|
|
43
|
-
dumped: dict[str, Any] = handler(self)
|
|
44
|
-
return {EODAGSearch.to_eodag(k): v for k, v in dumped.items()}
|
eodag/rest/types/eodag_search.py
DELETED
|
@@ -1,386 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
# Copyright 2023, 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
|
-
from __future__ import annotations
|
|
19
|
-
|
|
20
|
-
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
21
|
-
|
|
22
|
-
from pydantic import (
|
|
23
|
-
AliasChoices,
|
|
24
|
-
AliasPath,
|
|
25
|
-
BaseModel,
|
|
26
|
-
ConfigDict,
|
|
27
|
-
Field,
|
|
28
|
-
ValidationError,
|
|
29
|
-
ValidationInfo,
|
|
30
|
-
field_validator,
|
|
31
|
-
model_validator,
|
|
32
|
-
)
|
|
33
|
-
from pydantic.alias_generators import to_camel, to_snake
|
|
34
|
-
from pydantic_core import InitErrorDetails, PydanticCustomError
|
|
35
|
-
from pygeofilter.parsers.cql2_json import parse as parse_json
|
|
36
|
-
from shapely.geometry import (
|
|
37
|
-
GeometryCollection,
|
|
38
|
-
LinearRing,
|
|
39
|
-
LineString,
|
|
40
|
-
MultiLineString,
|
|
41
|
-
MultiPoint,
|
|
42
|
-
MultiPolygon,
|
|
43
|
-
Point,
|
|
44
|
-
Polygon,
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
from eodag.rest.utils import flatten_list, is_dict_str_any, list_to_str_list
|
|
48
|
-
from eodag.rest.utils.cql_evaluate import EodagEvaluator
|
|
49
|
-
from eodag.utils import DEFAULT_ITEMS_PER_PAGE
|
|
50
|
-
|
|
51
|
-
if TYPE_CHECKING:
|
|
52
|
-
from typing_extensions import Self
|
|
53
|
-
|
|
54
|
-
Geometry = Union[
|
|
55
|
-
dict[str, Any],
|
|
56
|
-
Point,
|
|
57
|
-
MultiPoint,
|
|
58
|
-
LineString,
|
|
59
|
-
MultiLineString,
|
|
60
|
-
Polygon,
|
|
61
|
-
MultiPolygon,
|
|
62
|
-
LinearRing,
|
|
63
|
-
GeometryCollection,
|
|
64
|
-
]
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class EODAGSearch(BaseModel):
|
|
68
|
-
"""Model used to convert a STAC formated request to an EODAG formated one"""
|
|
69
|
-
|
|
70
|
-
model_config = ConfigDict(
|
|
71
|
-
extra="allow", populate_by_name=True, arbitrary_types_allowed=True
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
productType: Optional[str] = Field(None, alias="collections", validate_default=True)
|
|
75
|
-
provider: Optional[str] = Field(None)
|
|
76
|
-
ids: Optional[list[str]] = Field(None)
|
|
77
|
-
id: Optional[list[str]] = Field(
|
|
78
|
-
None, alias="ids"
|
|
79
|
-
) # TODO: remove when updating queryables
|
|
80
|
-
geom: Optional[Geometry] = Field(None, alias="geometry")
|
|
81
|
-
start: Optional[str] = Field(None, alias="start_datetime")
|
|
82
|
-
end: Optional[str] = Field(None, alias="end_datetime")
|
|
83
|
-
startTimeFromAscendingNode: Optional[str] = Field(
|
|
84
|
-
None,
|
|
85
|
-
alias="start_datetime",
|
|
86
|
-
validation_alias=AliasChoices("start_datetime", "datetime"),
|
|
87
|
-
)
|
|
88
|
-
completionTimeFromAscendingNode: Optional[str] = Field(None, alias="end_datetime")
|
|
89
|
-
publicationDate: Optional[str] = Field(None, alias="published")
|
|
90
|
-
creationDate: Optional[str] = Field(None, alias="created")
|
|
91
|
-
modificationDate: Optional[str] = Field(None, alias="updated")
|
|
92
|
-
platformSerialIdentifier: Optional[str] = Field(None, alias="platform")
|
|
93
|
-
instrument: Optional[str] = Field(None, alias="instruments")
|
|
94
|
-
platform: Optional[str] = Field(None, alias="constellation")
|
|
95
|
-
resolution: Optional[Union[int, str]] = Field(None, alias="gsd")
|
|
96
|
-
cloudCover: Optional[int] = Field(None, alias="eo:cloud_cover")
|
|
97
|
-
snowCover: Optional[int] = Field(None, alias="eo:snow_cover")
|
|
98
|
-
processingLevel: Optional[str] = Field(None, alias="processing:level")
|
|
99
|
-
orbitDirection: Optional[str] = Field(None, alias="sat:orbit_state")
|
|
100
|
-
relativeOrbitNumber: Optional[int] = Field(None, alias="sat:relative_orbit")
|
|
101
|
-
orbitNumber: Optional[int] = Field(None, alias="sat:absolute_orbit")
|
|
102
|
-
# TODO: colision in property name. Need to handle "sar:product_type"
|
|
103
|
-
sensorMode: Optional[str] = Field(None, alias="sar:instrument_mode")
|
|
104
|
-
polarizationChannels: Optional[list[str]] = Field(None, alias="sar:polarizations")
|
|
105
|
-
dopplerFrequency: Optional[str] = Field(None, alias="sar:frequency_band")
|
|
106
|
-
doi: Optional[str] = Field(None, alias="sci:doi")
|
|
107
|
-
illuminationElevationAngle: Optional[float] = Field(
|
|
108
|
-
None, alias="view:sun_elevation"
|
|
109
|
-
)
|
|
110
|
-
illuminationAzimuthAngle: Optional[float] = Field(None, alias="view:sun_azimuth")
|
|
111
|
-
page: Optional[int] = Field(1)
|
|
112
|
-
items_per_page: int = Field(DEFAULT_ITEMS_PER_PAGE, alias="limit")
|
|
113
|
-
sort_by: Optional[list[tuple[str, str]]] = Field(None, alias="sortby")
|
|
114
|
-
raise_errors: bool = False
|
|
115
|
-
|
|
116
|
-
_to_eodag_map: dict[str, str]
|
|
117
|
-
|
|
118
|
-
@model_validator(mode="after")
|
|
119
|
-
def remove_timeFromAscendingNode(self) -> Self: # pylint: disable=invalid-name
|
|
120
|
-
"""TimeFromAscendingNode are just used for translation and not for search"""
|
|
121
|
-
self.startTimeFromAscendingNode = None # pylint: disable=invalid-name
|
|
122
|
-
self.completionTimeFromAscendingNode = None # pylint: disable=invalid-name
|
|
123
|
-
return self
|
|
124
|
-
|
|
125
|
-
@model_validator(mode="after")
|
|
126
|
-
def parse_extra_fields(self) -> Self:
|
|
127
|
-
"""process unknown and oseo EODAG custom extensions fields"""
|
|
128
|
-
# Transform EODAG custom extensions OSEO and UNK.
|
|
129
|
-
if not self.__pydantic_extra__:
|
|
130
|
-
return self
|
|
131
|
-
|
|
132
|
-
keys_to_update: dict[str, str] = {}
|
|
133
|
-
for key in self.__pydantic_extra__.keys():
|
|
134
|
-
if key.startswith("unk:"):
|
|
135
|
-
keys_to_update[key] = key[len("unk:") :]
|
|
136
|
-
elif key.startswith("oseo:"):
|
|
137
|
-
keys_to_update[key] = key[len("oseo:") :]
|
|
138
|
-
|
|
139
|
-
for old_key, new_key in keys_to_update.items():
|
|
140
|
-
self.__pydantic_extra__[
|
|
141
|
-
to_camel(to_snake(new_key))
|
|
142
|
-
] = self.__pydantic_extra__.pop(old_key)
|
|
143
|
-
|
|
144
|
-
return self
|
|
145
|
-
|
|
146
|
-
@model_validator(mode="before")
|
|
147
|
-
@classmethod
|
|
148
|
-
def remove_keys(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
149
|
-
"""Remove 'datetime', 'crunch', 'intersects', and 'bbox' keys"""
|
|
150
|
-
for key in ["datetime", "crunch", "intersects", "bbox", "filter_lang"]:
|
|
151
|
-
values.pop(key, None)
|
|
152
|
-
return values
|
|
153
|
-
|
|
154
|
-
@model_validator(mode="before")
|
|
155
|
-
@classmethod
|
|
156
|
-
def parse_collections(
|
|
157
|
-
cls, values: dict[str, Any], info: ValidationInfo
|
|
158
|
-
) -> dict[str, Any]:
|
|
159
|
-
"""convert collections to productType"""
|
|
160
|
-
|
|
161
|
-
if collections := values.pop("collections", None):
|
|
162
|
-
if len(collections) > 1:
|
|
163
|
-
raise ValueError("Only one collection is supported per search")
|
|
164
|
-
values["productType"] = collections[0]
|
|
165
|
-
else:
|
|
166
|
-
if not getattr(info, "context", None) or not info.context.get( # type: ignore
|
|
167
|
-
"isCatalog"
|
|
168
|
-
):
|
|
169
|
-
raise ValueError("A collection is required")
|
|
170
|
-
|
|
171
|
-
return values
|
|
172
|
-
|
|
173
|
-
@model_validator(mode="before")
|
|
174
|
-
@classmethod
|
|
175
|
-
def parse_query(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
176
|
-
"""
|
|
177
|
-
Convert a STAC query parameter filter with the "eq" operator to a dict.
|
|
178
|
-
"""
|
|
179
|
-
|
|
180
|
-
def add_error(error_message: str, input: Any) -> None:
|
|
181
|
-
errors.append(
|
|
182
|
-
InitErrorDetails(
|
|
183
|
-
type=PydanticCustomError("invalid_query", error_message), # type: ignore
|
|
184
|
-
loc=("query",),
|
|
185
|
-
input=input,
|
|
186
|
-
)
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
query = values.pop("query", None)
|
|
190
|
-
if not query:
|
|
191
|
-
return values
|
|
192
|
-
|
|
193
|
-
query_props: dict[str, Any] = {}
|
|
194
|
-
errors: list[InitErrorDetails] = []
|
|
195
|
-
for property_name, conditions in cast(dict[str, Any], query).items():
|
|
196
|
-
# Remove the prefix "properties." if present
|
|
197
|
-
prop = property_name.replace("properties.", "", 1)
|
|
198
|
-
|
|
199
|
-
# Check if exactly one operator is specified per property
|
|
200
|
-
if not is_dict_str_any(conditions) or len(conditions) != 1: # type: ignore
|
|
201
|
-
add_error(
|
|
202
|
-
"Exactly 1 operator must be specified per property",
|
|
203
|
-
query[property_name],
|
|
204
|
-
)
|
|
205
|
-
continue
|
|
206
|
-
|
|
207
|
-
# Retrieve the operator and its value
|
|
208
|
-
operator, value = next(iter(cast(dict[str, Any], conditions).items()))
|
|
209
|
-
|
|
210
|
-
# Validate the operator
|
|
211
|
-
# only eq, in and lte are allowed
|
|
212
|
-
# lte is only supported with eo:cloud_cover
|
|
213
|
-
# eo:cloud_cover only accept lte operator
|
|
214
|
-
if (
|
|
215
|
-
operator not in ("eq", "lte", "in")
|
|
216
|
-
or (operator == "lte" and prop != "eo:cloud_cover")
|
|
217
|
-
or (prop == "eo:cloud_cover" and operator != "lte")
|
|
218
|
-
):
|
|
219
|
-
add_error(
|
|
220
|
-
f'operator "{operator}" is not supported for property "{prop}"',
|
|
221
|
-
query[property_name],
|
|
222
|
-
)
|
|
223
|
-
continue
|
|
224
|
-
if operator == "in" and not isinstance(value, list):
|
|
225
|
-
add_error(
|
|
226
|
-
f'operator "{operator}" requires a value of type list for property "{prop}"',
|
|
227
|
-
query[property_name],
|
|
228
|
-
)
|
|
229
|
-
continue
|
|
230
|
-
|
|
231
|
-
query_props[prop] = value
|
|
232
|
-
|
|
233
|
-
if errors:
|
|
234
|
-
raise ValidationError.from_exception_data(
|
|
235
|
-
title=cls.__name__, line_errors=errors
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
return {**values, **query_props}
|
|
239
|
-
|
|
240
|
-
@model_validator(mode="before")
|
|
241
|
-
@classmethod
|
|
242
|
-
def parse_cql(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
243
|
-
"""
|
|
244
|
-
Process cql2 filter
|
|
245
|
-
"""
|
|
246
|
-
|
|
247
|
-
def add_error(error_message: str) -> None:
|
|
248
|
-
errors.append(
|
|
249
|
-
InitErrorDetails(
|
|
250
|
-
type=PydanticCustomError("invalid_filter", error_message), # type: ignore
|
|
251
|
-
loc=("filter",),
|
|
252
|
-
)
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
filter_ = values.pop("filter", None)
|
|
256
|
-
if not filter_:
|
|
257
|
-
return values
|
|
258
|
-
|
|
259
|
-
errors: list[InitErrorDetails] = []
|
|
260
|
-
try:
|
|
261
|
-
parsing_result = EodagEvaluator().evaluate(parse_json(filter_)) # type: ignore
|
|
262
|
-
except (ValueError, NotImplementedError) as e:
|
|
263
|
-
add_error(str(e))
|
|
264
|
-
raise ValidationError.from_exception_data(
|
|
265
|
-
title=cls.__name__, line_errors=errors
|
|
266
|
-
) from e
|
|
267
|
-
|
|
268
|
-
if not is_dict_str_any(parsing_result):
|
|
269
|
-
add_error("The parsed filter is not a proper dictionary")
|
|
270
|
-
raise ValidationError.from_exception_data(
|
|
271
|
-
title=cls.__name__, line_errors=errors
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
cql_args: dict[str, Any] = cast(dict[str, Any], parsing_result)
|
|
275
|
-
|
|
276
|
-
invalid_keys = {
|
|
277
|
-
"collections": 'Use "collection" instead of "collections"',
|
|
278
|
-
"ids": 'Use "id" instead of "ids"',
|
|
279
|
-
}
|
|
280
|
-
for k, m in invalid_keys.items():
|
|
281
|
-
if k in cql_args:
|
|
282
|
-
add_error(m)
|
|
283
|
-
|
|
284
|
-
if errors:
|
|
285
|
-
raise ValidationError.from_exception_data(
|
|
286
|
-
title=cls.__name__, line_errors=errors
|
|
287
|
-
)
|
|
288
|
-
|
|
289
|
-
# convert collection to EODAG collections
|
|
290
|
-
if col := cql_args.pop("collection", None):
|
|
291
|
-
cql_args["collections"] = col if isinstance(col, list) else [col]
|
|
292
|
-
|
|
293
|
-
# convert id to EODAG ids
|
|
294
|
-
if id := cql_args.pop("id", None):
|
|
295
|
-
cql_args["ids"] = id if isinstance(id, list) else [id]
|
|
296
|
-
|
|
297
|
-
return {**values, **cql_args}
|
|
298
|
-
|
|
299
|
-
@field_validator("instrument", mode="before")
|
|
300
|
-
@classmethod
|
|
301
|
-
def join_instruments(cls, v: Union[str, list[str]]) -> str:
|
|
302
|
-
"""convert instruments to instrument"""
|
|
303
|
-
if isinstance(v, list):
|
|
304
|
-
return ",".join(v)
|
|
305
|
-
return v
|
|
306
|
-
|
|
307
|
-
@field_validator("sort_by", mode="before")
|
|
308
|
-
@classmethod
|
|
309
|
-
def parse_sortby(
|
|
310
|
-
cls,
|
|
311
|
-
sortby_post_params: list[dict[str, str]],
|
|
312
|
-
) -> list[tuple[str, str]]:
|
|
313
|
-
"""
|
|
314
|
-
Convert STAC POST sortby to EODAG sort_by
|
|
315
|
-
"""
|
|
316
|
-
special_fields = {
|
|
317
|
-
"start": "startTimeFromAscendingNode",
|
|
318
|
-
"end": "completionTimeFromAscendingNode",
|
|
319
|
-
}
|
|
320
|
-
return [
|
|
321
|
-
(
|
|
322
|
-
special_fields.get(
|
|
323
|
-
to_camel(to_snake(cls.to_eodag(param["field"]))),
|
|
324
|
-
to_camel(to_snake(cls.to_eodag(param["field"]))),
|
|
325
|
-
),
|
|
326
|
-
param["direction"],
|
|
327
|
-
)
|
|
328
|
-
for param in sortby_post_params
|
|
329
|
-
]
|
|
330
|
-
|
|
331
|
-
@field_validator("start", "end")
|
|
332
|
-
@classmethod
|
|
333
|
-
def cleanup_dates(cls, v: str) -> str:
|
|
334
|
-
"""proper format dates"""
|
|
335
|
-
if v.endswith("+00:00"):
|
|
336
|
-
return v.replace("+00:00", "") + "Z"
|
|
337
|
-
return v
|
|
338
|
-
|
|
339
|
-
@classmethod
|
|
340
|
-
def _create_to_eodag_map(cls) -> None:
|
|
341
|
-
"""Create mapping to convert fields from STAC to EODAG"""
|
|
342
|
-
cls._to_eodag_map = {}
|
|
343
|
-
for name, field_info in cls.model_fields.items():
|
|
344
|
-
if field_info.validation_alias:
|
|
345
|
-
if isinstance(field_info.validation_alias, (AliasChoices, AliasPath)):
|
|
346
|
-
for a in list_to_str_list(
|
|
347
|
-
flatten_list(field_info.validation_alias.convert_to_aliases())
|
|
348
|
-
):
|
|
349
|
-
cls._to_eodag_map[a] = name
|
|
350
|
-
else:
|
|
351
|
-
cls._to_eodag_map[field_info.validation_alias] = name
|
|
352
|
-
elif field_info.alias:
|
|
353
|
-
cls._to_eodag_map[field_info.alias] = name
|
|
354
|
-
|
|
355
|
-
@classmethod
|
|
356
|
-
def to_eodag(cls, value: str) -> str:
|
|
357
|
-
"""Convert a STAC parameter to its matching EODAG name"""
|
|
358
|
-
if not isinstance(cls._to_eodag_map, dict) or not cls._to_eodag_map:
|
|
359
|
-
cls._create_to_eodag_map()
|
|
360
|
-
return cls._to_eodag_map.get(value, value)
|
|
361
|
-
|
|
362
|
-
@classmethod
|
|
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:
|
|
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"
|
|
376
|
-
field = cls.model_fields.get(field_name)
|
|
377
|
-
if field is not None and field.alias is not None:
|
|
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}"
|
|
386
|
-
return field_name
|
eodag/rest/types/queryables.py
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
# Copyright 2023, 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
|
-
from __future__ import annotations
|
|
19
|
-
|
|
20
|
-
from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Optional, Union
|
|
21
|
-
|
|
22
|
-
from pydantic import (
|
|
23
|
-
BaseModel,
|
|
24
|
-
ConfigDict,
|
|
25
|
-
Field,
|
|
26
|
-
SerializationInfo,
|
|
27
|
-
SerializerFunctionWrapHandler,
|
|
28
|
-
field_validator,
|
|
29
|
-
model_serializer,
|
|
30
|
-
model_validator,
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
from eodag.rest.types.eodag_search import EODAGSearch
|
|
34
|
-
from eodag.rest.utils.rfc3339 import str_to_interval
|
|
35
|
-
from eodag.types import python_field_definition_to_json
|
|
36
|
-
|
|
37
|
-
if TYPE_CHECKING:
|
|
38
|
-
from pydantic.fields import FieldInfo
|
|
39
|
-
from typing_extensions import Self
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class QueryablesGetParams(BaseModel):
|
|
43
|
-
"""Store GET Queryables query params"""
|
|
44
|
-
|
|
45
|
-
collection: Optional[str] = Field(default=None, serialization_alias="productType")
|
|
46
|
-
datetime: Optional[str] = Field(default=None)
|
|
47
|
-
start_datetime: Optional[str] = Field(default=None)
|
|
48
|
-
end_datetime: Optional[str] = Field(default=None)
|
|
49
|
-
|
|
50
|
-
model_config = ConfigDict(extra="allow", frozen=True)
|
|
51
|
-
|
|
52
|
-
@model_serializer(mode="wrap")
|
|
53
|
-
def _serialize(self, handler: SerializerFunctionWrapHandler) -> dict[str, Any]:
|
|
54
|
-
dumped: dict[str, Any] = handler(self)
|
|
55
|
-
return {EODAGSearch.to_eodag(k): v for k, v in dumped.items()}
|
|
56
|
-
|
|
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]
|
|
62
|
-
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
class StacQueryableProperty(BaseModel):
|
|
83
|
-
"""A class representing a queryable property.
|
|
84
|
-
|
|
85
|
-
:param description: The description of the queryables property
|
|
86
|
-
:param ref: (optional) A reference link to the schema of the property.
|
|
87
|
-
:param type: (optional) possible types of the property
|
|
88
|
-
"""
|
|
89
|
-
|
|
90
|
-
description: str
|
|
91
|
-
ref: Optional[str] = Field(default=None, serialization_alias="$ref")
|
|
92
|
-
type: Optional[Union[str, list[str]]] = None
|
|
93
|
-
enum: Optional[list[Any]] = None
|
|
94
|
-
value: Optional[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
|
|
98
|
-
items: Optional[Any] = None
|
|
99
|
-
|
|
100
|
-
@classmethod
|
|
101
|
-
def from_python_field_definition(
|
|
102
|
-
cls, id: str, python_field_definition: Annotated[Any, FieldInfo]
|
|
103
|
-
) -> StacQueryableProperty:
|
|
104
|
-
"""Build Model from python_field_definition"""
|
|
105
|
-
def_dict = python_field_definition_to_json(python_field_definition)
|
|
106
|
-
|
|
107
|
-
if not def_dict.get("description"):
|
|
108
|
-
def_dict["description"] = def_dict.get("title") or id
|
|
109
|
-
|
|
110
|
-
return cls(**def_dict)
|
|
111
|
-
|
|
112
|
-
@model_serializer(mode="wrap")
|
|
113
|
-
def remove_none(
|
|
114
|
-
self,
|
|
115
|
-
handler: SerializerFunctionWrapHandler,
|
|
116
|
-
_: SerializationInfo,
|
|
117
|
-
):
|
|
118
|
-
"""Remove none value property fields during serialization"""
|
|
119
|
-
props: dict[str, Any] = handler(self)
|
|
120
|
-
return {k: v for k, v in props.items() if v is not None}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
class StacQueryables(BaseModel):
|
|
124
|
-
"""A class representing queryable properties for the STAC API.
|
|
125
|
-
|
|
126
|
-
:param json_schema: The URL of the JSON schema.
|
|
127
|
-
:param q_id: (optional) The identifier of the queryables.
|
|
128
|
-
:param q_type: The type of the object.
|
|
129
|
-
:param title: The title of the queryables.
|
|
130
|
-
:param description: The description of the queryables
|
|
131
|
-
:param properties: A dictionary of queryable properties.
|
|
132
|
-
:param additional_properties: Whether additional properties are allowed.
|
|
133
|
-
"""
|
|
134
|
-
|
|
135
|
-
json_schema: str = Field(
|
|
136
|
-
default="https://json-schema.org/draft/2019-09/schema",
|
|
137
|
-
serialization_alias="$schema",
|
|
138
|
-
)
|
|
139
|
-
q_id: Optional[str] = Field(default=None, serialization_alias="$id")
|
|
140
|
-
q_type: str = Field(default="object", serialization_alias="type")
|
|
141
|
-
title: str = Field(default="Queryables for EODAG STAC API")
|
|
142
|
-
description: str = Field(
|
|
143
|
-
default="Queryable names for the EODAG STAC API Item Search filter."
|
|
144
|
-
)
|
|
145
|
-
default_properties: ClassVar[dict[str, StacQueryableProperty]] = {
|
|
146
|
-
"collection": StacQueryableProperty(
|
|
147
|
-
description="Collection",
|
|
148
|
-
ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/collection",
|
|
149
|
-
)
|
|
150
|
-
}
|
|
151
|
-
possible_properties: ClassVar[dict[str, StacQueryableProperty]] = {
|
|
152
|
-
"geometry": StacQueryableProperty(
|
|
153
|
-
description="Geometry",
|
|
154
|
-
ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/geometry",
|
|
155
|
-
),
|
|
156
|
-
"datetime": StacQueryableProperty(
|
|
157
|
-
description="Datetime - use parameters year, month, day, time instead if available",
|
|
158
|
-
ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#/properties/datetime",
|
|
159
|
-
),
|
|
160
|
-
"bbox": StacQueryableProperty(
|
|
161
|
-
description="BBox",
|
|
162
|
-
type="array",
|
|
163
|
-
oneOf=[{"minItems": 4, "maxItems": 4}, {"minItems": 6, "maxItems": 6}],
|
|
164
|
-
items={"type": "number"},
|
|
165
|
-
),
|
|
166
|
-
}
|
|
167
|
-
properties: dict[str, Any] = Field()
|
|
168
|
-
required: Optional[list[str]] = Field(None)
|
|
169
|
-
additional_properties: bool = Field(
|
|
170
|
-
default=True, serialization_alias="additionalProperties"
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
def __contains__(self, name: str) -> bool:
|
|
174
|
-
return name in self.properties
|