2121import zipfile
2222from datetime import datetime
2323
24+ from eodag .api .search_result import SearchResult
2425from eodag .plugins .apis .base import Api
2526from eodag .plugins .search .qssearch import ODataV4Search
2627from eodag .utils import get_progress_callback
27- from sentinelsat import SentinelAPI
28+ from eodag .utils .exceptions import MisconfiguredError , RequestError
29+ from sentinelsat import SentinelAPI , SentinelAPIError
2830
2931logger = logging .getLogger ("eodag.plugins.apis.sentinelsat" )
3032
@@ -35,6 +37,13 @@ class SentinelsatAPI(Api, ODataV4Search):
3537
3638 Api that enables to search and download EO products from catalogs implementing the SchiHub interface.
3739 It is basically a wrapper around sentinelsat, enabling it to be used on eodag.
40+
41+ We use the API to download data. Available keywords are:
42+ [area, date, raw, area_relation, order_by, limit, offset, **keywords]
43+ https://sentinelsat.readthedocs.io/en/stable/api.html#sentinelsat.SentinelAPI.query
44+
45+ The keywords are those that can be found here:
46+ https://sentinelsat.readthedocs.io/en/stable/api.html#opensearch-example
3847 """
3948
4049 def __init__ (self , provider , config ):
@@ -94,6 +103,22 @@ def query(self, items_per_page=None, page=None, count=True, **kwargs):
94103 )
95104 logger .info ("No results found !" )
96105
106+ except SentinelAPIError as ex :
107+ # TODO: change it to ServerError when ssat 0.15 will be published !
108+ """
109+ SentinelAPIError -- the parent, catch-all exception. Only used when no other more specific exception
110+ can be applied.
111+ SentinelAPILTAError -- raised when retrieving a product from the Long Term Archive.
112+ ServerError -- raised when the server responded in an unexpected manner, typically due to undergoing
113+ maintenance.
114+ UnauthorizedError -- raised when attempting to retrieve a product with incorrect credentials.
115+ QuerySyntaxError -- raised when the query string could not be parsed on the server side.
116+ QueryLengthError -- raised when the query string length was excessively long.
117+ InvalidKeyError -- raised when product with given key was not found on the server.
118+ InvalidChecksumError -- MD5 checksum of a local file does not match the one from the server.
119+ """
120+ raise RequestError (ex ) from ex
121+
97122 return eo_products , len (eo_products )
98123
99124 def download (self , product , auth = None , progress_callback = None , ** kwargs ) -> str :
@@ -106,35 +131,19 @@ def download(self, product, auth=None, progress_callback=None, **kwargs) -> str:
106131 :param kwargs: Not used, just here for compatibility reasons
107132 :return: Downloaded product path
108133 """
109- # Init Sentinelsat API if needed (connect...)
110- self ._init_api ()
111-
112- # Download all products
113- prod_id = product .properties ["id" ]
114- product_info = self .api .download_all (
115- [prod_id ], directory_path = self .config .outputs_prefix
134+ prods = self .download_all (
135+ SearchResult (
136+ [
137+ product ,
138+ ]
139+ ),
140+ auth ,
141+ progress_callback ,
142+ ** kwargs
116143 )
117144
118- # Only select the downloaded products
119- product_info = product_info [0 ][prod_id ]
120-
121- # Extract them if needed
122- if self .config .extract and product_info ["path" ].endswith (".zip" ):
123- logger .info ("Extraction activated" )
124- with zipfile .ZipFile (product_info ["path" ], "r" ) as zfile :
125- fileinfos = zfile .infolist ()
126- with get_progress_callback () as bar :
127- bar .max_size = len (fileinfos )
128- bar .unit = "file"
129- bar .desc = "Extracting files from {}" .format (product_info ["path" ])
130- bar .unit_scale = False
131- bar .position = 2
132- for fileinfo in fileinfos :
133- zfile .extract (fileinfo , path = self .config .outputs_prefix )
134- bar (1 )
135- return product_info ["path" ][: product_info ["path" ].index (".zip" )]
136- else :
137- return product_info ["path" ]
145+ # Manage the case if nothing has been downloaded
146+ return prods [0 ] if len (prods ) > 0 else ""
138147
139148 def download_all (
140149 self , search_result , auth = None , progress_callback = None , ** kwargs
@@ -154,44 +163,51 @@ def download_all(
154163
155164 # Download all products
156165 prod_ids = [prod .properties ["uuid" ] for prod in search_result .data ]
157- product_info = self .api .download_all (
166+ success , _ , _ = self .api .download_all (
158167 prod_ids , directory_path = self .config .outputs_prefix
159168 )
160169
161- # Only select the downloaded products
162- paths = []
163- for prod_id in prod_ids :
164- info = product_info [0 ][prod_id ]
165-
166- # Extract them if needed
167- if self .config .extract and info ["path" ].endswith (".zip" ):
168- logger .info ("Extraction activated" )
169- with zipfile .ZipFile (info ["path" ], "r" ) as zfile :
170- fileinfos = zfile .infolist ()
171- with get_progress_callback () as bar :
172- bar .max_size = len (fileinfos )
173- bar .unit = "file"
174- bar .desc = "Extracting files from {}" .format (info ["path" ])
175- bar .unit_scale = False
176- bar .position = 2
177- for fileinfo in fileinfos :
178- zfile .extract (fileinfo , path = self .config .outputs_prefix )
179- bar (1 )
180- paths .append (info ["path" ][: info ["path" ].index (".zip" )])
181- else :
182- paths .append (info ["path" ])
183-
170+ # Only extract the successfully downloaded products
171+ paths = [self .extract (prods ) for prods in success .values ()]
184172 return paths
185173
186- def _init_api (self ):
174+ def extract (self , product_info : dict ) -> str :
175+ """
176+ Extract products if needed.
177+
178+ :param product_info: Product info
179+ :return: Path (archive or extracted according to the config)
180+ """
181+ # Extract them if needed
182+ if self .config .extract and product_info ["path" ].endswith (".zip" ):
183+ logger .info ("Extraction activated" )
184+ with zipfile .ZipFile (product_info ["path" ], "r" ) as zfile :
185+ fileinfos = zfile .infolist ()
186+ with get_progress_callback () as bar :
187+ bar .max_size = len (fileinfos )
188+ bar .unit = "file"
189+ bar .desc = "Extracting files from {}" .format (product_info ["path" ])
190+ bar .unit_scale = False
191+ bar .position = 2
192+ for fileinfo in fileinfos :
193+ zfile .extract (fileinfo , path = self .config .outputs_prefix )
194+ bar (1 )
195+ return product_info ["path" ][: product_info ["path" ].index (".zip" )]
196+ else :
197+ return product_info ["path" ]
198+
199+ def _init_api (self ) -> None :
187200 """Initialize Sentinelsat API if needed (connection and link)."""
188201 if not self .api :
189- logger .debug ("Initializing Sentinelsat API" )
190- self .api = SentinelAPI (
191- self .config .credentials ["username" ],
192- self .config .credentials ["password" ],
193- self .config .endpoint ,
194- )
202+ try :
203+ logger .debug ("Initializing Sentinelsat API" )
204+ self .api = SentinelAPI (
205+ self .config .credentials ["username" ],
206+ self .config .credentials ["password" ],
207+ self .config .endpoint ,
208+ )
209+ except KeyError as ex :
210+ raise MisconfiguredError (ex ) from ex
195211 else :
196212 logger .debug ("Sentinelsat API already initialized" )
197213
@@ -242,7 +258,7 @@ def update_keyword(self, **kwargs):
242258 )
243259
244260 # Footprint
245- if "area" in qp :
246- qp ["area" ] = qp . pop ( "area" ). wkt
261+ if "area" in qp and isinstance ( qp [ "area" ], list ) :
262+ qp ["area" ] = qp [ "area" ][ 0 ]
247263
248264 return qp , provider_product_type
0 commit comments