scc_access/scc_access.py

changeset 14
c2020b2fdd05
parent 7
415d034b0864
child 15
93b6b945d939
child 29
3e3e5bda6b77
--- a/scc_access/scc_access.py	Fri Dec 15 22:51:47 2017 +0200
+++ b/scc_access/scc_access.py	Fri Dec 15 22:53:17 2017 +0200
@@ -1,41 +1,7 @@
-#!/usr/bin/env python
-"""
-The MIT License (MIT)
-
-Copyright (c) 2015, Ioannis Binietoglou
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-"""
-
-__version__ = "0.6.0"
-
-# Try to read the settings from the settings.py file
-try:
-    from settings import *
-except:
-    raise ImportError(
-        """A settings file (setting.py) is required to run the script. 
-         You can use settings.sample.py as a template.""")
-
 import requests
 requests.packages.urllib3.disable_warnings()
 
+import sys
 import urlparse
 import argparse
 import os
@@ -45,21 +11,10 @@
 from zipfile import ZipFile
 import datetime
 import logging
-
-logger = logging.getLogger(__name__)
+import yaml
 
 
-# Construct the absolute URLs
-LOGIN_URL = urlparse.urljoin(BASE_URL, 'accounts/login/')
-UPLOAD_URL = urlparse.urljoin(BASE_URL, 'data_processing/measurements/quick/')
-DOWNLOAD_PREPROCESSED = urlparse.urljoin(BASE_URL, 'data_processing/measurements/{0}/download-preprocessed/')
-DOWNLOAD_OPTICAL = urlparse.urljoin(BASE_URL, 'data_processing/measurements/{0}/download-optical/')
-DOWNLOAD_GRAPH = urlparse.urljoin(BASE_URL, 'data_processing/measurements/{0}/download-plots/')
-RERUN_ALL = urlparse.urljoin(BASE_URL, 'data_processing/measurements/{0}/rerun-all/')
-RERUN_PROCESSING = urlparse.urljoin(BASE_URL, 'data_processing/measurements/{0}/rerun-optical/')
-
-DELETE_MEASUREMENT = urlparse.urljoin(BASE_URL, 'admin/database/measurements/{0}/delete/')
-API_BASE_URL = urlparse.urljoin(BASE_URL, 'api/v1/')
+logger = logging.getLogger(__name__)
 
 # The regex to find the measurement id from the measurement page
 # This should be read from the uploaded file, but would require an extra NetCDF module.
@@ -68,34 +23,52 @@
 
 class SCC:
     """ 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. 
     """
 
-    def __init__(self, auth=BASIC_LOGIN, output_dir=OUTPUT_DIR):
+    def __init__(self, auth, output_dir, base_url):
         self.auth = auth
         self.output_dir = output_dir
+        self.base_url = base_url
         self.session = requests.Session()
+        self.construct_urls()
 
-    def login(self, credentials=DJANGO_LOGIN):
+    def construct_urls(self):
+        """ Construct all URLs needed for processing. """
+        # Construct the absolute URLs
+        self.login_url = urlparse.urljoin(self.base_url, 'accounts/login/')
+        self.upload_url = urlparse.urljoin(self.base_url, 'data_processing/measurements/quick/')
+        self.download_preprocessed_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/download-preprocessed/')
+        self.download_optical_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/download-optical/')
+        self.download_graph_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/download-plots/')
+        self.delete_measurement_pattern = urlparse.urljoin(self.base_url, 'admin/database/measurements/{0}/delete/')
+        self.api_base_url = urlparse.urljoin(self.base_url, 'api/v1/')
+
+    def login(self, credentials):
         """ Login the the website. """
         logger.debug("Attempting to login to SCC, username %s." % credentials[0])
         self.login_credentials = {'username': credentials[0],
                                   'password': credentials[1]}
 
-        logger.debug("Accessing login page at %s." % LOGIN_URL)
+        logger.debug("Accessing login page at %s." % self.login_url)
 
         # Get upload form
-        login_page = self.session.get(LOGIN_URL,
-                                      auth=self.auth, verify=False)
+        login_page = self.session.get(self.login_url, auth=self.auth, verify=False)
+
+        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(LOGIN_URL,
+        login_submit = self.session.post(self.login_url,
                                          data=self.login_credentials,
                                          headers={'X-CSRFToken': login_page.cookies['csrftoken'],
-                                                  'referer': LOGIN_URL},
+                                                  'referer': self.login_url},
                                          verify=False,
                                          auth=self.auth)
         return login_submit
