eodag 3.1.0b2__py3-none-any.whl → 3.2.1__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/core.py +10 -11
- eodag/api/product/_assets.py +45 -9
- eodag/api/product/_product.py +14 -18
- eodag/api/product/metadata_mapping.py +23 -5
- eodag/config.py +11 -11
- eodag/plugins/apis/ecmwf.py +2 -6
- eodag/plugins/apis/usgs.py +1 -1
- eodag/plugins/authentication/openid_connect.py +6 -0
- eodag/plugins/download/aws.py +90 -11
- eodag/plugins/search/base.py +3 -2
- eodag/plugins/search/build_search_result.py +348 -281
- eodag/plugins/search/data_request_search.py +3 -3
- eodag/plugins/search/qssearch.py +32 -103
- eodag/plugins/search/static_stac_search.py +1 -1
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +564 -114
- eodag/resources/providers.yml +956 -1173
- eodag/resources/user_conf_template.yml +1 -11
- eodag/rest/stac.py +1 -0
- eodag/rest/types/queryables.py +28 -16
- eodag/types/__init__.py +73 -11
- eodag/utils/__init__.py +2 -2
- eodag/utils/s3.py +31 -8
- {eodag-3.1.0b2.dist-info → eodag-3.2.1.dist-info}/METADATA +10 -9
- {eodag-3.1.0b2.dist-info → eodag-3.2.1.dist-info}/RECORD +29 -29
- {eodag-3.1.0b2.dist-info → eodag-3.2.1.dist-info}/WHEEL +1 -1
- {eodag-3.1.0b2.dist-info → eodag-3.2.1.dist-info}/entry_points.txt +0 -0
- {eodag-3.1.0b2.dist-info → eodag-3.2.1.dist-info/licenses}/LICENSE +0 -0
- {eodag-3.1.0b2.dist-info → eodag-3.2.1.dist-info}/top_level.txt +0 -0
|
@@ -187,7 +187,7 @@ class DataRequestSearch(Search):
|
|
|
187
187
|
)
|
|
188
188
|
if other_product_for_mapping:
|
|
189
189
|
other_product_type_def_params = self.get_product_type_def_params(
|
|
190
|
-
other_product_for_mapping,
|
|
190
|
+
other_product_for_mapping,
|
|
191
191
|
)
|
|
192
192
|
product_type_metadata_mapping.update(
|
|
193
193
|
other_product_type_def_params.get("metadata_mapping", {})
|
|
@@ -253,7 +253,7 @@ class DataRequestSearch(Search):
|
|
|
253
253
|
|
|
254
254
|
# provider product type specific conf
|
|
255
255
|
self.product_type_def_params = self.get_product_type_def_params(
|
|
256
|
-
product_type,
|
|
256
|
+
product_type, format_variables=kwargs
|
|
257
257
|
)
|
|
258
258
|
|
|
259
259
|
# update config using provider product type definition metadata_mapping
|
|
@@ -263,7 +263,7 @@ class DataRequestSearch(Search):
|
|
|
263
263
|
)
|
|
264
264
|
if other_product_for_mapping:
|
|
265
265
|
other_product_type_def_params = self.get_product_type_def_params(
|
|
266
|
-
other_product_for_mapping,
|
|
266
|
+
other_product_for_mapping, format_variables=kwargs
|
|
267
267
|
)
|
|
268
268
|
self.config.metadata_mapping.update(
|
|
269
269
|
other_product_type_def_params.get("metadata_mapping", {})
|
eodag/plugins/search/qssearch.py
CHANGED
|
@@ -19,8 +19,8 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import logging
|
|
21
21
|
import re
|
|
22
|
+
import socket
|
|
22
23
|
from copy import copy as copy_copy
|
|
23
|
-
from datetime import datetime, timedelta
|
|
24
24
|
from typing import (
|
|
25
25
|
TYPE_CHECKING,
|
|
26
26
|
Annotated,
|
|
@@ -48,7 +48,6 @@ import geojson
|
|
|
48
48
|
import orjson
|
|
49
49
|
import requests
|
|
50
50
|
import yaml
|
|
51
|
-
from dateutil.utils import today
|
|
52
51
|
from jsonpath_ng import JSONPath
|
|
53
52
|
from lxml import etree
|
|
54
53
|
from pydantic import create_model
|
|
@@ -73,7 +72,6 @@ from eodag.plugins.search.base import Search
|
|
|
73
72
|
from eodag.types import json_field_definition_to_python, model_fields_to_annotated
|
|
74
73
|
from eodag.types.search_args import SortByList
|
|
75
74
|
from eodag.utils import (
|
|
76
|
-
DEFAULT_MISSION_START_DATE,
|
|
77
75
|
DEFAULT_SEARCH_TIMEOUT,
|
|
78
76
|
GENERIC_PRODUCT_TYPE,
|
|
79
77
|
HTTP_REQ_TIMEOUT,
|
|
@@ -125,6 +123,8 @@ class QueryStringSearch(Search):
|
|
|
125
123
|
authentication error; only used if ``need_auth=true``
|
|
126
124
|
* :attr:`~eodag.config.PluginConfig.ssl_verify` (``bool``): if the ssl certificates should be verified in
|
|
127
125
|
requests; default: ``True``
|
|
126
|
+
* :attr:`~eodag.config.PluginConfig.asset_key_from_href` (``bool``): guess assets keys using their ``href``. Use
|
|
127
|
+
their original key if ``False``; default: ``True``
|
|
128
128
|
* :attr:`~eodag.config.PluginConfig.dont_quote` (``list[str]``): characters that should not be quoted in the
|
|
129
129
|
url params
|
|
130
130
|
* :attr:`~eodag.config.PluginConfig.timeout` (``int``): time to wait until request timeout in seconds;
|
|
@@ -533,7 +533,7 @@ class QueryStringSearch(Search):
|
|
|
533
533
|
|
|
534
534
|
prep.info_message = "Fetching product types: {}".format(prep.url)
|
|
535
535
|
prep.exception_message = (
|
|
536
|
-
"Skipping error while fetching product types for
|
|
536
|
+
"Skipping error while fetching product types for {} {} instance:"
|
|
537
537
|
).format(self.provider, self.__class__.__name__)
|
|
538
538
|
|
|
539
539
|
# Query using appropriate method
|
|
@@ -567,7 +567,7 @@ class QueryStringSearch(Search):
|
|
|
567
567
|
result = result[0]
|
|
568
568
|
|
|
569
569
|
def conf_update_from_product_type_result(
|
|
570
|
-
product_type_result: dict[str, Any]
|
|
570
|
+
product_type_result: dict[str, Any],
|
|
571
571
|
) -> None:
|
|
572
572
|
"""Update ``conf_update_dict`` using given product type json response"""
|
|
573
573
|
# providers_config extraction
|
|
@@ -751,7 +751,7 @@ class QueryStringSearch(Search):
|
|
|
751
751
|
|
|
752
752
|
# provider product type specific conf
|
|
753
753
|
prep.product_type_def_params = (
|
|
754
|
-
self.get_product_type_def_params(product_type,
|
|
754
|
+
self.get_product_type_def_params(product_type, format_variables=kwargs)
|
|
755
755
|
if product_type is not None
|
|
756
756
|
else {}
|
|
757
757
|
)
|
|
@@ -775,7 +775,7 @@ class QueryStringSearch(Search):
|
|
|
775
775
|
}
|
|
776
776
|
)
|
|
777
777
|
|
|
778
|
-
qp, qs = self.build_query_string(product_type,
|
|
778
|
+
qp, qs = self.build_query_string(product_type, keywords)
|
|
779
779
|
|
|
780
780
|
prep.query_params = qp
|
|
781
781
|
prep.query_string = qs
|
|
@@ -809,15 +809,15 @@ class QueryStringSearch(Search):
|
|
|
809
809
|
self.config.metadata_mapping.update(metadata_mapping)
|
|
810
810
|
|
|
811
811
|
def build_query_string(
|
|
812
|
-
self, product_type: str,
|
|
812
|
+
self, product_type: str, query_dict: dict[str, Any]
|
|
813
813
|
) -> tuple[dict[str, Any], str]:
|
|
814
814
|
"""Build The query string using the search parameters"""
|
|
815
815
|
logger.debug("Building the query string that will be used for search")
|
|
816
|
-
query_params = format_query_params(product_type, self.config,
|
|
816
|
+
query_params = format_query_params(product_type, self.config, query_dict)
|
|
817
817
|
|
|
818
818
|
# Build the final query string, in one go without quoting it
|
|
819
819
|
# (some providers do not operate well with urlencoded and quoted query strings)
|
|
820
|
-
def quote_via(x: Any, *_args, **_kwargs) -> str:
|
|
820
|
+
def quote_via(x: Any, *_args: Any, **_kwargs: Any) -> str:
|
|
821
821
|
return x
|
|
822
822
|
|
|
823
823
|
return (
|
|
@@ -1074,6 +1074,7 @@ class QueryStringSearch(Search):
|
|
|
1074
1074
|
% normalize_remaining_count
|
|
1075
1075
|
)
|
|
1076
1076
|
products: list[EOProduct] = []
|
|
1077
|
+
asset_key_from_href = getattr(self.config, "asset_key_from_href", True)
|
|
1077
1078
|
for result in results:
|
|
1078
1079
|
product = EOProduct(
|
|
1079
1080
|
self.provider,
|
|
@@ -1091,7 +1092,8 @@ class QueryStringSearch(Search):
|
|
|
1091
1092
|
# move assets from properties to product's attr, normalize keys & roles
|
|
1092
1093
|
for key, asset in product.properties.pop("assets", {}).items():
|
|
1093
1094
|
norm_key, asset["roles"] = product.driver.guess_asset_key_and_roles(
|
|
1094
|
-
asset.get("href", ""),
|
|
1095
|
+
asset.get("href", "") if asset_key_from_href else key,
|
|
1096
|
+
product,
|
|
1095
1097
|
)
|
|
1096
1098
|
if norm_key:
|
|
1097
1099
|
product.assets[norm_key] = asset
|
|
@@ -1259,6 +1261,9 @@ class QueryStringSearch(Search):
|
|
|
1259
1261
|
response.raise_for_status()
|
|
1260
1262
|
except requests.exceptions.Timeout as exc:
|
|
1261
1263
|
raise TimeOutError(exc, timeout=timeout) from exc
|
|
1264
|
+
except socket.timeout:
|
|
1265
|
+
err = requests.exceptions.Timeout(request=requests.Request(url=url))
|
|
1266
|
+
raise TimeOutError(err, timeout=timeout)
|
|
1262
1267
|
except (requests.RequestException, URLError) as err:
|
|
1263
1268
|
err_msg = err.readlines() if hasattr(err, "readlines") else ""
|
|
1264
1269
|
if exception_message:
|
|
@@ -1434,82 +1439,6 @@ class PostJsonSearch(QueryStringSearch):
|
|
|
1434
1439
|
|
|
1435
1440
|
"""
|
|
1436
1441
|
|
|
1437
|
-
def _get_default_end_date_from_start_date(
|
|
1438
|
-
self, start_datetime: str, product_type_conf: dict[str, Any]
|
|
1439
|
-
) -> str:
|
|
1440
|
-
try:
|
|
1441
|
-
start_date = datetime.fromisoformat(start_datetime)
|
|
1442
|
-
except ValueError:
|
|
1443
|
-
start_date = datetime.strptime(start_datetime, "%Y-%m-%dT%H:%M:%SZ")
|
|
1444
|
-
if "completionTimeFromAscendingNode" in product_type_conf:
|
|
1445
|
-
mapping = product_type_conf["completionTimeFromAscendingNode"]
|
|
1446
|
-
# if date is mapped to year/month/(day), use end_date = start_date else start_date + 1 day
|
|
1447
|
-
# (default dates are only needed for ecmwf products where selected timespans should not be too large)
|
|
1448
|
-
if isinstance(mapping, list) and "year" in mapping[0]:
|
|
1449
|
-
end_date = start_date
|
|
1450
|
-
else:
|
|
1451
|
-
end_date = start_date + timedelta(days=1)
|
|
1452
|
-
return end_date.isoformat()
|
|
1453
|
-
return self.get_product_type_cfg_value("missionEndDate", today().isoformat())
|
|
1454
|
-
|
|
1455
|
-
def _check_date_params(
|
|
1456
|
-
self, keywords: dict[str, Any], product_type: Optional[str]
|
|
1457
|
-
) -> None:
|
|
1458
|
-
"""checks if start and end date are present in the keywords and adds them if not"""
|
|
1459
|
-
if (
|
|
1460
|
-
"startTimeFromAscendingNode"
|
|
1461
|
-
and "completionTimeFromAscendingNode" in keywords
|
|
1462
|
-
):
|
|
1463
|
-
return
|
|
1464
|
-
|
|
1465
|
-
product_type_conf = getattr(self.config, "metadata_mapping", {})
|
|
1466
|
-
if (
|
|
1467
|
-
product_type
|
|
1468
|
-
and product_type in self.config.products
|
|
1469
|
-
and "metadata_mapping" in self.config.products[product_type]
|
|
1470
|
-
):
|
|
1471
|
-
product_type_conf = self.config.products[product_type]["metadata_mapping"]
|
|
1472
|
-
# start time given, end time missing
|
|
1473
|
-
if "startTimeFromAscendingNode" in keywords:
|
|
1474
|
-
keywords[
|
|
1475
|
-
"completionTimeFromAscendingNode"
|
|
1476
|
-
] = self._get_default_end_date_from_start_date(
|
|
1477
|
-
keywords["startTimeFromAscendingNode"], product_type_conf
|
|
1478
|
-
)
|
|
1479
|
-
return
|
|
1480
|
-
|
|
1481
|
-
if "completionTimeFromAscendingNode" in product_type_conf:
|
|
1482
|
-
mapping = product_type_conf["startTimeFromAscendingNode"]
|
|
1483
|
-
if not isinstance(mapping, list):
|
|
1484
|
-
mapping = product_type_conf["completionTimeFromAscendingNode"]
|
|
1485
|
-
if isinstance(mapping, list):
|
|
1486
|
-
# get time parameters (date, year, month, ...) from metadata mapping
|
|
1487
|
-
input_mapping = mapping[0].replace("{{", "").replace("}}", "")
|
|
1488
|
-
time_params = [
|
|
1489
|
-
values.split(":")[0].strip() for values in input_mapping.split(",")
|
|
1490
|
-
]
|
|
1491
|
-
time_params = [
|
|
1492
|
-
tp.replace('"', "").replace("'", "") for tp in time_params
|
|
1493
|
-
]
|
|
1494
|
-
# if startTime is not given but other time params (e.g. year/month/(day)) are given,
|
|
1495
|
-
# no default date is required
|
|
1496
|
-
in_keywords = True
|
|
1497
|
-
for tp in time_params:
|
|
1498
|
-
if tp not in keywords:
|
|
1499
|
-
in_keywords = False
|
|
1500
|
-
break
|
|
1501
|
-
if not in_keywords:
|
|
1502
|
-
keywords[
|
|
1503
|
-
"startTimeFromAscendingNode"
|
|
1504
|
-
] = self.get_product_type_cfg_value(
|
|
1505
|
-
"missionStartDate", DEFAULT_MISSION_START_DATE
|
|
1506
|
-
)
|
|
1507
|
-
keywords[
|
|
1508
|
-
"completionTimeFromAscendingNode"
|
|
1509
|
-
] = self._get_default_end_date_from_start_date(
|
|
1510
|
-
keywords["startTimeFromAscendingNode"], product_type_conf
|
|
1511
|
-
)
|
|
1512
|
-
|
|
1513
1442
|
def query(
|
|
1514
1443
|
self,
|
|
1515
1444
|
prep: PreparedSearch = PreparedSearch(),
|
|
@@ -1532,7 +1461,7 @@ class PostJsonSearch(QueryStringSearch):
|
|
|
1532
1461
|
|
|
1533
1462
|
# provider product type specific conf
|
|
1534
1463
|
prep.product_type_def_params = self.get_product_type_def_params(
|
|
1535
|
-
product_type,
|
|
1464
|
+
product_type, format_variables=kwargs
|
|
1536
1465
|
)
|
|
1537
1466
|
else:
|
|
1538
1467
|
keywords = {
|
|
@@ -1546,7 +1475,7 @@ class PostJsonSearch(QueryStringSearch):
|
|
|
1546
1475
|
|
|
1547
1476
|
# provider product type specific conf
|
|
1548
1477
|
prep.product_type_def_params = self.get_product_type_def_params(
|
|
1549
|
-
product_type,
|
|
1478
|
+
product_type, format_variables=kwargs
|
|
1550
1479
|
)
|
|
1551
1480
|
|
|
1552
1481
|
# Add to the query, the queryable parameters set in the provider product type definition
|
|
@@ -1559,10 +1488,8 @@ class PostJsonSearch(QueryStringSearch):
|
|
|
1559
1488
|
and isinstance(self.config.metadata_mapping[k], list)
|
|
1560
1489
|
}
|
|
1561
1490
|
)
|
|
1562
|
-
if getattr(self.config, "dates_required", False):
|
|
1563
|
-
self._check_date_params(keywords, product_type)
|
|
1564
1491
|
|
|
1565
|
-
qp, _ = self.build_query_string(product_type,
|
|
1492
|
+
qp, _ = self.build_query_string(product_type, keywords)
|
|
1566
1493
|
|
|
1567
1494
|
for query_param, query_value in qp.items():
|
|
1568
1495
|
if (
|
|
@@ -1845,24 +1772,24 @@ class StacSearch(PostJsonSearch):
|
|
|
1845
1772
|
self.config.results_entry = results_entry
|
|
1846
1773
|
|
|
1847
1774
|
def build_query_string(
|
|
1848
|
-
self, product_type: str,
|
|
1775
|
+
self, product_type: str, query_dict: dict[str, Any]
|
|
1849
1776
|
) -> tuple[dict[str, Any], str]:
|
|
1850
1777
|
"""Build The query string using the search parameters"""
|
|
1851
1778
|
logger.debug("Building the query string that will be used for search")
|
|
1852
1779
|
|
|
1853
1780
|
# handle opened time intervals
|
|
1854
1781
|
if any(
|
|
1855
|
-
|
|
1856
|
-
for
|
|
1782
|
+
q in query_dict
|
|
1783
|
+
for q in ("startTimeFromAscendingNode", "completionTimeFromAscendingNode")
|
|
1857
1784
|
):
|
|
1858
|
-
|
|
1859
|
-
|
|
1785
|
+
query_dict.setdefault("startTimeFromAscendingNode", "..")
|
|
1786
|
+
query_dict.setdefault("completionTimeFromAscendingNode", "..")
|
|
1860
1787
|
|
|
1861
|
-
query_params = format_query_params(product_type, self.config,
|
|
1788
|
+
query_params = format_query_params(product_type, self.config, query_dict)
|
|
1862
1789
|
|
|
1863
1790
|
# Build the final query string, in one go without quoting it
|
|
1864
1791
|
# (some providers do not operate well with urlencoded and quoted query strings)
|
|
1865
|
-
def quote_via(x: Any, *_args, **_kwargs) -> str:
|
|
1792
|
+
def quote_via(x: Any, *_args: Any, **_kwargs: Any) -> str:
|
|
1866
1793
|
return x
|
|
1867
1794
|
|
|
1868
1795
|
return (
|
|
@@ -1919,7 +1846,8 @@ class StacSearch(PostJsonSearch):
|
|
|
1919
1846
|
return None
|
|
1920
1847
|
|
|
1921
1848
|
fetch_url = unparsed_fetch_url.format(
|
|
1922
|
-
provider_product_type=provider_product_type,
|
|
1849
|
+
provider_product_type=provider_product_type,
|
|
1850
|
+
**self.config.__dict__,
|
|
1923
1851
|
)
|
|
1924
1852
|
auth = (
|
|
1925
1853
|
self.auth
|
|
@@ -1936,7 +1864,8 @@ class StacSearch(PostJsonSearch):
|
|
|
1936
1864
|
"{} {} instance:".format(self.provider, self.__class__.__name__),
|
|
1937
1865
|
),
|
|
1938
1866
|
)
|
|
1939
|
-
except (RequestError, KeyError, AttributeError):
|
|
1867
|
+
except (RequestError, KeyError, AttributeError) as e:
|
|
1868
|
+
logger.warning("failure in queryables discovery: %s", e)
|
|
1940
1869
|
return None
|
|
1941
1870
|
else:
|
|
1942
1871
|
json_queryables = dict()
|
|
@@ -2001,7 +1930,7 @@ class PostJsonSearchWithStacQueryables(StacSearch, PostJsonSearch):
|
|
|
2001
1930
|
PostJsonSearch.__init__(self, provider, config)
|
|
2002
1931
|
|
|
2003
1932
|
def build_query_string(
|
|
2004
|
-
self, product_type: str,
|
|
1933
|
+
self, product_type: str, query_dict: dict[str, Any]
|
|
2005
1934
|
) -> tuple[dict[str, Any], str]:
|
|
2006
1935
|
"""Build The query string using the search parameters"""
|
|
2007
|
-
return PostJsonSearch.build_query_string(self, product_type,
|
|
1936
|
+
return PostJsonSearch.build_query_string(self, product_type, query_dict)
|
|
@@ -142,7 +142,7 @@ class StaticStacSearch(StacSearch):
|
|
|
142
142
|
product_type = kwargs.get("productType", prep.product_type)
|
|
143
143
|
# provider product type specific conf
|
|
144
144
|
self.product_type_def_params = (
|
|
145
|
-
self.get_product_type_def_params(product_type,
|
|
145
|
+
self.get_product_type_def_params(product_type, format_variables=kwargs)
|
|
146
146
|
if product_type is not None
|
|
147
147
|
else {}
|
|
148
148
|
)
|