scc_access/scc_access.py

Mon, 08 Jan 2018 14:59:21 +0100

author
Moritz Wanzenböck <moritz.wanzenboeck@catalysts.cc>
date
Mon, 08 Jan 2018 14:59:21 +0100
changeset 29
3e3e5bda6b77
parent 14
c2020b2fdd05
child 30
4669876326d4
permissions
-rw-r--r--

Refactor argument parsing + New commands

* Restructured argument parsing to use subcommands.
* Move some common options to requests session (auth + verify)
* Allow for a common location for settings file (~/.scc_access.yaml)
* Update Readme
* Add option to list available files
* Add option to download existing files

victor@7 1 import requests
moritz@29 2
victor@7 3 requests.packages.urllib3.disable_warnings()
victor@7 4
victor@7 5 import urlparse
victor@7 6 import argparse
victor@7 7 import os
victor@7 8 import re
victor@7 9 import time
victor@7 10 import StringIO
victor@7 11 from zipfile import ZipFile
victor@7 12 import datetime
victor@7 13 import logging
i@14 14 import yaml
victor@7 15
i@14 16 logger = logging.getLogger(__name__)
victor@7 17
victor@7 18 # The regex to find the measurement id from the measurement page
victor@7 19 # This should be read from the uploaded file, but would require an extra NetCDF module.
victor@7 20 regex = "<h3>Measurement (?P<measurement_id>.{12}) <small>"
victor@7 21
victor@7 22
victor@7 23 class SCC:
victor@7 24 """ A simple class that will attempt to upload a file on the SCC server.
i@14 25
victor@7 26 The uploading is done by simulating a normal browser session. In the current
victor@7 27 version no check is performed, and no feedback is given if the upload
victor@7 28 was successful. If everything is setup correctly, it will work.
victor@7 29 """
victor@7 30
i@14 31 def __init__(self, auth, output_dir, base_url):
moritz@29 32
victor@7 33 self.auth = auth
victor@7 34 self.output_dir = output_dir
i@14 35 self.base_url = base_url
victor@7 36 self.session = requests.Session()
moritz@29 37 self.session.auth = auth
moritz@29 38 self.session.verify = False
i@14 39 self.login_url = urlparse.urljoin(self.base_url, 'accounts/login/')
i@14 40 self.upload_url = urlparse.urljoin(self.base_url, 'data_processing/measurements/quick/')
moritz@29 41 self.download_preprocessed_pattern = urlparse.urljoin(self.base_url,
moritz@29 42 'data_processing/measurements/{0}/download-preprocessed/')
moritz@29 43 self.download_optical_pattern = urlparse.urljoin(self.base_url,
moritz@29 44 'data_processing/measurements/{0}/download-optical/')
moritz@29 45 self.download_graph_pattern = urlparse.urljoin(self.base_url,
moritz@29 46 'data_processing/measurements/{0}/download-plots/')
i@14 47 self.delete_measurement_pattern = urlparse.urljoin(self.base_url, 'admin/database/measurements/{0}/delete/')
i@14 48 self.api_base_url = urlparse.urljoin(self.base_url, 'api/v1/')
moritz@29 49 self.list_measurements_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/')
i@14 50
i@14 51 def login(self, credentials):
victor@7 52 """ Login the the website. """
victor@7 53 logger.debug("Attempting to login to SCC, username %s." % credentials[0])
moritz@29 54 login_credentials = {'username': credentials[0],
moritz@29 55 'password': credentials[1]}
victor@7 56
i@14 57 logger.debug("Accessing login page at %s." % self.login_url)
victor@7 58
victor@7 59 # Get upload form
moritz@29 60 login_page = self.session.get(self.login_url)
victor@7 61
victor@7 62 logger.debug("Submiting credentials.")
victor@7 63 # Submit the login data
i@14 64 login_submit = self.session.post(self.login_url,
moritz@29 65 data=login_credentials,
victor@7 66 headers={'X-CSRFToken': login_page.cookies['csrftoken'],
moritz@29 67 'referer': self.login_url})
victor@7 68 return login_submit
victor@7 69
victor@7 70 def logout(self):
victor@7 71 pass
victor@7 72
victor@7 73 def upload_file(self, filename, system_id):
victor@7 74 """ Upload a filename for processing with a specific system. If the
victor@7 75 upload is successful, it returns the measurement id. """
victor@7 76 # Get submit page
moritz@29 77 upload_page = self.session.get(self.upload_url)
victor@7 78
victor@7 79 # Submit the data
victor@7 80 upload_data = {'system': system_id}
victor@7 81 files = {'data': open(filename, 'rb')}
victor@7 82
i@14 83 logger.info("Uploading of file %s started." % filename)
victor@7 84
i@14 85 upload_submit = self.session.post(self.upload_url,
victor@7 86 data=upload_data,
victor@7 87 files=files,
victor@7 88 headers={'X-CSRFToken': upload_page.cookies['csrftoken'],
moritz@29 89 'referer': self.upload_url})
victor@7 90
victor@7 91 if upload_submit.status_code != 200:
i@14 92 logger.warning("Connection error. Status code: %s" % upload_submit.status_code)
victor@7 93 return False
victor@7 94
victor@7 95 # Check if there was a redirect to a new page.
i@14 96 if upload_submit.url == self.upload_url:
victor@7 97 measurement_id = False
i@14 98 logger.error("Uploaded file rejected! Try to upload manually to see the error.")
victor@7 99 else:
victor@7 100 measurement_id = re.findall(regex, upload_submit.text)[0]
i@14 101 logger.error("Successfully uploaded measurement with id %s." % measurement_id)
victor@7 102
victor@7 103 return measurement_id
victor@7 104
victor@7 105 def download_files(self, measurement_id, subdir, download_url):
victor@7 106 """ Downloads some files from the download_url to the specified
victor@7 107 subdir. This method is used to download preprocessed file, optical
victor@7 108 files etc.
victor@7 109 """
victor@7 110 # Get the file
moritz@29 111 request = self.session.get(download_url, stream=True)
moritz@29 112
moritz@29 113 if not request.ok:
moritz@29 114 raise Exception("Could not download files for measurement '%s'" % measurement_id)
victor@7 115
victor@7 116 # Create the dir if it does not exist
victor@7 117 local_dir = os.path.join(self.output_dir, measurement_id, subdir)
victor@7 118 if not os.path.exists(local_dir):
victor@7 119 os.makedirs(local_dir)
victor@7 120
victor@7 121 # Save the file by chunk, needed if the file is big.
victor@7 122 memory_file = StringIO.StringIO()
victor@7 123
victor@7 124 for chunk in request.iter_content(chunk_size=1024):
victor@7 125 if chunk: # filter out keep-alive new chunks
victor@7 126 memory_file.write(chunk)
victor@7 127 memory_file.flush()
victor@7 128
victor@7 129 zip_file = ZipFile(memory_file)
victor@7 130
victor@7 131 for ziped_name in zip_file.namelist():
victor@7 132 basename = os.path.basename(ziped_name)
victor@7 133
victor@7 134 local_file = os.path.join(local_dir, basename)
victor@7 135
victor@7 136 with open(local_file, 'wb') as f:
victor@7 137 f.write(zip_file.read(ziped_name))
victor@7 138
victor@7 139 def download_preprocessed(self, measurement_id):
victor@7 140 """ Download preprocessed files for the measurement id. """
victor@7 141 # Construct the download url
i@14 142 download_url = self.download_preprocessed_pattern.format(measurement_id)
victor@7 143 self.download_files(measurement_id, 'scc_preprocessed', download_url)
victor@7 144
victor@7 145 def download_optical(self, measurement_id):
victor@7 146 """ Download optical files for the measurement id. """
victor@7 147 # Construct the download url
moritz@29 148 download_url = self.download_optical_pattern.format(measurement_id)
victor@7 149 self.download_files(measurement_id, 'scc_optical', download_url)
victor@7 150
victor@7 151 def download_graphs(self, measurement_id):
victor@7 152 """ Download profile graphs for the measurement id. """
victor@7 153 # Construct the download url
i@14 154 download_url = self.download_graph_pattern.format(measurement_id)
victor@7 155 self.download_files(measurement_id, 'scc_plots', download_url)
victor@7 156
victor@7 157 def rerun_processing(self, measurement_id, monitor=True):
victor@7 158 measurement = self.get_measurement(measurement_id)
victor@7 159
victor@7 160 if measurement:
moritz@29 161 request = self.session.get(measurement.rerun_processing_url, stream=True)
victor@7 162
victor@7 163 if request.status_code != 200:
i@14 164 logger.error(
i@14 165 "Could not rerun processing for %s. Status code: %s" % (measurement_id, request.status_code))
victor@7 166 return
victor@7 167
victor@7 168 if monitor:
victor@7 169 self.monitor_processing(measurement_id)
victor@7 170
victor@7 171 def rerun_all(self, measurement_id, monitor=True):
victor@7 172 logger.debug("Started rerun_all procedure.")
victor@7 173
victor@7 174 logger.debug("Getting measurement %s" % measurement_id)
victor@7 175 measurement = self.get_measurement(measurement_id)
victor@7 176
victor@7 177 if measurement:
victor@7 178 logger.debug("Attempting to rerun all processing through %s." % measurement.rerun_all_url)
victor@7 179
moritz@29 180 request = self.session.get(measurement.rerun_all_url, stream=True)
victor@7 181
victor@7 182 if request.status_code != 200:
victor@7 183 logger.error("Could not rerun pre processing for %s. Status code: %s" %
victor@7 184 (measurement_id, request.status_code))
victor@7 185 return
victor@7 186
victor@7 187 if monitor:
victor@7 188 self.monitor_processing(measurement_id)
victor@7 189
moritz@29 190 def process(self, filename, system_id, monitor):
victor@7 191 """ Upload a file for processing and wait for the processing to finish.
victor@7 192 If the processing is successful, it will download all produced files.
victor@7 193 """
victor@7 194 logger.info("--- Processing started on %s. ---" % datetime.datetime.now())
victor@7 195 # Upload file
victor@7 196 measurement_id = self.upload_file(filename, system_id)
victor@7 197
moritz@29 198 if monitor:
moritz@29 199 return self.monitor_processing(measurement_id)
moritz@29 200 return None
victor@7 201
victor@7 202 def monitor_processing(self, measurement_id):
victor@7 203 """ Monitor the processing progress of a measurement id"""
victor@7 204
victor@7 205 measurement = self.get_measurement(measurement_id)
victor@7 206 if measurement is not None:
victor@7 207 while measurement.is_running:
victor@7 208 logger.info("Measurement is being processed (status: %s, %s, %s). Please wait." % (measurement.upload,
i@14 209 measurement.pre_processing,
i@14 210 measurement.processing))
victor@7 211 time.sleep(10)
victor@7 212 measurement = self.get_measurement(measurement_id)
victor@7 213 logger.info("Measurement processing finished (status: %s, %s, %s)." % (measurement.upload,
i@14 214 measurement.pre_processing,
i@14 215 measurement.processing))
victor@7 216 if measurement.pre_processing == 127:
victor@7 217 logger.info("Downloading preprocessed files.")
victor@7 218 self.download_preprocessed(measurement_id)
victor@7 219 if measurement.processing == 127:
victor@7 220 logger.info("Downloading optical files.")
victor@7 221 self.download_optical(measurement_id)
victor@7 222 logger.info("Downloading graphs.")
victor@7 223 self.download_graphs(measurement_id)
victor@7 224 logger.info("--- Processing finished. ---")
victor@7 225 return measurement
victor@7 226
victor@7 227 def get_status(self, measurement_id):
victor@7 228 """ Get the processing status for a measurement id through the API. """
i@14 229 measurement_url = urlparse.urljoin(self.api_base_url, 'measurements/?id__exact=%s' % measurement_id)
victor@7 230
moritz@29 231 response = self.session.get(measurement_url)
victor@7 232
victor@7 233 response_dict = response.json()
victor@7 234
victor@7 235 if response_dict['objects']:
victor@7 236 measurement_list = response_dict['objects']
i@14 237 measurement = Measurement(self.base_url, measurement_list[0])
i@14 238 return measurement.upload, measurement.pre_processing, measurement.processing
victor@7 239 else:
victor@7 240 logger.error("No measurement with id %s found on the SCC." % measurement_id)
victor@7 241 return None
victor@7 242
victor@7 243 def get_measurement(self, measurement_id):
i@14 244 measurement_url = urlparse.urljoin(self.api_base_url, 'measurements/%s/' % measurement_id)
victor@7 245
moritz@29 246 response = self.session.get(measurement_url)
victor@7 247
moritz@29 248 if not response.ok:
i@14 249 logger.error('Could not access API. Status code %s.' % response.status_code)
moritz@29 250 return None
i@14 251
victor@7 252 response_dict = response.json()
victor@7 253
victor@7 254 if response_dict:
moritz@29 255 measurement = Measurement(self.base_url, response_dict)
victor@7 256 return measurement
victor@7 257 else:
victor@7 258 logger.error("No measurement with id %s found on the SCC." % measurement_id)
victor@7 259 return None
victor@7 260
victor@7 261 def delete_measurement(self, measurement_id):
victor@7 262 """ Deletes a measurement with the provided measurement id. The user
victor@7 263 should have the appropriate permissions.
victor@7 264
victor@7 265 The procedures is performed directly through the web interface and
victor@7 266 NOT through the API.
victor@7 267 """
victor@7 268 # Get the measurement object
victor@7 269 measurement = self.get_measurement(measurement_id)
victor@7 270
victor@7 271 # Check that it exists
victor@7 272 if measurement is None:
victor@7 273 logger.warning("Nothing to delete.")
victor@7 274 return None
victor@7 275
victor@7 276 # Go the the page confirming the deletion
moritz@29 277 delete_url = self.delete_measurement_pattern.format(measurement_id)
victor@7 278
moritz@29 279 confirm_page = self.session.get(delete_url)
victor@7 280
victor@7 281 # Check that the page opened properly
victor@7 282 if confirm_page.status_code != 200:
victor@7 283 logger.warning("Could not open delete page. Status: {0}".format(confirm_page.status_code))
victor@7 284 return None
victor@7 285
victor@7 286 # Delete the measurement
victor@7 287 delete_page = self.session.post(delete_url,
victor@7 288 data={'post': 'yes'},
victor@7 289 headers={'X-CSRFToken': confirm_page.cookies['csrftoken'],
victor@7 290 'referer': delete_url}
victor@7 291 )
victor@7 292 if delete_page.status_code != 200:
victor@7 293 logger.warning("Something went wrong. Delete page status: {0}".format(
victor@7 294 delete_page.status_code))
victor@7 295 return None
victor@7 296
victor@7 297 logger.info("Deleted measurement {0}".format(measurement_id))
victor@7 298 return True
victor@7 299
victor@7 300 def available_measurements(self):
victor@7 301 """ Get a list of available measurement on the SCC. """
i@14 302 measurement_url = urlparse.urljoin(self.api_base_url, 'measurements')
moritz@29 303 response = self.session.get(measurement_url)
victor@7 304 response_dict = response.json()
victor@7 305
victor@7 306 measurements = None
victor@7 307 if response_dict:
victor@7 308 measurement_list = response_dict['objects']
i@14 309 measurements = [Measurement(self.base_url, measurement_dict) for measurement_dict in measurement_list]
victor@7 310 logger.info("Found %s measurements on the SCC." % len(measurements))
victor@7 311 else:
victor@7 312 logger.warning("No response received from the SCC when asked for available measurements.")
victor@7 313
victor@7 314 return measurements
victor@7 315
moritz@29 316 def list_measurements(self, station=None, system=None, start=None, stop=None, upload_status=None,
moritz@29 317 processing_status=None, optical_processing=None):
moritz@29 318
moritz@29 319 # Need to set to empty string if not specified, we won't get any results
moritz@29 320 params = {
moritz@29 321 "station": station if station is not None else "",
moritz@29 322 "system": system if system is not None else "",
moritz@29 323 "stop": stop if stop is not None else "",
moritz@29 324 "start": start if start is not None else "",
moritz@29 325 "upload_status": upload_status if upload_status is not None else "",
moritz@29 326 "preprocessing_status": processing_status if processing_status is not None else "",
moritz@29 327 "optical_processing_status": optical_processing if optical_processing is not None else ""
moritz@29 328 }
moritz@29 329 resp = self.session.get(self.list_measurements_pattern, params=params).text
moritz@29 330 tbl_rgx = re.compile(r'<table id="measurements">(.*?)</table>', re.DOTALL)
moritz@29 331 entry_rgx = re.compile(r'<tr>(.*?)</tr>', re.DOTALL)
moritz@29 332 measurement_rgx = re.compile(
moritz@29 333 r'.*?<td><a[^>]*>(\w+)</a>.*?<td>.*?<td>([\w-]+ [\w:]+)</td>.*<td data-order="([-]?\d+),([-]?\d+),([-]?\d+)".*',
moritz@29 334 re.DOTALL)
moritz@29 335 matches = tbl_rgx.findall(resp)
moritz@29 336 if len(matches) != 1:
moritz@29 337 return []
moritz@29 338
moritz@29 339 ret = []
moritz@29 340 for entry in entry_rgx.finditer(matches[0]):
moritz@29 341 m = measurement_rgx.match(entry.string[entry.start(0):entry.end(0)])
moritz@29 342 if m:
moritz@29 343 name, date, upload, preproc, optical = m.groups()
moritz@29 344 ret.append(
moritz@29 345 Measurement(self.base_url, {"id": name, "upload": int(upload), "pre_processing": int(preproc),
moritz@29 346 "processing": int(optical)}))
moritz@29 347
moritz@29 348 return ret
moritz@29 349
victor@7 350 def measurement_id_for_date(self, t1, call_sign='bu', base_number=0):
victor@7 351 """ Give the first available measurement id on the SCC for the specific
victor@7 352 date.
victor@7 353 """
victor@7 354 date_str = t1.strftime('%Y%m%d')
i@14 355 search_url = urlparse.urljoin(self.api_base_url, 'measurements/?id__startswith=%s' % date_str)
victor@7 356
moritz@29 357 response = self.session.get(search_url)
victor@7 358
victor@7 359 response_dict = response.json()
victor@7 360
victor@7 361 measurement_id = None
victor@7 362
victor@7 363 if response_dict:
victor@7 364 measurement_list = response_dict['objects']
victor@7 365 existing_ids = [measurement_dict['id'] for measurement_dict in measurement_list]
victor@7 366
victor@7 367 measurement_number = base_number
victor@7 368 measurement_id = "%s%s%02i" % (date_str, call_sign, measurement_number)
victor@7 369
victor@7 370 while measurement_id in existing_ids:
victor@7 371 measurement_number = measurement_number + 1
victor@7 372 measurement_id = "%s%s%02i" % (date_str, call_sign, measurement_number)
victor@7 373 if measurement_number == 100:
victor@7 374 raise ValueError('No available measurement id found.')
victor@7 375
victor@7 376 return measurement_id
victor@7 377
victor@7 378
victor@7 379 class ApiObject:
victor@7 380 """ A generic class object. """
victor@7 381
i@14 382 def __init__(self, base_url, dict_response):
i@14 383 self.base_url = base_url
victor@7 384
victor@7 385 if dict_response:
victor@7 386 # Add the dictionary key value pairs as object properties
victor@7 387 for key, value in dict_response.items():
victor@7 388 setattr(self, key, value)
victor@7 389 self.exists = True
victor@7 390 else:
victor@7 391 self.exists = False
victor@7 392
victor@7 393
victor@7 394 class Measurement(ApiObject):
victor@7 395 """ This class represents the measurement object as returned in the SCC API.
victor@7 396 """
victor@7 397
victor@7 398 @property
victor@7 399 def is_running(self):
victor@7 400 """ Returns True if the processing has not finished.
victor@7 401 """
victor@7 402 if self.upload == 0:
victor@7 403 return False
victor@7 404 if self.pre_processing == -127:
victor@7 405 return False
victor@7 406 if self.pre_processing == 127:
victor@7 407 if self.processing in [127, -127]:
victor@7 408 return False
victor@7 409 return True
victor@7 410
victor@7 411 @property
victor@7 412 def rerun_processing_url(self):
i@14 413 url_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/rerun-optical/')
i@14 414 return url_pattern.format(self.id)
victor@7 415
victor@7 416 @property
victor@7 417 def rerun_all_url(self):
i@14 418 ulr_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/rerun-all/')
i@14 419 return ulr_pattern.format(self.id)
victor@7 420
victor@7 421 def __str__(self):
victor@7 422 return "%s: %s, %s, %s" % (self.id,
victor@7 423 self.upload,
victor@7 424 self.pre_processing,
victor@7 425 self.processing)
victor@7 426
victor@7 427
i@14 428 def upload_file(filename, system_id, settings):
victor@7 429 """ Shortcut function to upload a file to the SCC. """
victor@7 430 logger.info("Uploading file %s, using sytem %s" % (filename, system_id))
victor@7 431
i@14 432 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url'])
i@14 433 scc.login(settings['website_credentials'])
victor@7 434 measurement_id = scc.upload_file(filename, system_id)
victor@7 435 scc.logout()
victor@7 436 return measurement_id
victor@7 437
victor@7 438
moritz@29 439 def process_file(filename, system_id, monitor, settings):
victor@7 440 """ Shortcut function to process a file to the SCC. """
victor@7 441 logger.info("Processing file %s, using sytem %s" % (filename, system_id))
i@14 442 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url'])
i@14 443 scc.login(settings['website_credentials'])
moritz@29 444 measurement = scc.process(filename, system_id, monitor)
victor@7 445 scc.logout()
victor@7 446 return measurement
victor@7 447
victor@7 448
moritz@29 449 def delete_measurement(measurement_ids, settings):
moritz@29 450 """ Shortcut function to delete measurements from the SCC. """
moritz@29 451 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url'])
moritz@29 452 scc.login(settings['website_credentials'])
moritz@29 453 for m_id in measurement_ids:
moritz@29 454 logger.info("Deleting %s" % m_id)
moritz@29 455 scc.delete_measurement(m_id)
moritz@29 456 scc.logout()
moritz@29 457
moritz@29 458
moritz@29 459 def rerun_all(measurement_ids, monitor, settings):
moritz@29 460 """ Shortcut function to rerun measurements from the SCC. """
i@14 461
i@14 462 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url'])
i@14 463 scc.login(settings['website_credentials'])
moritz@29 464 for m_id in measurement_ids:
moritz@29 465 logger.info("Rerunning all products for %s" % m_id)
moritz@29 466 scc.rerun_all(m_id, monitor)
victor@7 467 scc.logout()
victor@7 468
victor@7 469
moritz@29 470 def rerun_processing(measurement_ids, monitor, settings):
victor@7 471 """ Shortcut function to delete a measurement from the SCC. """
i@14 472
i@14 473 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url'])
i@14 474 scc.login(settings['website_credentials'])
moritz@29 475 for m_id in measurement_ids:
moritz@29 476 logger.info("Rerunning (optical) processing for %s" % m_id)
moritz@29 477 scc.rerun_processing(m_id, monitor)
moritz@29 478 scc.logout()
moritz@29 479
moritz@29 480
moritz@29 481 def list_measurements(settings, station=None, system=None, start=None, stop=None, upload_status=None,
moritz@29 482 preprocessing_status=None,
moritz@29 483 optical_processing=None):
moritz@29 484 """List all available measurements"""
moritz@29 485 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url'])
moritz@29 486 scc.login(settings['website_credentials'])
moritz@29 487 ret = scc.list_measurements(station=station, system=system, start=start, stop=stop, upload_status=upload_status,
moritz@29 488 processing_status=preprocessing_status, optical_processing=optical_processing)
moritz@29 489 for entry in ret:
moritz@29 490 print("%s" % entry.id)
victor@7 491 scc.logout()
victor@7 492
victor@7 493
moritz@29 494 def download_measurements(measurement_ids, download_preproc, download_optical, download_graph, settings):
moritz@29 495 """Download all measurements for the specified IDs"""
i@14 496 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url'])
i@14 497 scc.login(settings['website_credentials'])
moritz@29 498 for m_id in measurement_ids:
moritz@29 499 if download_preproc:
moritz@29 500 logger.info("Downloading preprocessed files for '%s'" % m_id)
moritz@29 501 scc.download_preprocessed(m_id)
moritz@29 502 logger.info("Complete")
moritz@29 503 if download_optical:
moritz@29 504 logger.info("Downloading optical files for '%s'" % m_id)
moritz@29 505 scc.download_optical(m_id)
moritz@29 506 logger.info("Complete")
moritz@29 507 if download_graph:
moritz@29 508 logger.info("Downloading profile graph files for '%s'" % m_id)
moritz@29 509 scc.download_graphs(m_id)
moritz@29 510 logger.info("Complete")
i@14 511
i@14 512
moritz@29 513 def settings_from_path(config_file_path):
i@14 514 """ Read the configuration file.
i@14 515
i@14 516 The file should be in YAML syntax."""
i@14 517
i@14 518 if not os.path.isfile(config_file_path):
moritz@29 519 raise argparse.ArgumentTypeError("Wrong path for configuration file (%s)" % config_file_path)
i@14 520
i@14 521 with open(config_file_path) as yaml_file:
i@14 522 try:
i@14 523 settings = yaml.safe_load(yaml_file)
i@14 524 logger.debug("Read settings file(%s)" % config_file_path)
moritz@29 525 except Exception:
moritz@29 526 raise argparse.ArgumentTypeError("Could not parse YAML file (%s)" % config_file_path)
i@14 527
i@14 528 # YAML limitation: does not read tuples
i@14 529 settings['basic_credentials'] = tuple(settings['basic_credentials'])
i@14 530 settings['website_credentials'] = tuple(settings['website_credentials'])
i@14 531 return settings
i@14 532
i@14 533
moritz@29 534 # Setup for command specific parsers
moritz@29 535 def setup_delete(parser):
moritz@29 536 def delete_from_args(parsed):
moritz@29 537 delete_measurement(parsed.IDs, parsed.config)
moritz@29 538
moritz@29 539 parser.add_argument("IDs", nargs="+", help="measurement IDs to delete.")
moritz@29 540 parser.set_defaults(execute=delete_from_args)
moritz@29 541
moritz@29 542
moritz@29 543 def setup_rerun_all(parser):
moritz@29 544 def rerun_all_from_args(parsed):
moritz@29 545 rerun_all(parsed.IDs, parsed.process, parsed.config)
moritz@29 546
moritz@29 547 parser.add_argument("IDs", nargs="+", help="Measurement IDs to rerun.")
moritz@29 548 parser.add_argument("-p", "--process", help="Wait for the results of the processing.",
moritz@29 549 action="store_true")
moritz@29 550 parser.set_defaults(execute=rerun_all_from_args)
moritz@29 551
moritz@29 552
moritz@29 553 def setup_rerun_processing(parser):
moritz@29 554 def rerun_processing_from_args(parsed):
moritz@29 555 rerun_processing(parsed.IDs, parsed.process, parsed.config)
moritz@29 556
moritz@29 557 parser.add_argument("IDs", nargs="+", help="Measurement IDs to rerun the processing on.")
moritz@29 558 parser.add_argument("-p", "--process", help="Wait for the results of the processing.",
moritz@29 559 action="store_true")
moritz@29 560 parser.set_defaults(execute=rerun_processing_from_args)
moritz@29 561
moritz@29 562
moritz@29 563 def setup_process_file(parser):
moritz@29 564 def process_file_from_args(parsed):
moritz@29 565 process_file(parsed.file, parsed.system, parsed.process, parsed.config)
moritz@29 566
moritz@29 567 parser.add_argument("filename", help="Measurement file name or path.")
moritz@29 568 parser.add_argument("system", help="Processing system id.")
moritz@29 569 parser.add_argument("-p", "--process", help="Wait for the results of the processing.",
moritz@29 570 action="store_true")
moritz@29 571 parser.set_defaults(execute=process_file_from_args)
moritz@29 572
moritz@29 573
moritz@29 574 def setup_upload_file(parser):
moritz@29 575 def upload_file_from_args(parsed):
moritz@29 576 upload_file(parsed.file, parsed.system, parsed.config)
moritz@29 577
moritz@29 578 parser.add_argument("filename", help="Measurement file name or path.")
moritz@29 579 parser.add_argument("system", help="Processing system id.")
moritz@29 580 parser.set_defaults(execute=upload_file_from_args)
moritz@29 581
moritz@29 582
moritz@29 583 def setup_list_measurements(parser):
moritz@29 584 def list_measurements_from_args(parsed):
moritz@29 585 list_measurements(parsed.config, station=parsed.station, system=parsed.system, start=parsed.start,
moritz@29 586 stop=parsed.stop,
moritz@29 587 upload_status=parsed.upload_status, preprocessing_status=parsed.preprocessing_status,
moritz@29 588 optical_processing=parsed.optical_processing_status)
moritz@29 589
moritz@29 590 def status(arg):
moritz@29 591 if -127 <= int(arg) <= 127:
moritz@29 592 return arg
moritz@29 593 else:
moritz@29 594 raise argparse.ArgumentTypeError("Status must be between -127 and 127")
moritz@29 595
moritz@29 596 def date(arg):
moritz@29 597 if re.match(r'\d{4}-\d{2}-\d{2}', arg):
moritz@29 598 return arg
moritz@29 599 else:
moritz@29 600 raise argparse.ArgumentTypeError("Date must be in format 'YYYY-MM-DD'")
moritz@29 601
moritz@29 602 parser.add_argument("--station", help="Filter for only the selected station")
moritz@29 603 parser.add_argument("--system", help="Filter for only the selected station")
moritz@29 604 parser.add_argument("--start", help="Filter for only the selected station", type=date)
moritz@29 605 parser.add_argument("--stop", help="Filter for only the selected station", type=date)
moritz@29 606 parser.add_argument("--upload-status", help="Filter for only the selected station", type=status)
moritz@29 607 parser.add_argument("--preprocessing-status", help="Filter for only the selected station", type=status)
moritz@29 608 parser.add_argument("--optical-processing-status", help="Filter for only the selected station", type=status)
moritz@29 609 parser.set_defaults(execute=list_measurements_from_args)
moritz@29 610
moritz@29 611
moritz@29 612 def setup_download_measurements(parser):
moritz@29 613 def download_measurements_from_args(parsed):
moritz@29 614 preproc = parsed.download_preprocessed
moritz@29 615 optical = parsed.download_optical
moritz@29 616 graphs = parsed.download_profile_graphs
moritz@29 617 if not preproc and not graphs:
moritz@29 618 optical = True
moritz@29 619 download_measurements(parsed.IDs, preproc, optical, graphs, parsed.config)
moritz@29 620
moritz@29 621 parser.add_argument("IDs", help="Measurement IDs that should be downloaded.", nargs="+")
moritz@29 622 parser.add_argument("--download-preprocessed", action="store_true", help="Download preprocessed files.")
moritz@29 623 parser.add_argument("--download-optical", action="store_true",
moritz@29 624 help="Download optical files (default if no other download is used).")
moritz@29 625 parser.add_argument("--download-profile-graphs", action="store_true", help="Download profile graph files.")
moritz@29 626 parser.set_defaults(execute=download_measurements_from_args)
moritz@29 627
moritz@29 628
victor@7 629 def main():
victor@7 630 # Define the command line arguments.
victor@7 631 parser = argparse.ArgumentParser()
moritz@29 632 subparsers = parser.add_subparsers()
moritz@29 633
moritz@29 634 delete_parser = subparsers.add_parser("delete", help="Deletes a measurement.")
moritz@29 635 rerun_all_parser = subparsers.add_parser("rerun-all", help="Rerun a measurement.")
moritz@29 636 rerun_processing_parser = subparsers.add_parser("rerun-processing",
moritz@29 637 help="Rerun processing routings for a measurement.")
moritz@29 638 process_file_parser = subparsers.add_parser("process-file", help="Process a file.")
moritz@29 639 upload_file_parser = subparsers.add_parser("upload-file", help="Upload a file.")
moritz@29 640 list_parser = subparsers.add_parser("list", help="List all measurements.")
moritz@29 641 download_parser = subparsers.add_parser("download", help="Download selected measurements.")
moritz@29 642
moritz@29 643 setup_delete(delete_parser)
moritz@29 644 setup_rerun_all(rerun_all_parser)
moritz@29 645 setup_rerun_processing(rerun_processing_parser)
moritz@29 646 setup_process_file(process_file_parser)
moritz@29 647 setup_upload_file(upload_file_parser)
moritz@29 648 setup_list_measurements(list_parser)
moritz@29 649 setup_download_measurements(download_parser)
victor@7 650
victor@7 651 # Verbosity settings from http://stackoverflow.com/a/20663028
victor@7 652 parser.add_argument('-d', '--debug', help="Print debugging information.", action="store_const",
victor@7 653 dest="loglevel", const=logging.DEBUG, default=logging.INFO,
victor@7 654 )
victor@7 655 parser.add_argument('-s', '--silent', help="Show only warning and error messages.", action="store_const",
victor@7 656 dest="loglevel", const=logging.WARNING
victor@7 657 )
victor@7 658
moritz@29 659 home = os.path.expanduser("~")
moritz@29 660 default_config_location = os.path.abspath(os.path.join(home, ".scc_access.yaml"))
moritz@29 661 parser.add_argument("-c", "--config", help="Path to the config file.", type=settings_from_path,
moritz@29 662 default=default_config_location)
moritz@29 663
victor@7 664 args = parser.parse_args()
victor@7 665
victor@7 666 # Get the logger with the appropriate level
victor@7 667 logging.basicConfig(format='%(levelname)s: %(message)s', level=args.loglevel)
victor@7 668
moritz@29 669 # Dispatch to appropriate function
moritz@29 670 args.execute(args)
moritz@29 671
i@14 672
moritz@29 673 # When running through terminal
moritz@29 674 if __name__ == '__main__':
moritz@29 675 main()

mercurial