@@ -107,7 +80,7 @@
         """ 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(UPLOAD_URL,
+        upload_page = self.session.get(self.upload_url,
                                        auth=self.auth,
                                        verify=False)
 
@@ -115,27 +88,27 @@
         upload_data = {'system': system_id}
         files = {'data': open(filename, 'rb')}
 
-        logging.info("Uploading of file %s started." % filename)
+        logger.info("Uploading of file %s started." % filename)
 
-        upload_submit = self.session.post(UPLOAD_URL,
+        upload_submit = self.session.post(self.upload_url,
                                           data=upload_data,
                                           files=files,
                                           headers={'X-CSRFToken': upload_page.cookies['csrftoken'],
-                                                   'referer': UPLOAD_URL},
+                                                   'referer': self.upload_url,},
                                           verify=False,
                                           auth=self.auth)
 
         if upload_submit.status_code != 200:
-            logging.warning("Connection error. Status code: %s" % upload_submit.status_code)
+            logger.warning("Connection error. Status code: %s" % upload_submit.status_code)
             return False
 
         # Check if there was a redirect to a new page.
-        if upload_submit.url == UPLOAD_URL:
+        if upload_submit.url == self.upload_url:
             measurement_id = False
-            logging.error("Uploaded file rejected! Try to upload manually to see the error.")
+            logger.error("Uploaded file rejected! Try to upload manually to see the error.")
         else:
             measurement_id = re.findall(regex, upload_submit.text)[0]
-            logging.error("Successfully uploaded measurement with id %s." % measurement_id)
+            logger.error("Successfully uploaded measurement with id %s." % measurement_id)
 
         return measurement_id
 
@@ -175,19 +148,19 @@
     def download_preprocessed(self, measurement_id):
         """ Download preprocessed files for the measurement id. """
         # Construct the download url
-        download_url = DOWNLOAD_PREPROCESSED.format(measurement_id)
+        download_url = self.download_preprocessed_pattern.format(measurement_id)
         self.download_files(measurement_id, 'scc_preprocessed', download_url)
 
     def download_optical(self, measurement_id):
         """ Download optical files for the measurement id. """
         # Construct the download url
-        download_url = DOWNLOAD_OPTICAL.format(measurement_id)
+        download_url = self.download_optical.format(measurement_id)
         self.download_files(measurement_id, 'scc_optical', download_url)
 
     def download_graphs(self, measurement_id):
         """ Download profile graphs for the measurement id. """
         # Construct the download url
-        download_url = DOWNLOAD_GRAPH.format(measurement_id)
+        download_url = self.download_graph_pattern.format(measurement_id)
         self.download_files(measurement_id, 'scc_plots', download_url)
 
     def rerun_processing(self, measurement_id, monitor=True):
@@ -199,7 +172,8 @@
                                        stream=True)
 
             if request.status_code != 200:
-                logging.error("Could not rerun processing for %s. Status code: %s" % (measurement_id, request.status_code))
+                logger.error(
+                    "Could not rerun processing for %s. Status code: %s" % (measurement_id, request.status_code))
                 return
 
             if monitor:
@@ -244,13 +218,13 @@
         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))
+                                                                                                   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.pre_processing,
+                                                                                   measurement.processing))
             if measurement.pre_processing == 127:
                 logger.info("Downloading preprocessed files.")
                 self.download_preprocessed(measurement_id)
@@ -264,7 +238,7 @@
 
     def get_status(self, measurement_id):
         """ Get the processing status for a measurement id through the API. """
-        measurement_url = urlparse.urljoin(API_BASE_URL, 'measurements/?id__exact=%s' % measurement_id)
+        measurement_url = urlparse.urljoin(self.api_base_url, 'measurements/?id__exact=%s' % measurement_id)
 
         response = self.session.get(measurement_url,
                                     auth=self.auth,
@@ -274,23 +248,27 @@
 
         if response_dict['objects']:
             measurement_list = response_dict['objects']
-            measurement = Measurement(measurement_list[0])
-            return (measurement.upload, measurement.pre_processing, measurement.processing)
+            measurement = Measurement(self.base_url, measurement_list[0])
+            return measurement.upload, measurement.pre_processing, measurement.processing
         else:
             logger.error("No measurement with id %s found on the SCC." % measurement_id)
             return None
 
     def get_measurement(self, measurement_id):
-        measurement_url = urlparse.urljoin(API_BASE_URL, 'measurements/%s/' % measurement_id)
+        measurement_url = urlparse.urljoin(self.api_base_url, 'measurements/%s/' % measurement_id)
 
         response = self.session.get(measurement_url,
                                     auth=self.auth,
                                     verify=False)
 
+        if response.status_code != 200:
+            logger.error('Could not access API. Status code %s.' % response.status_code)
+            sys.exit(1)
+
         response_dict = response.json()
 
         if response_dict:
-            measurement = Measurement(response_dict)
+            measurement = Measurement(self.base_url,response_dict)
             return measurement
         else:
             logger.error("No measurement with id %s found on the SCC." % measurement_id)
@@ -312,7 +290,7 @@
             return None
 
         # Go the the page confirming the deletion
-        delete_url = DELETE_MEASUREMENT.format(measurement.id)
+        delete_url = self.delete_measurement_pattern.format(measurement.id)
 
         confirm_page = self.session.get(delete_url,
                                         auth=self.auth,
@@ -341,7 +319,7 @@
 
     def available_measurements(self):
         """ Get a list of available measurement on the SCC. """
-        measurement_url = urlparse.urljoin(API_BASE_URL, 'measurements')
+        measurement_url = urlparse.urljoin(self.api_base_url, 'measurements')
         response = self.session.get(measurement_url,
                                     auth=self.auth,
                                     verify=False)
@@ -350,7 +328,7 @@
         measurements = None
         if response_dict:
             measurement_list = response_dict['objects']
-            measurements = [Measurement(measurement_dict) for measurement_dict in measurement_list]
+            measurements = [Measurement(self.base_url, measurement_dict) for measurement_dict in measurement_list]
             logger.info("Found %s measurements on the SCC." % len(measurements))
         else:
             logger.warning("No response received from the SCC when asked for available measurements.")
@@ -362,7 +340,7 @@
         date. 
         """
         date_str = t1.strftime('%Y%m%d')
