scc_access/scc_access.py

changeset 28
2d90204710af
parent 19
2a6a9e95bd16
parent 26
0aedb8dd6861
child 30
4669876326d4
equal deleted inserted replaced
27:8ebbba085eb0 28:2d90204710af
7 import urllib.parse as urlparse # Python 3 7 import urllib.parse as urlparse # Python 3
8 except ImportError: 8 except ImportError:
9 import urlparse # Python 2 9 import urlparse # Python 2
10 10
11 import argparse 11 import argparse
12 import datetime
13 import logging
12 import os 14 import os
13 import re 15 import re
16 from io import StringIO
17 import sys
14 import time 18 import time
15 from io import StringIO 19 import urlparse
16 from zipfile import ZipFile 20 from zipfile import ZipFile
17 import datetime 21
18 import logging 22 import requests
19 import yaml 23 import yaml
20 24
25 requests.packages.urllib3.disable_warnings()
21 26
22 logger = logging.getLogger(__name__) 27 logger = logging.getLogger(__name__)
23 28
24 # The regex to find the measurement id from the measurement page 29 # The regex to find the measurement id from the measurement page
25 # This should be read from the uploaded file, but would require an extra NetCDF module. 30 # This should be read from the uploaded file, but would require an extra NetCDF module.
26 regex = "<h3>Measurement (?P<measurement_id>.{12}) <small>" 31 regex = "<h3>Measurement (?P<measurement_id>.{12}) <small>"
27 32
28 33
29 class SCC: 34 class SCC:
30 """ A simple class that will attempt to upload a file on the SCC server. 35 """A simple class that will attempt to upload a file on the SCC server.
31 36
32 The uploading is done by simulating a normal browser session. In the current 37 The uploading is done by simulating a normal browser session. In the current
33 version no check is performed, and no feedback is given if the upload 38 version no check is performed, and no feedback is given if the upload
34 was successful. If everything is setup correctly, it will work. 39 was successful. If everything is setup correctly, it will work.
35 """ 40 """
36 41
37 def __init__(self, auth, output_dir, base_url): 42 def __init__(self, auth, output_dir, base_url):
38 self.auth = auth 43 self.auth = auth
39 self.output_dir = output_dir 44 self.output_dir = output_dir
66 if login_page.status_code != 200: 71 if login_page.status_code != 200:
67 logger.error('Could not access login pages. Status code %s' % login_page.status_code) 72 logger.error('Could not access login pages. Status code %s' % login_page.status_code)
68 sys.exit(1) 73 sys.exit(1)
69 74
70 logger.debug("Submiting credentials.") 75 logger.debug("Submiting credentials.")
71 76
72 # Submit the login data 77 # Submit the login data
73 login_submit = self.session.post(self.login_url, 78 login_submit = self.session.post(self.login_url,
74 data=self.login_credentials, 79 data=self.login_credentials,
75 headers={'X-CSRFToken': login_page.cookies['csrftoken'], 80 headers={'X-CSRFToken': login_page.cookies['csrftoken'],
76 'referer': self.login_url}, 81 'referer': self.login_url},
79 return login_submit 84 return login_submit
80 85
81 def logout(self): 86 def logout(self):
82 pass 87 pass
83 88
84 def upload_file(self, filename, system_id): 89 def upload_file(self, filename, system_id, rs_filename=None):
85 """ Upload a filename for processing with a specific system. If the 90 """ Upload a filename for processing with a specific system. If the
86 upload is successful, it returns the measurement id. """ 91 upload is successful, it returns the measurement id. """
87 # Get submit page 92 # Get submit page
88 upload_page = self.session.get(self.upload_url, 93 upload_page = self.session.get(self.upload_url,
89 auth=self.auth, 94 auth=self.auth,
90 verify=False) 95 verify=False)
91 96
92 # Submit the data 97 # Submit the data
93 upload_data = {'system': system_id} 98 upload_data = {'system': system_id}
94 files = {'data': open(filename, 'rb')} 99 files = {'data': open(filename, 'rb')}
100
101 if rs_filename is not None:
102 files['sounding_file'] = open(rs_filename, 'rb')
95 103
96 logger.info("Uploading of file %s started." % filename) 104 logger.info("Uploading of file %s started." % filename)
97 105
98 upload_submit = self.session.post(self.upload_url, 106 upload_submit = self.session.post(self.upload_url,
99 data=upload_data, 107 data=upload_data,
117 125
118 return measurement_id 126 return measurement_id
119 127
120 def download_files(self, measurement_id, subdir, download_url): 128 def download_files(self, measurement_id, subdir, download_url):
121 """ Downloads some files from the download_url to the specified 129 """ Downloads some files from the download_url to the specified
122 subdir. This method is used to download preprocessed file, optical 130 subdir. This method is used to download preprocessed file, optical
123 files etc. 131 files etc.
124 """ 132 """
125 # Get the file 133 # Get the file
126 request = self.session.get(download_url, auth=self.auth, 134 request = self.session.get(download_url, auth=self.auth,
127 verify=False, 135 verify=False,
167 # Construct the download url 175 # Construct the download url
168 download_url = self.download_graph_pattern.format(measurement_id) 176 download_url = self.download_graph_pattern.format(measurement_id)
169 self.download_files(measurement_id, 'scc_plots', download_url) 177 self.download_files(measurement_id, 'scc_plots', download_url)
170 178
171 def rerun_processing(self, measurement_id, monitor=True): 179 def rerun_processing(self, measurement_id, monitor=True):
172 measurement = self.get_measurement(measurement_id) 180 measurement, status = self.get_measurement(measurement_id)
173 181
174 if measurement: 182 if measurement:
175 request = self.session.get(measurement.rerun_processing_url, auth=self.auth, 183 request = self.session.get(measurement.rerun_processing_url, auth=self.auth,
176 verify=False, 184 verify=False,
177 stream=True) 185 stream=True)
186 194
187 def rerun_all(self, measurement_id, monitor=True): 195 def rerun_all(self, measurement_id, monitor=True):
188 logger.debug("Started rerun_all procedure.") 196 logger.debug("Started rerun_all procedure.")
189 197
190 logger.debug("Getting measurement %s" % measurement_id) 198 logger.debug("Getting measurement %s" % measurement_id)
191 measurement = self.get_measurement(measurement_id) 199 measurement, status = self.get_measurement(measurement_id)
192 200
193 if measurement: 201 if measurement:
194 logger.debug("Attempting to rerun all processing through %s." % measurement.rerun_all_url) 202 logger.debug("Attempting to rerun all processing through %s." % measurement.rerun_all_url)
195 203
196 request = self.session.get(measurement.rerun_all_url, auth=self.auth, 204 request = self.session.get(measurement.rerun_all_url, auth=self.auth,
203 return 211 return
204 212
205 if monitor: 213 if monitor:
206 self.monitor_processing(measurement_id) 214 self.monitor_processing(measurement_id)
207 215
208 def process(self, filename, system_id): 216 def process(self, filename, system_id, rs_filename=None):
209 """ Upload a file for processing and wait for the processing to finish. 217 """ Upload a file for processing and wait for the processing to finish.
210 If the processing is successful, it will download all produced files. 218 If the processing is successful, it will download all produced files.
211 """ 219 """
212 logger.info("--- Processing started on %s. ---" % datetime.datetime.now()) 220 logger.info("--- Processing started on %s. ---" % datetime.datetime.now())
213 # Upload file 221 # Upload file
214 measurement_id = self.upload_file(filename, system_id) 222 logger.info("--- Uploading file")
215 223 measurement_id = self.upload_file(filename, system_id, rs_filename=rs_filename)
224
225 logger.info("--- Monitoring processing")
216 measurement = self.monitor_processing(measurement_id) 226 measurement = self.monitor_processing(measurement_id)
217 return measurement 227 return measurement
218 228
219 def monitor_processing(self, measurement_id): 229 def monitor_processing(self, measurement_id):
220 """ Monitor the processing progress of a measurement id""" 230 """ Monitor the processing progress of a measurement id"""
221 231
222 measurement = self.get_measurement(measurement_id) 232 # try to deal with error 404
233 error_count = 0
234 error_max = 6
235 time_sleep = 10
236
237 # try to wait for measurement to appear in API
238 measurement = None
239 logger.info("looking for measurement %s in SCC", measurement_id)
240 while error_count < error_max:
241 time.sleep(time_sleep)
242 measurement, status = self.get_measurement(measurement_id)
243 if status != 200 and error_count < error_max:
244 logger.error("measurement not found. waiting %ds", time_sleep)
245 error_count += 1
246 else:
247 break
248
249 if error_count == error_max:
250 logger.critical("measurement %s doesn't seem to exist", measurement_id)
251 sys.exit(1)
252
253 logger.info('measurement %s found', measurement_id)
254
223 if measurement is not None: 255 if measurement is not None:
256 error_count = 0
224 while measurement.is_running: 257 while measurement.is_running:
225 logger.info("Measurement is being processed (status: %s, %s, %s). Please wait." % (measurement.upload, 258 logger.info("Measurement is being processed (status: %s, %s, %s). Please wait.", measurement.upload, measurement.pre_processing, measurement.processing)
226 measurement.pre_processing,
227 measurement.processing))
228 time.sleep(10) 259 time.sleep(10)
229 measurement = self.get_measurement(measurement_id) 260 measurement, status = self.get_measurement(measurement_id)
230 logger.info("Measurement processing finished (status: %s, %s, %s)." % (measurement.upload, 261
231 measurement.pre_processing, 262 logger.info("Measurement processing finished (status: %s, %s, %s).",measurement.upload, measurement.pre_processing, measurement.processing)
232 measurement.processing))
233 if measurement.pre_processing == 127: 263 if measurement.pre_processing == 127:
234 logger.info("Downloading preprocessed files.") 264 logger.info("Downloading preprocessed files.")
235 self.download_preprocessed(measurement_id) 265 self.download_preprocessed(measurement_id)
236 if measurement.processing == 127: 266 if measurement.processing == 127:
237 logger.info("Downloading optical files.") 267 logger.info("Downloading optical files.")
264 294
265 response = self.session.get(measurement_url, 295 response = self.session.get(measurement_url,
266 auth=self.auth, 296 auth=self.auth,
267 verify=False) 297 verify=False)
268 298
299 # maybe the measurements isn't already available on the database.
300 if response.status_code == 404:
301 return None, 404
302
269 if response.status_code != 200: 303 if response.status_code != 200:
270 logger.error('Could not access API. Status code %s.' % response.status_code) 304 logger.error('Could not access API. Status code %s.' % response.status_code)
271 sys.exit(1) 305 return None, response.status_code
272 306
273 response_dict = response.json() 307 response_dict = response.json()
274 308
275 if response_dict: 309 if response_dict:
276 measurement = Measurement(self.base_url,response_dict) 310 measurement = Measurement(self.base_url, response_dict)
277 return measurement 311 return measurement, response.status_code
278 else: 312 else:
279 logger.error("No measurement with id %s found on the SCC." % measurement_id) 313 logger.error("No measurement with id %s found on the SCC." % measurement_id)
280 return None 314 return None, response.status_code
281 315
282 def delete_measurement(self, measurement_id): 316 def delete_measurement(self, measurement_id):
283 """ Deletes a measurement with the provided measurement id. The user 317 """ Deletes a measurement with the provided measurement id. The user
284 should have the appropriate permissions. 318 should have the appropriate permissions.
285 319
286 The procedures is performed directly through the web interface and 320 The procedures is performed directly through the web interface and
287 NOT through the API. 321 NOT through the API.
288 """ 322 """
289 # Get the measurement object 323 # Get the measurement object
290 measurement = self.get_measurement(measurement_id) 324 measurement, status = self.get_measurement(measurement_id)
291 325
292 # Check that it exists 326 # Check that it exists
293 if measurement is None: 327 if measurement is None:
294 logger.warning("Nothing to delete.") 328 logger.warning("Nothing to delete.")
295 return None 329 return None
340 374
341 return measurements 375 return measurements
342 376
343 def measurement_id_for_date(self, t1, call_sign='bu', base_number=0): 377 def measurement_id_for_date(self, t1, call_sign='bu', base_number=0):
344 """ Give the first available measurement id on the SCC for the specific 378 """ Give the first available measurement id on the SCC for the specific
345 date. 379 date.
346 """ 380 """
347 date_str = t1.strftime('%Y%m%d') 381 date_str = t1.strftime('%Y%m%d')
348 search_url = urlparse.urljoin(self.api_base_url, 'measurements/?id__startswith=%s' % date_str) 382 search_url = urlparse.urljoin(self.api_base_url, 'measurements/?id__startswith=%s' % date_str)
349 383
350 response = self.session.get(search_url, 384 response = self.session.get(search_url,
422 self.upload, 456 self.upload,
423 self.pre_processing, 457 self.pre_processing,
424 self.processing) 458 self.processing)
425 459
426 460
427 def upload_file(filename, system_id, settings): 461 def upload_file(filename, system_id, settings, rs_filename=None):
428 """ Shortcut function to upload a file to the SCC. """ 462 """ Shortcut function to upload a file to the SCC. """
429 logger.info("Uploading file %s, using sytem %s" % (filename, system_id)) 463 logger.info("Uploading file %s, using sytem %s" % (filename, system_id))
430 464
431 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) 465 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url'])
432 scc.login(settings['website_credentials']) 466 scc.login(settings['website_credentials'])
433 measurement_id = scc.upload_file(filename, system_id) 467 measurement_id = scc.upload_file(filename, system_id, rs_filename=rs_filename)
434 scc.logout() 468 scc.logout()
435 return measurement_id 469 return measurement_id
436 470
437 471
438 def process_file(filename, system_id, settings): 472 def process_file(filename, system_id, settings, rs_filename=None):
439 """ Shortcut function to process a file to the SCC. """ 473 """ Shortcut function to process a file to the SCC. """
440 logger.info("Processing file %s, using sytem %s" % (filename, system_id)) 474 logger.info("Processing file %s, using sytem %s" % (filename, system_id))
441 475
442 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) 476 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url'])
443 scc.login(settings['website_credentials']) 477 scc.login(settings['website_credentials'])
444 measurement = scc.process(filename, system_id) 478 measurement = scc.process(filename, system_id, rs_filename=rs_filename)
445 scc.logout() 479 scc.logout()
446 return measurement 480 return measurement
447 481
448 482
449 def delete_measurement(measurement_id, settings): 483 def delete_measurement(measurement_id, settings):
509 action="store_true") 543 action="store_true")
510 parser.add_argument("--delete", help="Measurement ID to delete.") 544 parser.add_argument("--delete", help="Measurement ID to delete.")
511 parser.add_argument("--rerun-all", help="Measurement ID to rerun.") 545 parser.add_argument("--rerun-all", help="Measurement ID to rerun.")
512 parser.add_argument("--rerun-processing", help="Measurement ID to rerun processing routines.") 546 parser.add_argument("--rerun-processing", help="Measurement ID to rerun processing routines.")
513 547
548 # others files
549 parser.add_argument("--radiosounding", default=None, help="Radiosounding file name or path")
550
514 # Verbosity settings from http://stackoverflow.com/a/20663028 551 # Verbosity settings from http://stackoverflow.com/a/20663028
515 parser.add_argument('-d', '--debug', help="Print debugging information.", action="store_const", 552 parser.add_argument('-d', '--debug', help="Print debugging information.", action="store_const",
516 dest="loglevel", const=logging.DEBUG, default=logging.INFO, 553 dest="loglevel", const=logging.DEBUG, default=logging.INFO,
517 ) 554 )
518 parser.add_argument('-s', '--silent', help="Show only warning and error messages.", action="store_const", 555 parser.add_argument('-s', '--silent', help="Show only warning and error messages.", action="store_const",
537 else: 574 else:
538 if (args.filename == '') or (args.system == 0): 575 if (args.filename == '') or (args.system == 0):
539 parser.error('Provide a valid filename and system parameters.\nRun with -h for help.\n') 576 parser.error('Provide a valid filename and system parameters.\nRun with -h for help.\n')
540 577
541 if args.process: 578 if args.process:
542 process_file(args.filename, args.system, settings) 579 process_file(args.filename, args.system, settings, rs_filename=args.radiosounding)
543 else: 580 else:
544 upload_file(args.filename, args.system, settings) 581 upload_file(args.filename, args.system, settings, rs_filename=args.radiosounding)

mercurial