20
20
import requests
21
21
import sqlite3
22
22
23
- from ..io import HOME , DATA_NAME
23
+ from ..project import Project
24
24
25
25
class APIDownloader (object ):
26
26
"""
@@ -55,7 +55,7 @@ class APIDownloader(object):
55
55
area_of_interest : gpd.GeoDataFrame, shapely geometry, or bounding box, optional
56
56
The spatial boundary to subset. Bounding boxes should
57
57
match GeoDataFrame.total_bounds style.
58
- auth_method :
58
+ auth_method :
59
59
60
60
Attributes
61
61
----------
@@ -72,17 +72,20 @@ class APIDownloader(object):
72
72
def __init__ (
73
73
self ,
74
74
download_label = "earthpy-downloads" ,
75
- project_dir = os .path .join (HOME , DATA_NAME ),
76
75
start_date = None , end_date = None ,
77
76
start_doy = None , end_doy = None , months = None , seasons = None ,
78
77
start_year = None , end_year = None ,
79
78
area_of_interest = None ,
80
79
resubmit_in_progress = False ,
81
80
auth_method = 'keyring' ):
82
-
81
+ # Initialize project
82
+ if 'EARTHPY_PROJECT_DATA_DIR' in os .environ :
83
+ self .project_dir = os .environ ['EARTHPY_PROJECT_DATA_DIR' ]
84
+ else :
85
+ self .project_dir = Project ().project_dir
86
+
83
87
# Initialize file structure
84
88
self .download_label = download_label
85
- self .project_dir = project_dir
86
89
self .download_dir = os .path .join (self .project_dir , self .download_label )
87
90
os .makedirs (self .download_dir , exist_ok = True )
88
91
self .db_path = os .path .join (self .download_dir , 'db.sqlite' )
@@ -104,8 +107,6 @@ def __init__(
104
107
self .end_year = end_year
105
108
self .area_of_interest = area_of_interest
106
109
107
-
108
-
109
110
self .auth_method = auth_method
110
111
111
112
# Set up task id
@@ -152,245 +153,4 @@ def _init_database(self):
152
153
FOREIGN KEY(request_id) REFERENCES download_requests(request_id)
153
154
);
154
155
''' )
155
- conn .commit ()
156
-
157
-
158
- def appeears_request (
159
- self , endpoint ,
160
- method = 'POST' , req_json = None , stream = False ,
161
- ** parameters ):
162
- """
163
- Submits a request to the AppEEARS API
164
-
165
- Parameters
166
- ----------
167
- endpoint : str
168
- The API endpoint from
169
- https://appeears.earthdatacloud.nasa.gov/api/
170
- method : str
171
- HTTP method 'GET' or 'POST'
172
- json : dictlike, optional
173
- JSON to submit with the request (for the task endpoint)
174
- **parameters : dict, optional
175
- Named parameters to format into the endpoint
176
- """
177
-
178
- logging .info ('Submitting {} request...' .format (endpoint ))
179
-
180
- kwargs = {
181
- 'url' : self .base_url + endpoint .format (** parameters ),
182
- 'headers' : {'Authorization' : self .auth_header }
183
- }
184
- if req_json :
185
- logging .debug ('Submitting task with JSON\n {}' .format (
186
- json .dumps (req_json )))
187
- kwargs ['json' ] = req_json
188
-
189
- # Stream file downloads
190
- if stream :
191
- kwargs ['allow_redirects' ] = True
192
- kwargs ['stream' ] = True
193
-
194
- # Submit request
195
- response = requests .request (method = method , ** kwargs )
196
- logging .debug ('RESPONSE TEXT: \n {}' .format (response .text ))
197
- response .raise_for_status ()
198
-
199
- logging .info ('{} request successfully completed' .format (endpoint ))
200
-
201
- return response
202
-
203
-
204
- def login (self , service = 'NASA_EARTHDATA' , username_id = 'NED_USERNAME' ):
205
- """
206
- Logs in to the AppEEARS API.
207
-
208
- Login happens automatically when self.auth_header is
209
- requested. Call this function to use a customized
210
- service name in the keyring, or set the self._auth_header
211
- value manually for other custom situations.
212
-
213
- Parameters
214
- ----------
215
- service : str, optional
216
- The name under which to store the credential in keyring
217
- """
218
- # Get username and password from keyring
219
- try :
220
- username = keyring .get_password (service , username_id )
221
- password = keyring .get_password (service , username )
222
- except :
223
- username = None
224
- password = None
225
-
226
- # Get username and password from environment
227
- try :
228
- username = os .environ ['EARTHDATA_USERNAME' ]
229
- password = os .environ ['EARTHDATA_PASSWORD' ]
230
- except :
231
- username = None
232
- password = None
233
-
234
- # Prompt user if no username or password is stored
235
- if (username is None ) or (password is None ):
236
- # Ask for the user's username and password
237
- username = input ('NASA Earthdata Username: ' )
238
- password = getpass .getpass ('NASA Earthdata Password: ' )
239
- try :
240
- keyring .set_password (service , username_id , username )
241
- keyring .set_password (service , username , password )
242
- except :
243
- pass
244
-
245
- logging .info ('Logging into AppEEARS API...' )
246
-
247
- # Set up authentication and submit login request
248
- login_resp = requests .post (
249
- self .base_url + 'login' ,
250
- auth = (username , password ))
251
- login_resp .raise_for_status ()
252
-
253
- self ._auth_header = (
254
- '{token_type} {token}' .format (** login_resp .json ()))
255
-
256
- logging .info (
257
- 'Login successful. Auth Header: {}' .format (self ._auth_header ))
258
-
259
- @property
260
- def auth_header (self ):
261
- if not self ._auth_header :
262
- self .login ()
263
- return self ._auth_header
264
-
265
- @property
266
- def task_id (self ):
267
- if not self ._task_id :
268
- self .submit_task_request ()
269
- return self ._task_id
270
-
271
- @property
272
- def task_status (self ):
273
- if self ._status != 'done' :
274
- self .wait_for_task ()
275
- return self ._status
276
-
277
- def submit_task_request (self ):
278
- """
279
- Submit task request for the object parameters
280
-
281
- This function is automatically called when self.task_id
282
- is requested. Set self._task_id to override.
283
- """
284
- # Task parameters
285
- task = {
286
- 'task_type' : 'area' ,
287
- 'task_name' : self .download_key ,
288
- 'params' : {
289
- 'dates' : [
290
- {
291
- 'startDate' : self ._start_date ,
292
- 'endDate' : self ._end_date
293
- }
294
- ],
295
- 'layers' : [
296
- {
297
- 'product' : self ._product ,
298
- 'layer' : self ._layer
299
- }
300
- ],
301
- # Need subdivisions as json, not as a string
302
- "geo" : json .loads (self ._polygon .dissolve ().envelope .to_json ()),
303
- "output" : {
304
- "format" : {"type" : "geotiff" },
305
- "projection" : "geographic"
306
- }
307
- }
308
- }
309
-
310
- if self ._recurring :
311
- if self ._year_range is None :
312
- raise ValueError (
313
- 'Must supply year range for recurring dates' )
314
- task ['params' ]['dates' ][0 ]['recurring' ] = True
315
- task ['params' ]['dates' ][0 ]['yearRange' ] = self ._year_range
316
-
317
- # Submit the task request
318
- task_response = self .appeears_request ('task' , req_json = task )
319
-
320
- # Save task ID for later
321
- self ._task_id = task_response .json ()['task_id' ]
322
- with open (self .task_id_path , 'w' ) as task_id_file :
323
- task_id_file .write (self ._task_id )
324
-
325
- def wait_for_task (self ):
326
- """
327
- Waits for the AppEEARS service to prepare data subset
328
- """
329
- self ._status = 'initializing'
330
- while self ._status != 'done' :
331
- time .sleep (3 )
332
- # Wait 20 seconds in between status checks
333
- if self ._status != 'initializing' :
334
- time .sleep (20 )
335
-
336
- # Check status
337
- status_response = self .appeears_request (
338
- 'status/{task_id}' , method = 'GET' , task_id = self .task_id )
339
-
340
- # Update status
341
- if 'progress' in status_response .json ():
342
- self ._status = status_response .json ()['progress' ]['summary' ]
343
- elif 'status' in status_response .json ():
344
- self ._status = status_response .json ()['status' ]
345
-
346
- logging .info (self ._status )
347
- logging .info ('Task completed - ready for download.' )
348
-
349
- def download_files (self , cache = True ):
350
- """
351
- Streams all prepared file downloads
352
-
353
- Parameters
354
- ----------
355
- cache : bool
356
- Use cache to avoid repeat downloads
357
- """
358
- status = self .task_status
359
- logging .info ('Current task status: {}' .format (status ))
360
-
361
- # Get file download information
362
- bundle_response = self .appeears_request (
363
- 'bundle/{task_id}' ,
364
- method = 'GET' ,
365
- task_id = self .task_id )
366
-
367
- files = bundle_response .json ()['files' ]
368
-
369
- '{} files available for download' .format (len (files ))
370
-
371
- # Download files
372
- for file_info in files :
373
- # Get a stream to the bundle file
374
- response = self .appeears_request (
375
- 'bundle/{task_id}/{file_id}' ,
376
- method = 'GET' , task_id = self .task_id , stream = True ,
377
- file_id = file_info ['file_id' ])
378
-
379
- # Create a destination directory to store the file in
380
- filepath = os .path .join (self .data_dir , file_info ['file_name' ])
381
- if not os .path .exists (os .path .dirname (filepath )):
382
- os .makedirs (os .path .dirname (filepath ))
383
-
384
- # Write the file to the destination directory
385
- if os .path .exists (filepath ) and cache :
386
- logging .info (
387
- 'File at {} alreading exists. Skipping...'
388
- .format (filepath ))
389
- else :
390
- logging .info ('Downloading file {}' .format (filepath ))
391
- with open (filepath , 'wb' ) as f :
392
- for data in response .iter_content (chunk_size = 8192 ):
393
- f .write (data )
394
-
395
- # Remove task id file when download is complete
396
- os .remove (self .task_id_path )
156
+ conn .commit ()
0 commit comments