eodag 4.0.0a5__py3-none-any.whl → 4.0.0b1__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/api/collection.py +65 -1
- eodag/api/core.py +48 -16
- eodag/api/product/_assets.py +1 -1
- eodag/api/product/_product.py +108 -15
- eodag/api/product/drivers/__init__.py +3 -1
- eodag/api/product/drivers/base.py +3 -1
- eodag/api/product/drivers/generic.py +9 -5
- eodag/api/product/drivers/sentinel1.py +14 -9
- eodag/api/product/drivers/sentinel2.py +14 -7
- eodag/api/product/metadata_mapping.py +5 -2
- eodag/api/provider.py +1 -0
- eodag/api/search_result.py +4 -1
- eodag/cli.py +7 -7
- eodag/config.py +22 -4
- eodag/plugins/download/aws.py +3 -1
- eodag/plugins/download/http.py +4 -10
- eodag/plugins/search/base.py +8 -3
- eodag/plugins/search/build_search_result.py +108 -120
- eodag/plugins/search/cop_marine.py +3 -1
- eodag/plugins/search/qssearch.py +7 -6
- eodag/resources/collections.yml +255 -0
- eodag/resources/ext_collections.json +1 -1
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/providers.yml +60 -25
- eodag/resources/user_conf_template.yml +6 -0
- eodag/types/__init__.py +22 -16
- eodag/types/download_args.py +3 -1
- eodag/types/queryables.py +125 -55
- eodag/types/stac_extensions.py +408 -0
- eodag/types/stac_metadata.py +312 -0
- eodag/utils/__init__.py +42 -4
- eodag/utils/dates.py +202 -2
- {eodag-4.0.0a5.dist-info → eodag-4.0.0b1.dist-info}/METADATA +7 -13
- {eodag-4.0.0a5.dist-info → eodag-4.0.0b1.dist-info}/RECORD +38 -36
- {eodag-4.0.0a5.dist-info → eodag-4.0.0b1.dist-info}/WHEEL +1 -1
- {eodag-4.0.0a5.dist-info → eodag-4.0.0b1.dist-info}/entry_points.txt +1 -1
- {eodag-4.0.0a5.dist-info → eodag-4.0.0b1.dist-info}/licenses/LICENSE +0 -0
- {eodag-4.0.0a5.dist-info → eodag-4.0.0b1.dist-info}/top_level.txt +0 -0
eodag/resources/providers.yml
CHANGED
|
@@ -590,9 +590,6 @@
|
|
|
590
590
|
version: '$.properties.version'
|
|
591
591
|
created: '$.properties.dhusIngestDate'
|
|
592
592
|
updated: '$.properties.updated'
|
|
593
|
-
sar:instrument_mode:
|
|
594
|
-
- 'sensorMode'
|
|
595
|
-
- '$.properties.sensorMode'
|
|
596
593
|
|
|
597
594
|
# OpenSearch Parameters for Acquistion Parameters Search (Table 6)
|
|
598
595
|
start_datetime:
|
|
@@ -800,7 +797,7 @@
|
|
|
800
797
|
- '$.Attributes.processingCenter'
|
|
801
798
|
processing:version:
|
|
802
799
|
- null
|
|
803
|
-
- '$.Attributes.processorVersion'
|
|
800
|
+
- '{$.Attributes.processorVersion#to_geojson}'
|
|
804
801
|
_processor_name:
|
|
805
802
|
- null
|
|
806
803
|
- '$.Attributes.processorName'
|
|
@@ -1676,12 +1673,9 @@
|
|
|
1676
1673
|
CAMS_SOLAR_RADIATION:
|
|
1677
1674
|
dataset: cams-solar-radiation-timeseries
|
|
1678
1675
|
metadata_mapping:
|
|
1679
|
-
|
|
1680
|
-
- '{{"location": {{"
|
|
1681
|
-
- $.
|
|
1682
|
-
longitude:
|
|
1683
|
-
- '{{"location": {{"latitude": {"latitude"}, "longitude": {"longitude"}}}}}'
|
|
1684
|
-
- $."longitude"
|
|
1676
|
+
geometry:
|
|
1677
|
+
- '{{"location": {{"longitude": {geometry#to_longitude_latitude}["lon"], "latitude": {geometry#to_longitude_latitude}["lat"]}}}}'
|
|
1678
|
+
- $.geometry
|
|
1685
1679
|
CAMS_GREENHOUSE_EGG4_MONTHLY:
|
|
1686
1680
|
dataset: cams-global-ghg-reanalysis-egg4-monthly
|
|
1687
1681
|
metadata_mapping:
|
|
@@ -2444,7 +2438,7 @@
|
|
|
2444
2438
|
- '$.Attributes.processingCenter'
|
|
2445
2439
|
processing:version:
|
|
2446
2440
|
- null
|
|
2447
|
-
- '$.Attributes.processorVersion'
|
|
2441
|
+
- '{$.Attributes.processorVersion#to_geojson}'
|
|
2448
2442
|
_processor_name:
|
|
2449
2443
|
- null
|
|
2450
2444
|
- '$.Attributes.processorName'
|
|
@@ -2972,8 +2966,8 @@
|
|
|
2972
2966
|
platform: properties.platform
|
|
2973
2967
|
metadata_mapping:
|
|
2974
2968
|
grid:code:
|
|
2975
|
-
- '{{"query":{{"s2:mgrs_tile":{{"eq":"{grid:code}"}}}}}}'
|
|
2976
|
-
- '$.properties."s2:mgrs_tile"'
|
|
2969
|
+
- '{{"query":{{"s2:mgrs_tile":{{"eq":"{grid:code#replace_str("MGRS-","")}"}}}}}}'
|
|
2970
|
+
- '{$.properties."s2:mgrs_tile"#replace_str(r"^T?(.*)$",r"MGRS-\1")}'
|
|
2977
2971
|
products:
|
|
2978
2972
|
S1_SAR_GRD:
|
|
2979
2973
|
_collection: sentinel-1-grd
|
|
@@ -3044,6 +3038,18 @@
|
|
|
3044
3038
|
end_datetime:
|
|
3045
3039
|
- '{{"query":{{"start_datetime":{{"lte":"{end_datetime#to_iso_utc_datetime}"}}}}}}'
|
|
3046
3040
|
- '$.properties.end_datetime'
|
|
3041
|
+
processing:software:
|
|
3042
|
+
- '{{"query":{{"processing:software":{{"eq":{processing:software}}}}}}}'
|
|
3043
|
+
- '$.properties.processing:software'
|
|
3044
|
+
spatial:cycle_id:
|
|
3045
|
+
- '{{"query":{{"spatial:cycle_id":{{"eq":{spatial:cycle_id}}}}}}}'
|
|
3046
|
+
- '$.properties.spatial:cycle_id'
|
|
3047
|
+
spatial:pass_id:
|
|
3048
|
+
- '{{"query":{{"spatial:pass_id":{{"eq":{spatial:pass_id}}}}}}}'
|
|
3049
|
+
- '$.properties.spatial:pass_id'
|
|
3050
|
+
spatial:scene_id:
|
|
3051
|
+
- '{{"query":{{"spatial:scene_id":{{"eq":{spatial:scene_id}}}}}}}'
|
|
3052
|
+
- '$.properties.spatial:scene_id'
|
|
3047
3053
|
products:
|
|
3048
3054
|
GENERIC_COLLECTION:
|
|
3049
3055
|
_collection: '{collection}'
|
|
@@ -3550,7 +3556,7 @@
|
|
|
3550
3556
|
prefix: EO:ECMWF:DAT:CAMS
|
|
3551
3557
|
discover_queryables:
|
|
3552
3558
|
fetch_url: null
|
|
3553
|
-
|
|
3559
|
+
collection_fetch_url: null
|
|
3554
3560
|
constraints_url: https://ads.atmosphere.copernicus.eu/api/catalogue/v1/collections/{dataset#wekeo_to_cop_collection(EO:ECMWF:DAT:)}/constraints.json
|
|
3555
3561
|
form_url: https://ads.atmosphere.copernicus.eu/api/catalogue/v1/collections/{dataset#wekeo_to_cop_collection(EO:ECMWF:DAT:)}/form.json
|
|
3556
3562
|
- collection_selector: # cop_cds
|
|
@@ -3576,7 +3582,7 @@
|
|
|
3576
3582
|
prefix: EO:ECMWF:DAT:CEMS
|
|
3577
3583
|
discover_queryables:
|
|
3578
3584
|
fetch_url: null
|
|
3579
|
-
|
|
3585
|
+
collection_fetch_url: null
|
|
3580
3586
|
constraints_url: https://ewds.climate.copernicus.eu/api/catalogue/v1/collections/{dataset#wekeo_to_cop_collection(EO:ECMWF:DAT:)}/constraints.json
|
|
3581
3587
|
form_url: https://ewds.climate.copernicus.eu/api/catalogue/v1/collections/{dataset#wekeo_to_cop_collection(EO:ECMWF:DAT:)}/form.json
|
|
3582
3588
|
metadata_mapping:
|
|
@@ -3587,7 +3593,8 @@
|
|
|
3587
3593
|
- '{{"startdate": "{start_datetime#to_iso_utc_datetime}"}}'
|
|
3588
3594
|
- $.properties.startdate
|
|
3589
3595
|
end_datetime:
|
|
3590
|
-
|
|
3596
|
+
# map 'date' to validate request against copernicus queryables
|
|
3597
|
+
- '{{"enddate": "{end_datetime#to_iso_utc_datetime}", "date": "{start_datetime#to_iso_date}/{end_datetime#to_iso_date}"}}'
|
|
3591
3598
|
- $.properties.enddate
|
|
3592
3599
|
product:type:
|
|
3593
3600
|
- dataset_id
|
|
@@ -3762,7 +3769,8 @@
|
|
|
3762
3769
|
dataset: EO:ECMWF:DAT:CAMS_SOLAR_RADIATION_TIMESERIES
|
|
3763
3770
|
metadata_mapping:
|
|
3764
3771
|
geometry:
|
|
3765
|
-
|
|
3772
|
+
# longitude/latitude to order from wekeo_ecmwf, location to validate against cop_ads constraints
|
|
3773
|
+
- '{{"longitude": {geometry#to_longitude_latitude}["lon"], "latitude": {geometry#to_longitude_latitude}["lat"], "location": {{"longitude": {geometry#to_longitude_latitude}["lon"], "latitude": {geometry#to_longitude_latitude}["lat"]}}}}'
|
|
3766
3774
|
- '$.null'
|
|
3767
3775
|
auth: !plugin
|
|
3768
3776
|
type: TokenAuth
|
|
@@ -4052,7 +4060,7 @@
|
|
|
4052
4060
|
- '$.Attributes.processingCenter'
|
|
4053
4061
|
processing:version:
|
|
4054
4062
|
- null
|
|
4055
|
-
- '$.Attributes.processorVersion'
|
|
4063
|
+
- '{$.Attributes.processorVersion#to_geojson}'
|
|
4056
4064
|
_processor_name:
|
|
4057
4065
|
- null
|
|
4058
4066
|
- '$.Attributes.processorName'
|
|
@@ -5467,7 +5475,7 @@
|
|
|
5467
5475
|
product:type:
|
|
5468
5476
|
- type
|
|
5469
5477
|
- '$.properties.productInformation.productType'
|
|
5470
|
-
|
|
5478
|
+
platform: '$.properties.acquisitionInformation[0].platform.platformShortName'
|
|
5471
5479
|
instruments: '{$.properties.acquisitionInformation[0].instrument.instrumentShortName#split( )}'
|
|
5472
5480
|
# INSPIRE obligated OpenSearch Parameters for Collection Search (Table 4)
|
|
5473
5481
|
title: '{$.properties.title#sanitize}'
|
|
@@ -5630,6 +5638,15 @@
|
|
|
5630
5638
|
metadata_mapping:
|
|
5631
5639
|
<<: *orbit_zone_tile
|
|
5632
5640
|
<<: *sentinel_params
|
|
5641
|
+
S3_OL_2_WFRBC003:
|
|
5642
|
+
product:type: OL_2_WFR___
|
|
5643
|
+
_collection: EO:EUM:DAT:0556
|
|
5644
|
+
metadata_mapping:
|
|
5645
|
+
<<: *orbit_zone_tile
|
|
5646
|
+
<<: *sentinel_params
|
|
5647
|
+
platform:
|
|
5648
|
+
- sat
|
|
5649
|
+
- '$.properties.acquisitionInformation[0].platform.platformShortName'
|
|
5633
5650
|
# S3 SLSTR
|
|
5634
5651
|
S3_SLSTR_L1RBT:
|
|
5635
5652
|
product:type: SL_1_RBT___
|
|
@@ -5834,6 +5851,16 @@
|
|
|
5834
5851
|
_collection: EO:EUM:DAT:0684
|
|
5835
5852
|
MTG_FCI_OLR:
|
|
5836
5853
|
_collection: EO:EUM:DAT:0685
|
|
5854
|
+
MSG_SEVIRI_RSS_AMV_CDR_V1:
|
|
5855
|
+
_collection: EO:EUM:DAT:1083
|
|
5856
|
+
MSG_SEVIRI_RSS_HR_IMG_L1_5_V1:
|
|
5857
|
+
_collection: EO:EUM:DAT:0962
|
|
5858
|
+
MSG_SEVIRI_SARAH_CDR_V003:
|
|
5859
|
+
_collection: EO:EUM:DAT:0863
|
|
5860
|
+
MTG_FCI_ACTIVE_FIRE_L2_V1:
|
|
5861
|
+
_collection: EO:EUM:DAT:0682
|
|
5862
|
+
MULT_PMW_IR_GIRAFE_PRECIP_CDR_V001:
|
|
5863
|
+
_collection: EO:EUM:DAT:0921
|
|
5837
5864
|
GENERIC_COLLECTION:
|
|
5838
5865
|
_collection: '{collection}'
|
|
5839
5866
|
download: !plugin
|
|
@@ -6079,6 +6106,9 @@
|
|
|
6079
6106
|
sat:absolute_orbit:
|
|
6080
6107
|
- '{{"query":{{"sat:absolute_orbit":{{"eq":{sat:absolute_orbit}}}}}}}'
|
|
6081
6108
|
- '$.properties."sat:absolute_orbit"'
|
|
6109
|
+
sat:orbit_state:
|
|
6110
|
+
- '{{"query":{{"sat:orbit_state":{{"eq":{sat:orbit_state}}}}}}}'
|
|
6111
|
+
- '{$.properties.sat:orbit_state#to_lower}'
|
|
6082
6112
|
sat:relative_orbit:
|
|
6083
6113
|
- '{{"query":{{"sat:relative_orbit":{{"eq":{sat:relative_orbit}}}}}}}'
|
|
6084
6114
|
- '$.properties."sat:relative_orbit"'
|
|
@@ -6106,6 +6136,7 @@
|
|
|
6106
6136
|
grid:code:
|
|
6107
6137
|
- '{{"query":{{"grid:code":{{"contains":"{grid:code#replace_str("MGRS-","T")}"}}}}}}'
|
|
6108
6138
|
- '{$.properties."grid:code"#replace_str(r"^T?(.*)$",r"MGRS-\1")}'
|
|
6139
|
+
sci:doi: '{$.properties.sci:doi#replace_str(r"^\[\]$","Not Available")}'
|
|
6109
6140
|
published: '$.properties.datetime'
|
|
6110
6141
|
eodag:download_link: '$.assets[?(@.roles[0] == "data") & (@.type != "application/xml")].href'
|
|
6111
6142
|
eodag:quicklook: '$.assets[?(@.roles[0] == "overview")].href.`sub(/^(.*)$/, \\1?scope=gdh)`'
|
|
@@ -6131,11 +6162,11 @@
|
|
|
6131
6162
|
S2_MSI_L1C:
|
|
6132
6163
|
_collection: PEPS_S2_L1C
|
|
6133
6164
|
S2_MSI_L2A_MAJA:
|
|
6134
|
-
_collection:
|
|
6165
|
+
_collection: THEIA_REFLECTANCE_SENTINEL2_L2A
|
|
6135
6166
|
S2_MSI_L2B_MAJA_SNOW:
|
|
6136
|
-
_collection:
|
|
6167
|
+
_collection: THEIA_SNOW_SENTINEL2_L2B
|
|
6137
6168
|
S2_MSI_L2B_MAJA_WATER:
|
|
6138
|
-
_collection:
|
|
6169
|
+
_collection: THEIA_WATERQUAL_SENTINEL2_L2B
|
|
6139
6170
|
GENERIC_COLLECTION:
|
|
6140
6171
|
_collection: '{collection}'
|
|
6141
6172
|
download: !plugin
|
|
@@ -6209,6 +6240,9 @@
|
|
|
6209
6240
|
sat:absolute_orbit:
|
|
6210
6241
|
- '{{"query":{{"sat:absolute_orbit":{{"eq":{sat:absolute_orbit}}}}}}}'
|
|
6211
6242
|
- '$.properties."sat:absolute_orbit"'
|
|
6243
|
+
sat:orbit_state:
|
|
6244
|
+
- '{{"query":{{"sat:orbit_state":{{"eq":{sat:orbit_state}}}}}}}'
|
|
6245
|
+
- '{$.properties.sat:orbit_state#to_lower}'
|
|
6212
6246
|
sat:relative_orbit:
|
|
6213
6247
|
- '{{"query":{{"sat:relative_orbit":{{"eq":{sat:relative_orbit}}}}}}}'
|
|
6214
6248
|
- '$.properties."sat:relative_orbit"'
|
|
@@ -6236,6 +6270,7 @@
|
|
|
6236
6270
|
grid:code:
|
|
6237
6271
|
- '{{"query":{{"grid:code":{{"contains":"{grid:code#replace_str("MGRS-","T")}"}}}}}}'
|
|
6238
6272
|
- '{$.properties."grid:code"#replace_str(r"^T?(.*)$",r"MGRS-\1")}'
|
|
6273
|
+
sci:doi: '{$.properties.sci:doi#replace_str(r"^\[\]$","Not Available")}'
|
|
6239
6274
|
published: '$.properties.datetime'
|
|
6240
6275
|
eodag:download_link: '$.properties.endpoint_url'
|
|
6241
6276
|
eodag:quicklook: '$.assets[?(@.roles[0] == "overview")].href.`sub(/^(.*)$/, \\1?scope=gdh)`'
|
|
@@ -6261,11 +6296,11 @@
|
|
|
6261
6296
|
S2_MSI_L1C:
|
|
6262
6297
|
_collection: PEPS_S2_L1C
|
|
6263
6298
|
S2_MSI_L2A_MAJA:
|
|
6264
|
-
_collection:
|
|
6299
|
+
_collection: THEIA_REFLECTANCE_SENTINEL2_L2A
|
|
6265
6300
|
S2_MSI_L2B_MAJA_SNOW:
|
|
6266
|
-
|
|
6301
|
+
_collection: THEIA_SNOW_SENTINEL2_L2B
|
|
6267
6302
|
S2_MSI_L2B_MAJA_WATER:
|
|
6268
|
-
_collection:
|
|
6303
|
+
_collection: THEIA_WATERQUAL_SENTINEL2_L2B
|
|
6269
6304
|
GENERIC_COLLECTION:
|
|
6270
6305
|
_collection: '{collection}'
|
|
6271
6306
|
download: !plugin
|
|
@@ -152,6 +152,12 @@ eumetsat_ds:
|
|
|
152
152
|
password:
|
|
153
153
|
download:
|
|
154
154
|
output_dir:
|
|
155
|
+
fedeo_ceda:
|
|
156
|
+
priority: # Lower value means lower priority (Default: 0)
|
|
157
|
+
search: # Search parameters configuration
|
|
158
|
+
download:
|
|
159
|
+
extract:
|
|
160
|
+
output_dir:
|
|
155
161
|
geodes:
|
|
156
162
|
priority: # Lower value means lower priority (Default: 0)
|
|
157
163
|
search: # Search parameters configuration
|
eodag/types/__init__.py
CHANGED
|
@@ -19,24 +19,15 @@
|
|
|
19
19
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
|
-
from typing import
|
|
23
|
-
Annotated,
|
|
24
|
-
Any,
|
|
25
|
-
Literal,
|
|
26
|
-
Optional,
|
|
27
|
-
Type,
|
|
28
|
-
TypedDict,
|
|
29
|
-
Union,
|
|
30
|
-
get_args,
|
|
31
|
-
get_origin,
|
|
32
|
-
)
|
|
22
|
+
from typing import Annotated, Any, Literal, Optional, Type, Union, get_args, get_origin
|
|
33
23
|
|
|
34
24
|
from annotated_types import Gt, Lt
|
|
35
|
-
from pydantic import BaseModel, ConfigDict, Field, create_model
|
|
25
|
+
from pydantic import AliasChoices, BaseModel, ConfigDict, Field, create_model
|
|
36
26
|
from pydantic.annotated_handlers import GetJsonSchemaHandler
|
|
37
27
|
from pydantic.fields import FieldInfo
|
|
38
28
|
from pydantic.json_schema import JsonSchemaValue
|
|
39
29
|
from pydantic_core import CoreSchema, PydanticUndefined
|
|
30
|
+
from typing_extensions import TypedDict
|
|
40
31
|
|
|
41
32
|
from eodag.utils import copy_deepcopy
|
|
42
33
|
from eodag.utils.exceptions import ValidationError
|
|
@@ -153,7 +144,8 @@ def json_field_definition_to_python(
|
|
|
153
144
|
json_field_definition: dict[str, Any],
|
|
154
145
|
default_value: Optional[Any] = None,
|
|
155
146
|
required: Optional[bool] = False,
|
|
156
|
-
|
|
147
|
+
validation_alias: Optional[Union[str, AliasChoices]] = None,
|
|
148
|
+
serialization_alias: Optional[str] = None,
|
|
157
149
|
) -> Annotated[Any, FieldInfo]:
|
|
158
150
|
"""Get python field definition from json object
|
|
159
151
|
|
|
@@ -171,6 +163,8 @@ def json_field_definition_to_python(
|
|
|
171
163
|
:param json_field_definition: the json field definition
|
|
172
164
|
:param default_value: default value of the field
|
|
173
165
|
:param required: if the field is required
|
|
166
|
+
:param validation_alias: validation alias
|
|
167
|
+
:param serialization_alias: serialization alias
|
|
174
168
|
:returns: the python field definition
|
|
175
169
|
"""
|
|
176
170
|
python_type = json_type_to_python(json_field_definition.get("type"))
|
|
@@ -181,7 +175,8 @@ def json_field_definition_to_python(
|
|
|
181
175
|
pattern=json_field_definition.get("pattern", PydanticUndefined),
|
|
182
176
|
le=json_field_definition.get("maximum", PydanticUndefined),
|
|
183
177
|
ge=json_field_definition.get("minimum", PydanticUndefined),
|
|
184
|
-
|
|
178
|
+
validation_alias=validation_alias or PydanticUndefined,
|
|
179
|
+
serialization_alias=serialization_alias or PydanticUndefined,
|
|
185
180
|
)
|
|
186
181
|
|
|
187
182
|
enum = json_field_definition.get("enum")
|
|
@@ -202,6 +197,14 @@ def json_field_definition_to_python(
|
|
|
202
197
|
enum = items.get("enum")
|
|
203
198
|
elif "const" in items:
|
|
204
199
|
const = items.get("const")
|
|
200
|
+
elif python_type is dict:
|
|
201
|
+
properties = json_field_definition.get("properties")
|
|
202
|
+
if isinstance(properties, dict):
|
|
203
|
+
fields_type: dict = {
|
|
204
|
+
k: json_field_definition_to_python(v, required=required)
|
|
205
|
+
for k, v in properties.items()
|
|
206
|
+
}
|
|
207
|
+
python_type = TypedDict("dictionary", fields_type) # type: ignore
|
|
205
208
|
|
|
206
209
|
if enum:
|
|
207
210
|
literal = Literal[tuple(sorted(enum))] # type: ignore
|
|
@@ -386,7 +389,9 @@ class BaseModelCustomJsonSchema(BaseModel):
|
|
|
386
389
|
|
|
387
390
|
|
|
388
391
|
def annotated_dict_to_model(
|
|
389
|
-
model_name: str,
|
|
392
|
+
model_name: str,
|
|
393
|
+
annotated_fields: dict[str, Annotated[Any, FieldInfo]],
|
|
394
|
+
model_class: Optional[type[BaseModel]] = BaseModelCustomJsonSchema,
|
|
390
395
|
) -> BaseModel:
|
|
391
396
|
"""Convert a dictionary of Annotated values to a Pydantic BaseModel.
|
|
392
397
|
|
|
@@ -411,6 +416,7 @@ def annotated_dict_to_model(
|
|
|
411
416
|
:param model_name: name of the model to be created
|
|
412
417
|
:param annotated_fields: dict containing the parameters and annotated values that should become
|
|
413
418
|
the properties of the model
|
|
419
|
+
:param model_class: (optiional) base class of the returned model
|
|
414
420
|
:returns: pydantic model
|
|
415
421
|
"""
|
|
416
422
|
fields = {}
|
|
@@ -423,7 +429,7 @@ def annotated_dict_to_model(
|
|
|
423
429
|
|
|
424
430
|
custom_model = create_model(
|
|
425
431
|
model_name,
|
|
426
|
-
__base__=
|
|
432
|
+
__base__=model_class,
|
|
427
433
|
**fields, # type: ignore
|
|
428
434
|
)
|
|
429
435
|
|
eodag/types/download_args.py
CHANGED
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
# limitations under the License.
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
from typing import Optional,
|
|
20
|
+
from typing import Optional, Union
|
|
21
|
+
|
|
22
|
+
from typing_extensions import TypedDict
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
class DownloadConf(TypedDict, total=False):
|
eodag/types/queryables.py
CHANGED
|
@@ -1,22 +1,61 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright 2025, 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.
|
|
1
18
|
from __future__ import annotations
|
|
2
19
|
|
|
20
|
+
import re
|
|
3
21
|
from collections import UserDict
|
|
4
|
-
from typing import Annotated, Any, Optional, Union
|
|
22
|
+
from typing import TYPE_CHECKING, Annotated, Any, Optional, Union, cast
|
|
5
23
|
|
|
6
24
|
from annotated_types import Lt
|
|
7
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
25
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
8
26
|
from pydantic.fields import FieldInfo
|
|
9
27
|
from pydantic.types import PositiveInt
|
|
10
28
|
from pydantic_core import PydanticUndefined
|
|
11
29
|
from shapely.geometry.base import BaseGeometry
|
|
30
|
+
from typing_extensions import get_args
|
|
12
31
|
|
|
13
|
-
from eodag.types import
|
|
32
|
+
from eodag.types import (
|
|
33
|
+
BaseModelCustomJsonSchema,
|
|
34
|
+
annotated_dict_to_model,
|
|
35
|
+
model_fields_to_annotated,
|
|
36
|
+
)
|
|
37
|
+
from eodag.types.stac_extensions import STAC_EXTENSIONS
|
|
38
|
+
from eodag.types.stac_metadata import CommonStacMetadata, create_stac_metadata_model
|
|
39
|
+
from eodag.utils.dates import (
|
|
40
|
+
COMPACT_DATE_PATTERN,
|
|
41
|
+
COMPACT_DATE_RANGE_PATTERN,
|
|
42
|
+
DATE_PATTERN,
|
|
43
|
+
DATE_RANGE_PATTERN,
|
|
44
|
+
datetime_range,
|
|
45
|
+
format_date,
|
|
46
|
+
format_date_range,
|
|
47
|
+
is_range_in_range,
|
|
48
|
+
parse_date,
|
|
49
|
+
)
|
|
14
50
|
from eodag.utils.repr import remove_class_repr, shorter_type_repr
|
|
15
51
|
|
|
52
|
+
if TYPE_CHECKING:
|
|
53
|
+
from eodag.types.stac_extensions import BaseStacExtension
|
|
54
|
+
|
|
16
55
|
Percentage = Annotated[PositiveInt, Lt(100)]
|
|
17
56
|
|
|
18
57
|
|
|
19
|
-
class CommonQueryables(
|
|
58
|
+
class CommonQueryables(BaseModelCustomJsonSchema):
|
|
20
59
|
"""A class representing search common queryable properties."""
|
|
21
60
|
|
|
22
61
|
collection: Annotated[str, Field()]
|
|
@@ -47,11 +86,33 @@ class CommonQueryables(BaseModel):
|
|
|
47
86
|
f.__metadata__[0].default = default
|
|
48
87
|
return f
|
|
49
88
|
|
|
89
|
+
@classmethod
|
|
90
|
+
def from_stac_models(
|
|
91
|
+
cls,
|
|
92
|
+
extensions: list[BaseStacExtension] = STAC_EXTENSIONS,
|
|
93
|
+
base_model: type[BaseModel] = CommonStacMetadata,
|
|
94
|
+
) -> type[Queryables]:
|
|
95
|
+
"""Creates Queryables from STAC models.
|
|
96
|
+
|
|
97
|
+
:param extensions: list of STAC extensions to include in the model
|
|
98
|
+
:param base_model: base STAC model to use
|
|
99
|
+
:return: Queryables model
|
|
100
|
+
"""
|
|
101
|
+
return cast(
|
|
102
|
+
type[Queryables],
|
|
103
|
+
create_stac_metadata_model(
|
|
104
|
+
base_models=[cls, base_model],
|
|
105
|
+
extensions=extensions,
|
|
106
|
+
class_name="Queryables",
|
|
107
|
+
),
|
|
108
|
+
)
|
|
109
|
+
|
|
50
110
|
|
|
51
111
|
class Queryables(CommonQueryables):
|
|
52
112
|
"""A class representing all search queryable properties.
|
|
53
113
|
|
|
54
114
|
Parameters default value is set to ``None`` to have them not required.
|
|
115
|
+
Fields described here are queryables-specific and complete StacMetadata fields.
|
|
55
116
|
"""
|
|
56
117
|
|
|
57
118
|
start: Annotated[
|
|
@@ -78,60 +139,69 @@ class Queryables(CommonQueryables):
|
|
|
78
139
|
description="Read EODAG documentation for all supported geometry format.",
|
|
79
140
|
),
|
|
80
141
|
]
|
|
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
142
|
id: Annotated[str, Field(None)]
|
|
87
|
-
instruments: Annotated[str, Field(None)]
|
|
88
|
-
keywords: Annotated[str, Field(None)]
|
|
89
|
-
license: Annotated[str, Field(None)]
|
|
90
|
-
platform: Annotated[str, Field(None)]
|
|
91
|
-
providers: Annotated[str, Field(None)]
|
|
92
|
-
title: Annotated[str, Field(None)]
|
|
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")]
|
|
132
143
|
|
|
133
144
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
134
145
|
|
|
146
|
+
@field_validator("ecmwf_date", mode="plain", check_fields=False)
|
|
147
|
+
@classmethod
|
|
148
|
+
def check_date_range(cls, v: str) -> str:
|
|
149
|
+
"""Validate date ranges"""
|
|
150
|
+
if not isinstance(v, str):
|
|
151
|
+
raise ValueError(
|
|
152
|
+
"date must be a string formatted as single date ('yyyy-mm-dd') or range ('yyyy-mm-dd/yyyy-mm-dd')"
|
|
153
|
+
)
|
|
154
|
+
date_regex = [
|
|
155
|
+
re.compile(p)
|
|
156
|
+
for p in (
|
|
157
|
+
DATE_PATTERN,
|
|
158
|
+
COMPACT_DATE_PATTERN,
|
|
159
|
+
DATE_RANGE_PATTERN,
|
|
160
|
+
COMPACT_DATE_RANGE_PATTERN,
|
|
161
|
+
)
|
|
162
|
+
]
|
|
163
|
+
if not any(r.match(v) is not None for r in date_regex):
|
|
164
|
+
raise ValueError(
|
|
165
|
+
"date must be a string formatted as single date ('yyyy-mm-dd') or range ('yyyy-mm-dd/yyyy-mm-dd')"
|
|
166
|
+
)
|
|
167
|
+
try:
|
|
168
|
+
start, end = parse_date(v)
|
|
169
|
+
except ValueError as e:
|
|
170
|
+
raise ValueError("date must follow 'yyyy-mm-dd' format") from e
|
|
171
|
+
if end < start:
|
|
172
|
+
raise ValueError("date range end must be after start")
|
|
173
|
+
# enumerate dates in range
|
|
174
|
+
v_set: set[str] = {format_date(d) for d in datetime_range(start, end)}
|
|
175
|
+
# is_range_in_range() support only ranges (no single date allowed) in the format 'yyyy-mm-dd/yyyy-mm-dd'
|
|
176
|
+
v_range: str = format_date_range(start, end)
|
|
177
|
+
|
|
178
|
+
field_info = cls.model_fields["ecmwf_date"]
|
|
179
|
+
literals = get_args(field_info.annotation)
|
|
180
|
+
|
|
181
|
+
# Collect missing values to report errors
|
|
182
|
+
missing_values = set(v_set)
|
|
183
|
+
|
|
184
|
+
# date constraint may be intervals. We identify intervals with a "/" in the value.
|
|
185
|
+
# date constraint can be a mixed list of single values (e.g "2023-06-27")
|
|
186
|
+
# and intervals (e.g. "2024-11-12/2025-11-20")
|
|
187
|
+
# collections with mixed values: CAMS_GAC_FORECAST, CAMS_EU_AIR_QUALITY_FORECAST
|
|
188
|
+
for literal in literals:
|
|
189
|
+
literal_start, literal_end = parse_date(literal)
|
|
190
|
+
if "/" in literal:
|
|
191
|
+
# range with separator / or /to/
|
|
192
|
+
literal_range: str = format_date_range(literal_start, literal_end)
|
|
193
|
+
if is_range_in_range(literal_range, v_range):
|
|
194
|
+
return v
|
|
195
|
+
else:
|
|
196
|
+
# convert literal to the format 'yyyy-mm-dd'
|
|
197
|
+
literal_start_str = format_date(literal_start)
|
|
198
|
+
if literal_start_str in v_set:
|
|
199
|
+
missing_values.remove(literal_start_str)
|
|
200
|
+
if not missing_values:
|
|
201
|
+
return v
|
|
202
|
+
|
|
203
|
+
raise ValueError("date not allowed")
|
|
204
|
+
|
|
135
205
|
|
|
136
206
|
class QueryablesDict(UserDict[str, Any]):
|
|
137
207
|
"""Class inheriting from UserDict which contains queryables with their annotated type;
|
|
@@ -232,4 +302,4 @@ class QueryablesDict(UserDict[str, Any]):
|
|
|
232
302
|
:param model_name: name used for :class:`pydantic.BaseModel` creation
|
|
233
303
|
:return: pydantic BaseModel of the queryables dict
|
|
234
304
|
"""
|
|
235
|
-
return annotated_dict_to_model(model_name, self.data)
|
|
305
|
+
return annotated_dict_to_model(model_name, self.data, Queryables)
|