-        search_url = urlparse.urljoin(API_BASE_URL, 'measurements/?id__startswith=%s' % date_str)
+        search_url = urlparse.urljoin(self.api_base_url, 'measurements/?id__startswith=%s' % date_str)
 
         response = self.session.get(search_url,
                                     auth=self.auth,
@@ -391,7 +369,8 @@
 class ApiObject:
     """ A generic class object. """
 
-    def __init__(self, dict_response):
+    def __init__(self, base_url, dict_response):
+        self.base_url = base_url
 
         if dict_response:
             # Add the dictionary key value pairs as object properties
@@ -421,11 +400,13 @@
 
     @property
     def rerun_processing_url(self):
-        return RERUN_PROCESSING.format(self.id)
+        url_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/rerun-optical/')
+        return url_pattern.format(self.id)
 
     @property
     def rerun_all_url(self):
-        return RERUN_ALL.format(self.id)
+        ulr_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/rerun-all/')
+        return ulr_pattern.format(self.id)
 
     def __str__(self):
         return "%s: %s, %s, %s" % (self.id,
@@ -434,64 +415,92 @@
                                    self.processing)
 
 
-def upload_file(filename, system_id, auth=BASIC_LOGIN, credential=DJANGO_LOGIN):
+def upload_file(filename, system_id, settings):
     """ Shortcut function to upload a file to the SCC. """
     logger.info("Uploading file %s, using sytem %s" % (filename, system_id))
 
-    scc = SCC(auth)
-    scc.login(credential)
+    scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url'])
+    scc.login(settings['website_credentials'])
     measurement_id = scc.upload_file(filename, system_id)
     scc.logout()
     return measurement_id
 
 
-def process_file(filename, system_id, auth=BASIC_LOGIN, credential=DJANGO_LOGIN):
+def process_file(filename, system_id, settings):
     """ Shortcut function to process a file to the SCC. """
     logger.info("Processing file %s, using sytem %s" % (filename, system_id))
 
-    scc = SCC(auth)
-    scc.login(credential)
+    scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url'])
+    scc.login(settings['website_credentials'])
     measurement = scc.process(filename, system_id)
     scc.logout()
     return measurement
 
 
-def delete_measurement(measurement_id, auth=BASIC_LOGIN, credential=DJANGO_LOGIN):
+def delete_measurement(measurement_id, settings):
     """ Shortcut function to delete a measurement from the SCC. """
     logger.info("Deleting %s" % measurement_id)
-    scc = SCC(auth)
-    scc.login(credential)
+
+    scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url'])
+    scc.login(settings['website_credentials'])
     scc.delete_measurement(measurement_id)
     scc.logout()
 
 
-def rerun_all(measurement_id, monitor, auth=BASIC_LOGIN, credential=DJANGO_LOGIN):
+def rerun_all(measurement_id, monitor, settings):
     """ Shortcut function to delete a measurement from the SCC. """
     logger.info("Rerunning all products for %s" % measurement_id)
-    scc = SCC(auth)
-    scc.login(credential)
+
+    scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url'])
+    scc.login(settings['website_credentials'])
     scc.rerun_all(measurement_id, monitor)
     scc.logout()
 
 
-def rerun_processing(measurement_id, monitor, auth=BASIC_LOGIN, credential=DJANGO_LOGIN):
+def rerun_processing(measurement_id, monitor, settings):
     """ Shortcut function to delete a measurement from the SCC. """
     logger.info("Rerunning (optical) processing for %s" % measurement_id)
-    scc = SCC(auth)
-    scc.login(credential)
+
+    scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url'])
+    scc.login(settings['website_credentials'])
     scc.rerun_processing(measurement_id, monitor)
     scc.logout()
-    
+
+
+def import_settings(config_file_path):
+    """ Read the configuration file.
+
+    The file should be in YAML syntax."""
+
+    if not os.path.isfile(config_file_path):
+        logger.error("Wrong path for configuration file (%s)" % config_file_path)
+        sys.exit(1)
+
+    with open(config_file_path) as yaml_file:
+        try:
+            settings = yaml.safe_load(yaml_file)
+            logger.debug("Read settings file(%s)" % config_file_path)
+        except:
+            logger.error("Could not parse YAML file (%s)" % config_file_path)
+            sys.exit(1)
+
+    # YAML limitation: does not read tuples
+    settings['basic_credentials'] = tuple(settings['basic_credentials'])
+    settings['website_credentials'] = tuple(settings['website_credentials'])
+    return settings
+
+
 def main():
     # Define the command line arguments.
     parser = argparse.ArgumentParser()
     parser.add_argument("filename", nargs='?', help="Measurement file name or path.", default='')
     parser.add_argument("system", nargs='?', help="Processing system id.", default=0)
+    parser.add_argument("-c", "--config", nargs='?', help="Path to configuration file")
     parser.add_argument("-p", "--process", help="Wait for the results of the processing.",
                         action="store_true")
     parser.add_argument("--delete", help="Measurement ID to delete.")
     parser.add_argument("--rerun-all", help="Measurement ID to rerun.")
-    parser.add_argument("--rerun-processing", help="Measurement ID to rerun processing routings.")
+    parser.add_argument("--rerun-processing", help="Measurement ID to rerun processing routines.")
 
     # Verbosity settings from http://stackoverflow.com/a/20663028
     parser.add_argument('-d', '--debug', help="Print debugging information.", action="store_const",
@@ -506,24 +515,21 @@
     # Get the logger with the appropriate level
     logging.basicConfig(format='%(levelname)s: %(message)s', level=args.loglevel)
 
-    # If the arguments are OK, try to login on the site and upload.
+    settings = import_settings(args.config)
+
+    # If the arguments are OK, try to log-in to SCC and upload.
     if args.delete:
         # If the delete is provided, do nothing else
-        delete_measurement(args.delete)
+        delete_measurement(args.delete, settings)
     elif args.rerun_all:
-        rerun_all(args.rerun_all, args.process)
+        rerun_all(args.rerun_all, args.process, settings)
     elif args.rerun_processing:
-        rerun_processing(args.rerun_processing, args.process)
+        rerun_processing(args.rerun_processing, args.process, settings)
     else:
         if (args.filename == '') or (args.system == 0):
             parser.error('Provide a valid filename and system parameters.\nRun with -h for help.\n')
 
         if args.process:
-            process_file(args.filename, args.system)
+            process_file(args.filename, args.system, settings)
         else:
-            upload_file(args.filename, args.system)
-
-
-# When running through terminal
-if __name__ == '__main__':
-        main()
+            upload_file(args.filename, args.system, settings)

mercurial