# HG changeset patch # User Iannis # Date 1539260755 -10800 # Node ID 4669876326d4f3fa7f4a4cf9b9a20389d33c48a9 # Parent 2d90204710af93d356605266c097349ec8ac448f# Parent 3e3e5bda6b7755fb89e9d6add5dc2ea2796663d1 Merge from Moritz branch. Clean-up is still needed. diff -r 3e3e5bda6b77 -r 4669876326d4 .hgignore --- a/.hgignore Mon Jan 08 14:59:21 2018 +0100 +++ b/.hgignore Thu Oct 11 15:25:55 2018 +0300 @@ -1,6 +1,8 @@ -syntax: glob -*.rst~ -*.py~ -*.pyc -re:^settings\.py$ -re:^\.idea/ +syntax: glob +*.rst~ +*.py~ +*.pyc +re:^settings\.py$ +re:^\.idea/ +scc_access.egg-info/ +re:^\.pytest_cache/ \ No newline at end of file diff -r 3e3e5bda6b77 -r 4669876326d4 CHANGELOG.rst --- a/CHANGELOG.rst Mon Jan 08 14:59:21 2018 +0100 +++ b/CHANGELOG.rst Thu Oct 11 15:25:55 2018 +0300 @@ -1,5 +1,18 @@ -0.5.0 (2015-06-23) -------------------- +Changelog +========= + +0.6.2 - 2018-01-10 +------------------ +* Fixed bug when download optical files. +* Changes config file path to positional argument. + +0.6.1 - 2017-12-15 +------------------ +* Converted script to python module +* Settings are now read from .yaml file. + +0.5.0 - 2015-06-23 +------------------ * Moved configuration settings to a separate file * Added lincence information (MIT) * Added version number diff -r 3e3e5bda6b77 -r 4669876326d4 README.rst --- a/README.rst Mon Jan 08 14:59:21 2018 +0100 +++ b/README.rst Thu Oct 11 15:25:55 2018 +0300 @@ -1,7 +1,7 @@ Overview -================= +======== -This package provides a script which permits interacting with the +This package provides a tool for interacting with the Single Calculus Chain through the command line. Specifically, with the script you can: * Upload a file to the SCC for processing @@ -25,13 +25,9 @@ The easiest way to install this module is from the python package index using pip:: - pip install scc_access - -See http://docs.python-requests.org/en/latest/user/install/ for more details. + pip install hg+https://bitbucket.org/iannis_b/scc-access#egg=scc-access -You can also use the script by cloning this mercurial repository. Alternatively, you -can just copy the scc_access.py and and settings.sample.py files to a local -directory. +You can also use the script by cloning this mercurial repository. Settings @@ -47,7 +43,7 @@ 1. Change the `basic_credentials` and `website_credentials` to your credentials. 2. Change the `output_dir` to the location were the results will be stored. -Please not that it's not a good idea to store your own credentials in the settings +Please not that it's not a good idea to store your stations management credentials in the settings file. The standard user has "Station Management" privileges and if the credentials are stolen, someone could change/delete the stations settings from the SCC database. For this, it is better to use a used account with minimum access settings, that diff -r 3e3e5bda6b77 -r 4669876326d4 requirements.txt --- a/requirements.txt Mon Jan 08 14:59:21 2018 +0100 +++ b/requirements.txt Thu Oct 11 15:25:55 2018 +0300 @@ -1,1 +1,1 @@ -requests +. \ No newline at end of file diff -r 3e3e5bda6b77 -r 4669876326d4 scc_access/__init__.py --- a/scc_access/__init__.py Mon Jan 08 14:59:21 2018 +0100 +++ b/scc_access/__init__.py Thu Oct 11 15:25:55 2018 +0300 @@ -1,1 +1,1 @@ -__version__ = "0.6.1" \ No newline at end of file +__version__ = "0.6.2" \ No newline at end of file diff -r 3e3e5bda6b77 -r 4669876326d4 scc_access/scc_access.py --- a/scc_access/scc_access.py Mon Jan 08 14:59:21 2018 +0100 +++ b/scc_access/scc_access.py Thu Oct 11 15:25:55 2018 +0300 @@ -1,18 +1,24 @@ import requests -requests.packages.urllib3.disable_warnings() +try: + import urllib.parse as urlparse # Python 3 +except ImportError: + import urlparse # Python 2 -import urlparse import argparse +import datetime +import logging import os import re +from io import StringIO +import sys import time -import StringIO +import urlparse from zipfile import ZipFile -import datetime -import logging + import yaml +requests.packages.urllib3.disable_warnings() logger = logging.getLogger(__name__) # The regex to find the measurement id from the measurement page @@ -21,11 +27,11 @@ class SCC: - """ A simple class that will attempt to upload a file on the SCC server. + """A simple class that will attempt to upload a file on the SCC server. The uploading is done by simulating a normal browser session. In the current - version no check is performed, and no feedback is given if the upload - was successful. If everything is setup correctly, it will work. + version no check is performed, and no feedback is given if the upload + was successful. If everything is setup correctly, it will work. """ def __init__(self, auth, output_dir, base_url): @@ -59,6 +65,11 @@ # Get upload form login_page = self.session.get(self.login_url) + # TODO: Do we need this? Mortiz removed it. + if login_page.status_code != 200: + logger.error('Could not access login pages. Status code %s' % login_page.status_code) + sys.exit(1) + logger.debug("Submiting credentials.") # Submit the login data login_submit = self.session.post(self.login_url, @@ -70,8 +81,8 @@ def logout(self): pass - def upload_file(self, filename, system_id): - """ Upload a filename for processing with a specific system. If the + def upload_file(self, filename, system_id, rs_filename=None): + """ Upload a filename for processing with a specific system. If the upload is successful, it returns the measurement id. """ # Get submit page upload_page = self.session.get(self.upload_url) @@ -80,6 +91,9 @@ upload_data = {'system': system_id} files = {'data': open(filename, 'rb')} + if rs_filename is not None: + files['sounding_file'] = open(rs_filename, 'rb') + logger.info("Uploading of file %s started." % filename) upload_submit = self.session.post(self.upload_url, @@ -98,13 +112,13 @@ logger.error("Uploaded file rejected! Try to upload manually to see the error.") else: measurement_id = re.findall(regex, upload_submit.text)[0] - logger.error("Successfully uploaded measurement with id %s." % measurement_id) + logger.info("Successfully uploaded measurement with id %s." % measurement_id) return measurement_id def download_files(self, measurement_id, subdir, download_url): """ Downloads some files from the download_url to the specified - subdir. This method is used to download preprocessed file, optical + subdir. This method is used to download preprocessed file, optical files etc. """ # Get the file @@ -119,7 +133,7 @@ os.makedirs(local_dir) # Save the file by chunk, needed if the file is big. - memory_file = StringIO.StringIO() + memory_file = StringIO() for chunk in request.iter_content(chunk_size=1024): if chunk: # filter out keep-alive new chunks @@ -155,7 +169,7 @@ self.download_files(measurement_id, 'scc_plots', download_url) def rerun_processing(self, measurement_id, monitor=True): - measurement = self.get_measurement(measurement_id) + measurement, status = self.get_measurement(measurement_id) if measurement: request = self.session.get(measurement.rerun_processing_url, stream=True) @@ -172,7 +186,7 @@ logger.debug("Started rerun_all procedure.") logger.debug("Getting measurement %s" % measurement_id) - measurement = self.get_measurement(measurement_id) + measurement, status = self.get_measurement(measurement_id) if measurement: logger.debug("Attempting to rerun all processing through %s." % measurement.rerun_all_url) @@ -187,14 +201,16 @@ if monitor: self.monitor_processing(measurement_id) - def process(self, filename, system_id, monitor): + def process(self, filename, system_id, monitor, rs_filename=None): """ Upload a file for processing and wait for the processing to finish. If the processing is successful, it will download all produced files. """ logger.info("--- Processing started on %s. ---" % datetime.datetime.now()) # Upload file - measurement_id = self.upload_file(filename, system_id) + logger.info("--- Uploading file") + measurement_id = self.upload_file(filename, system_id, rs_filename=rs_filename) + logger.info("--- Monitoring processing") if monitor: return self.monitor_processing(measurement_id) return None @@ -202,17 +218,36 @@ def monitor_processing(self, measurement_id): """ Monitor the processing progress of a measurement id""" - measurement = self.get_measurement(measurement_id) + # try to deal with error 404 + error_count = 0 + error_max = 6 + time_sleep = 10 + + # try to wait for measurement to appear in API + measurement = None + logger.info("looking for measurement %s in SCC", measurement_id) + while error_count < error_max: + time.sleep(time_sleep) + measurement, status = self.get_measurement(measurement_id) + if status != 200 and error_count < error_max: + logger.error("measurement not found. waiting %ds", time_sleep) + error_count += 1 + else: + break + + if error_count == error_max: + logger.critical("measurement %s doesn't seem to exist", measurement_id) + sys.exit(1) + + logger.info('measurement %s found', measurement_id) + if measurement is not None: while measurement.is_running: - logger.info("Measurement is being processed (status: %s, %s, %s). Please wait." % (measurement.upload, - measurement.pre_processing, - measurement.processing)) + logger.info("Measurement is being processed (status: %s, %s, %s). Please wait.", measurement.upload, measurement.pre_processing, measurement.processing) time.sleep(10) - measurement = self.get_measurement(measurement_id) - logger.info("Measurement processing finished (status: %s, %s, %s)." % (measurement.upload, - measurement.pre_processing, - measurement.processing)) + measurement, status = self.get_measurement(measurement_id) + + logger.info("Measurement processing finished (status: %s, %s, %s).",measurement.upload, measurement.pre_processing, measurement.processing) if measurement.pre_processing == 127: logger.info("Downloading preprocessed files.") self.download_preprocessed(measurement_id) @@ -247,26 +282,26 @@ if not response.ok: logger.error('Could not access API. Status code %s.' % response.status_code) - return None + return None, response.status_code response_dict = response.json() if response_dict: measurement = Measurement(self.base_url, response_dict) - return measurement + return measurement, response.status_code else: logger.error("No measurement with id %s found on the SCC." % measurement_id) - return None + return None, response.status_code def delete_measurement(self, measurement_id): """ Deletes a measurement with the provided measurement id. The user - should have the appropriate permissions. - + should have the appropriate permissions. + The procedures is performed directly through the web interface and NOT through the API. """ # Get the measurement object - measurement = self.get_measurement(measurement_id) + measurement, status = self.get_measurement(measurement_id) # Check that it exists if measurement is None: @@ -349,7 +384,7 @@ def measurement_id_for_date(self, t1, call_sign='bu', base_number=0): """ Give the first available measurement id on the SCC for the specific - date. + date. """ date_str = t1.strftime('%Y%m%d') search_url = urlparse.urljoin(self.api_base_url, 'measurements/?id__startswith=%s' % date_str) @@ -376,7 +411,7 @@ return measurement_id -class ApiObject: +class ApiObject(object): """ A generic class object. """ def __init__(self, base_url, dict_response): @@ -385,7 +420,11 @@ if dict_response: # Add the dictionary key value pairs as object properties for key, value in dict_response.items(): - setattr(self, key, value) + # logger.debug('Setting key {0} to value {1}'.format(key, value)) + try: + setattr(self, key, value) + except: + logger.warning('Could not set attribute {0} to value {1}'.format(key, value)) self.exists = True else: self.exists = False @@ -425,23 +464,24 @@ self.processing) -def upload_file(filename, system_id, settings): +def upload_file(filename, system_id, settings, rs_filename=None): """ Shortcut function to upload a file to the SCC. """ logger.info("Uploading file %s, using sytem %s" % (filename, system_id)) scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) scc.login(settings['website_credentials']) - measurement_id = scc.upload_file(filename, system_id) + measurement_id = scc.upload_file(filename, system_id, rs_filename=rs_filename) scc.logout() return measurement_id -def process_file(filename, system_id, monitor, settings): +def process_file(filename, system_id, settings, rs_filename=None): """ Shortcut function to process a file to the SCC. """ logger.info("Processing file %s, using sytem %s" % (filename, system_id)) + scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) scc.login(settings['website_credentials']) - measurement = scc.process(filename, system_id, monitor) + measurement = scc.process(filename, system_id, rs_filename=rs_filename) scc.logout() return measurement @@ -562,10 +602,11 @@ def setup_process_file(parser): def process_file_from_args(parsed): - process_file(parsed.file, parsed.system, parsed.process, parsed.config) + process_file(parsed.file, parsed.system, parsed.process, parsed.config, parsed.radiosounding) parser.add_argument("filename", help="Measurement file name or path.") parser.add_argument("system", help="Processing system id.") + parser.add_argument("--radiosounding", default=None, help="Radiosounding file name or path") parser.add_argument("-p", "--process", help="Wait for the results of the processing.", action="store_true") parser.set_defaults(execute=process_file_from_args) @@ -573,10 +614,12 @@ def setup_upload_file(parser): def upload_file_from_args(parsed): - upload_file(parsed.file, parsed.system, parsed.config) + upload_file(parsed.file, parsed.system, parsed.config, parsed.radiosounding) parser.add_argument("filename", help="Measurement file name or path.") parser.add_argument("system", help="Processing system id.") + parser.add_argument("--radiosounding", default=None, help="Radiosounding file name or path") + parser.set_defaults(execute=upload_file_from_args) diff -r 3e3e5bda6b77 -r 4669876326d4 settings_sample.yaml --- a/settings_sample.yaml Mon Jan 08 14:59:21 2018 +0100 +++ b/settings_sample.yaml Thu Oct 11 15:25:55 2018 +0300 @@ -1,6 +1,6 @@ # This file contains the user-specific settings for the scc_access script. # -# You should rename the file settings_sample.yaml to and move it outside the module repository. Take care to set the +# You should rename the file settings_sample.yaml (e.g. to settings.yaml) and move it outside the module repository. Take care to set the # minimum required permissions, as this file contains SCC access codes. For website login, it is recommended to # use credential for a user without station-management privileges. basic_credentials: ['username', 'password'] # The HTTP user name and password that is needed to access the SCC site.