eodag 4.0.0a4__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 +65 -19
- eodag/api/product/_assets.py +1 -1
- eodag/api/product/_product.py +133 -18
- 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 +17 -8
- eodag/config.py +22 -4
- eodag/plugins/apis/ecmwf.py +3 -24
- eodag/plugins/apis/usgs.py +3 -24
- eodag/plugins/download/aws.py +85 -44
- eodag/plugins/download/base.py +117 -41
- eodag/plugins/download/http.py +88 -65
- 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 +62 -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/utils/s3.py +4 -4
- {eodag-4.0.0a4.dist-info → eodag-4.0.0b1.dist-info}/METADATA +7 -13
- {eodag-4.0.0a4.dist-info → eodag-4.0.0b1.dist-info}/RECORD +42 -40
- {eodag-4.0.0a4.dist-info → eodag-4.0.0b1.dist-info}/WHEEL +1 -1
- {eodag-4.0.0a4.dist-info → eodag-4.0.0b1.dist-info}/entry_points.txt +1 -1
- {eodag-4.0.0a4.dist-info → eodag-4.0.0b1.dist-info}/licenses/LICENSE +0 -0
- {eodag-4.0.0a4.dist-info → eodag-4.0.0b1.dist-info}/top_level.txt +0 -0
eodag/resources/providers.yml
CHANGED
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
usgs:productId: '$.id'
|
|
50
50
|
extract: True
|
|
51
51
|
order_enabled: true
|
|
52
|
+
max_workers: 2
|
|
52
53
|
products:
|
|
53
54
|
# datasets list http://kapadia.github.io/usgs/_sources/reference/catalog/ee.txt may be outdated
|
|
54
55
|
# see also https://dds.cr.usgs.gov/ee-data/coveragemaps/shp/ee/
|
|
@@ -589,9 +590,6 @@
|
|
|
589
590
|
version: '$.properties.version'
|
|
590
591
|
created: '$.properties.dhusIngestDate'
|
|
591
592
|
updated: '$.properties.updated'
|
|
592
|
-
sar:instrument_mode:
|
|
593
|
-
- 'sensorMode'
|
|
594
|
-
- '$.properties.sensorMode'
|
|
595
593
|
|
|
596
594
|
# OpenSearch Parameters for Acquistion Parameters Search (Table 6)
|
|
597
595
|
start_datetime:
|
|
@@ -799,7 +797,7 @@
|
|
|
799
797
|
- '$.Attributes.processingCenter'
|
|
800
798
|
processing:version:
|
|
801
799
|
- null
|
|
802
|
-
- '$.Attributes.processorVersion'
|
|
800
|
+
- '{$.Attributes.processorVersion#to_geojson}'
|
|
803
801
|
_processor_name:
|
|
804
802
|
- null
|
|
805
803
|
- '$.Attributes.processorName'
|
|
@@ -1675,12 +1673,9 @@
|
|
|
1675
1673
|
CAMS_SOLAR_RADIATION:
|
|
1676
1674
|
dataset: cams-solar-radiation-timeseries
|
|
1677
1675
|
metadata_mapping:
|
|
1678
|
-
|
|
1679
|
-
- '{{"location": {{"
|
|
1680
|
-
- $.
|
|
1681
|
-
longitude:
|
|
1682
|
-
- '{{"location": {{"latitude": {"latitude"}, "longitude": {"longitude"}}}}}'
|
|
1683
|
-
- $."longitude"
|
|
1676
|
+
geometry:
|
|
1677
|
+
- '{{"location": {{"longitude": {geometry#to_longitude_latitude}["lon"], "latitude": {geometry#to_longitude_latitude}["lat"]}}}}'
|
|
1678
|
+
- $.geometry
|
|
1684
1679
|
CAMS_GREENHOUSE_EGG4_MONTHLY:
|
|
1685
1680
|
dataset: cams-global-ghg-reanalysis-egg4-monthly
|
|
1686
1681
|
metadata_mapping:
|
|
@@ -2443,7 +2438,7 @@
|
|
|
2443
2438
|
- '$.Attributes.processingCenter'
|
|
2444
2439
|
processing:version:
|
|
2445
2440
|
- null
|
|
2446
|
-
- '$.Attributes.processorVersion'
|
|
2441
|
+
- '{$.Attributes.processorVersion#to_geojson}'
|
|
2447
2442
|
_processor_name:
|
|
2448
2443
|
- null
|
|
2449
2444
|
- '$.Attributes.processorName'
|
|
@@ -2558,6 +2553,7 @@
|
|
|
2558
2553
|
extract: true
|
|
2559
2554
|
order_enabled: false
|
|
2560
2555
|
archive_depth: 2
|
|
2556
|
+
max_workers: 4
|
|
2561
2557
|
ssl_verify: true
|
|
2562
2558
|
auth: !plugin
|
|
2563
2559
|
type: KeycloakOIDCPasswordAuth
|
|
@@ -2970,8 +2966,8 @@
|
|
|
2970
2966
|
platform: properties.platform
|
|
2971
2967
|
metadata_mapping:
|
|
2972
2968
|
grid:code:
|
|
2973
|
-
- '{{"query":{{"s2:mgrs_tile":{{"eq":"{grid:code}"}}}}}}'
|
|
2974
|
-
- '$.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")}'
|
|
2975
2971
|
products:
|
|
2976
2972
|
S1_SAR_GRD:
|
|
2977
2973
|
_collection: sentinel-1-grd
|
|
@@ -3042,6 +3038,18 @@
|
|
|
3042
3038
|
end_datetime:
|
|
3043
3039
|
- '{{"query":{{"start_datetime":{{"lte":"{end_datetime#to_iso_utc_datetime}"}}}}}}'
|
|
3044
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'
|
|
3045
3053
|
products:
|
|
3046
3054
|
GENERIC_COLLECTION:
|
|
3047
3055
|
_collection: '{collection}'
|
|
@@ -3548,7 +3556,7 @@
|
|
|
3548
3556
|
prefix: EO:ECMWF:DAT:CAMS
|
|
3549
3557
|
discover_queryables:
|
|
3550
3558
|
fetch_url: null
|
|
3551
|
-
|
|
3559
|
+
collection_fetch_url: null
|
|
3552
3560
|
constraints_url: https://ads.atmosphere.copernicus.eu/api/catalogue/v1/collections/{dataset#wekeo_to_cop_collection(EO:ECMWF:DAT:)}/constraints.json
|
|
3553
3561
|
form_url: https://ads.atmosphere.copernicus.eu/api/catalogue/v1/collections/{dataset#wekeo_to_cop_collection(EO:ECMWF:DAT:)}/form.json
|
|
3554
3562
|
- collection_selector: # cop_cds
|
|
@@ -3574,7 +3582,7 @@
|
|
|
3574
3582
|
prefix: EO:ECMWF:DAT:CEMS
|
|
3575
3583
|
discover_queryables:
|
|
3576
3584
|
fetch_url: null
|
|
3577
|
-
|
|
3585
|
+
collection_fetch_url: null
|
|
3578
3586
|
constraints_url: https://ewds.climate.copernicus.eu/api/catalogue/v1/collections/{dataset#wekeo_to_cop_collection(EO:ECMWF:DAT:)}/constraints.json
|
|
3579
3587
|
form_url: https://ewds.climate.copernicus.eu/api/catalogue/v1/collections/{dataset#wekeo_to_cop_collection(EO:ECMWF:DAT:)}/form.json
|
|
3580
3588
|
metadata_mapping:
|
|
@@ -3585,7 +3593,8 @@
|
|
|
3585
3593
|
- '{{"startdate": "{start_datetime#to_iso_utc_datetime}"}}'
|
|
3586
3594
|
- $.properties.startdate
|
|
3587
3595
|
end_datetime:
|
|
3588
|
-
|
|
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}"}}'
|
|
3589
3598
|
- $.properties.enddate
|
|
3590
3599
|
product:type:
|
|
3591
3600
|
- dataset_id
|
|
@@ -3760,7 +3769,8 @@
|
|
|
3760
3769
|
dataset: EO:ECMWF:DAT:CAMS_SOLAR_RADIATION_TIMESERIES
|
|
3761
3770
|
metadata_mapping:
|
|
3762
3771
|
geometry:
|
|
3763
|
-
|
|
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"]}}}}'
|
|
3764
3774
|
- '$.null'
|
|
3765
3775
|
auth: !plugin
|
|
3766
3776
|
type: TokenAuth
|
|
@@ -4050,7 +4060,7 @@
|
|
|
4050
4060
|
- '$.Attributes.processingCenter'
|
|
4051
4061
|
processing:version:
|
|
4052
4062
|
- null
|
|
4053
|
-
- '$.Attributes.processorVersion'
|
|
4063
|
+
- '{$.Attributes.processorVersion#to_geojson}'
|
|
4054
4064
|
_processor_name:
|
|
4055
4065
|
- null
|
|
4056
4066
|
- '$.Attributes.processorName'
|
|
@@ -5465,7 +5475,7 @@
|
|
|
5465
5475
|
product:type:
|
|
5466
5476
|
- type
|
|
5467
5477
|
- '$.properties.productInformation.productType'
|
|
5468
|
-
|
|
5478
|
+
platform: '$.properties.acquisitionInformation[0].platform.platformShortName'
|
|
5469
5479
|
instruments: '{$.properties.acquisitionInformation[0].instrument.instrumentShortName#split( )}'
|
|
5470
5480
|
# INSPIRE obligated OpenSearch Parameters for Collection Search (Table 4)
|
|
5471
5481
|
title: '{$.properties.title#sanitize}'
|
|
@@ -5628,6 +5638,15 @@
|
|
|
5628
5638
|
metadata_mapping:
|
|
5629
5639
|
<<: *orbit_zone_tile
|
|
5630
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'
|
|
5631
5650
|
# S3 SLSTR
|
|
5632
5651
|
S3_SLSTR_L1RBT:
|
|
5633
5652
|
product:type: SL_1_RBT___
|
|
@@ -5832,6 +5851,16 @@
|
|
|
5832
5851
|
_collection: EO:EUM:DAT:0684
|
|
5833
5852
|
MTG_FCI_OLR:
|
|
5834
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
|
|
5835
5864
|
GENERIC_COLLECTION:
|
|
5836
5865
|
_collection: '{collection}'
|
|
5837
5866
|
download: !plugin
|
|
@@ -6077,6 +6106,9 @@
|
|
|
6077
6106
|
sat:absolute_orbit:
|
|
6078
6107
|
- '{{"query":{{"sat:absolute_orbit":{{"eq":{sat:absolute_orbit}}}}}}}'
|
|
6079
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}'
|
|
6080
6112
|
sat:relative_orbit:
|
|
6081
6113
|
- '{{"query":{{"sat:relative_orbit":{{"eq":{sat:relative_orbit}}}}}}}'
|
|
6082
6114
|
- '$.properties."sat:relative_orbit"'
|
|
@@ -6104,6 +6136,7 @@
|
|
|
6104
6136
|
grid:code:
|
|
6105
6137
|
- '{{"query":{{"grid:code":{{"contains":"{grid:code#replace_str("MGRS-","T")}"}}}}}}'
|
|
6106
6138
|
- '{$.properties."grid:code"#replace_str(r"^T?(.*)$",r"MGRS-\1")}'
|
|
6139
|
+
sci:doi: '{$.properties.sci:doi#replace_str(r"^\[\]$","Not Available")}'
|
|
6107
6140
|
published: '$.properties.datetime'
|
|
6108
6141
|
eodag:download_link: '$.assets[?(@.roles[0] == "data") & (@.type != "application/xml")].href'
|
|
6109
6142
|
eodag:quicklook: '$.assets[?(@.roles[0] == "overview")].href.`sub(/^(.*)$/, \\1?scope=gdh)`'
|
|
@@ -6129,11 +6162,11 @@
|
|
|
6129
6162
|
S2_MSI_L1C:
|
|
6130
6163
|
_collection: PEPS_S2_L1C
|
|
6131
6164
|
S2_MSI_L2A_MAJA:
|
|
6132
|
-
_collection:
|
|
6165
|
+
_collection: THEIA_REFLECTANCE_SENTINEL2_L2A
|
|
6133
6166
|
S2_MSI_L2B_MAJA_SNOW:
|
|
6134
|
-
_collection:
|
|
6167
|
+
_collection: THEIA_SNOW_SENTINEL2_L2B
|
|
6135
6168
|
S2_MSI_L2B_MAJA_WATER:
|
|
6136
|
-
_collection:
|
|
6169
|
+
_collection: THEIA_WATERQUAL_SENTINEL2_L2B
|
|
6137
6170
|
GENERIC_COLLECTION:
|
|
6138
6171
|
_collection: '{collection}'
|
|
6139
6172
|
download: !plugin
|
|
@@ -6207,6 +6240,9 @@
|
|
|
6207
6240
|
sat:absolute_orbit:
|
|
6208
6241
|
- '{{"query":{{"sat:absolute_orbit":{{"eq":{sat:absolute_orbit}}}}}}}'
|
|
6209
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}'
|
|
6210
6246
|
sat:relative_orbit:
|
|
6211
6247
|
- '{{"query":{{"sat:relative_orbit":{{"eq":{sat:relative_orbit}}}}}}}'
|
|
6212
6248
|
- '$.properties."sat:relative_orbit"'
|
|
@@ -6234,6 +6270,7 @@
|
|
|
6234
6270
|
grid:code:
|
|
6235
6271
|
- '{{"query":{{"grid:code":{{"contains":"{grid:code#replace_str("MGRS-","T")}"}}}}}}'
|
|
6236
6272
|
- '{$.properties."grid:code"#replace_str(r"^T?(.*)$",r"MGRS-\1")}'
|
|
6273
|
+
sci:doi: '{$.properties.sci:doi#replace_str(r"^\[\]$","Not Available")}'
|
|
6237
6274
|
published: '$.properties.datetime'
|
|
6238
6275
|
eodag:download_link: '$.properties.endpoint_url'
|
|
6239
6276
|
eodag:quicklook: '$.assets[?(@.roles[0] == "overview")].href.`sub(/^(.*)$/, \\1?scope=gdh)`'
|
|
@@ -6259,11 +6296,11 @@
|
|
|
6259
6296
|
S2_MSI_L1C:
|
|
6260
6297
|
_collection: PEPS_S2_L1C
|
|
6261
6298
|
S2_MSI_L2A_MAJA:
|
|
6262
|
-
_collection:
|
|
6299
|
+
_collection: THEIA_REFLECTANCE_SENTINEL2_L2A
|
|
6263
6300
|
S2_MSI_L2B_MAJA_SNOW:
|
|
6264
|
-
|
|
6301
|
+
_collection: THEIA_SNOW_SENTINEL2_L2B
|
|
6265
6302
|
S2_MSI_L2B_MAJA_WATER:
|
|
6266
|
-
_collection:
|
|
6303
|
+
_collection: THEIA_WATERQUAL_SENTINEL2_L2B
|
|
6267
6304
|
GENERIC_COLLECTION:
|
|
6268
6305
|
_collection: '{collection}'
|
|
6269
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)
|