scc_access/scc_access.py

changeset 67
0106aeed80d8
parent 65
74ff07f81f4a
child 68
70c869fa3242
equal deleted inserted replaced
66:58006f895f8a 67:0106aeed80d8
1 """ This is a script that allows interaction with the SCC through the command line.
2
3 It is based on the requests module for accessing the server.
4
5 Most of the interactions are done through the web interface, i.e. by mimicking user interaction with the
6 SCC website (i.e. user login, data submission, etc.). In few cases, the SCC API is also used.
7
8 Most of the functionality is included in the SCC class. The class is used to login into the SCC website and automate
9 interaction with the site (i.e. upload a file, get measurement status, etc.).
10
11 Two other classes (Measurement, AncillaryFile) are used in some cases to handle the output of the SCC API.
12
13 Several shortcut functions are defined to perform specific tasks using the SCC class (e.g. process_file, delete_measurements etc).
14 """
1 import sys 15 import sys
2 16
3 import requests 17 import requests
4 18
5 try: 19 try:
29 # This should be read from the uploaded file, but would require an extra NetCDF module. 43 # This should be read from the uploaded file, but would require an extra NetCDF module.
30 regex = "<h3>Measurement (?P<measurement_id>.{12,15}) <small>" # {12, 15} to handle both old- and new-style measurement ids. 44 regex = "<h3>Measurement (?P<measurement_id>.{12,15}) <small>" # {12, 15} to handle both old- and new-style measurement ids.
31 45
32 46
33 class SCC: 47 class SCC:
34 """A simple class that will attempt to upload a file on the SCC server. 48 """A class that will attempt to interact SCC server.
35 49
36 The uploading is done by simulating a normal browser session. In the current 50 Most interactions are by simulating a normal browser session. In the current
37 version no check is performed, and no feedback is given if the upload 51 version few checks are performed before upload a file, and no feedback is given in case the upload
38 was successful. If everything is setup correctly, it will work. 52 fails.
39 """ 53 """
40 54
41 def __init__(self, auth, output_dir, base_url): 55 def __init__(self, auth, output_dir, base_url):
42 56
43 self.auth = auth 57 self.auth = auth
45 self.base_url = base_url 59 self.base_url = base_url
46 self.session = requests.Session() 60 self.session = requests.Session()
47 self.session.auth = auth 61 self.session.auth = auth
48 self.session.verify = False 62 self.session.verify = False
49 63
64 # Setup SCC server URLS for later use
50 self.login_url = urlparse.urljoin(self.base_url, 'accounts/login/') 65 self.login_url = urlparse.urljoin(self.base_url, 'accounts/login/')
51 self.logout_url = urlparse.urljoin(self.base_url, 'accounts/logout/') 66 self.logout_url = urlparse.urljoin(self.base_url, 'accounts/logout/')
52 self.list_measurements_url = urlparse.urljoin(self.base_url, 'data_processing/measurements/') 67 self.list_measurements_url = urlparse.urljoin(self.base_url, 'data_processing/measurements/')
53 68
54 self.upload_url = urlparse.urljoin(self.base_url, 'data_processing/measurements/quick/') 69 self.upload_url = urlparse.urljoin(self.base_url, 'data_processing/measurements/quick/')
55 self.measurement_page_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/') 70 self.measurement_page_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/')
56 self.download_hirelpp_pattern = urlparse.urljoin(self.base_url, 71 self.download_hirelpp_pattern = urlparse.urljoin(self.base_url,
57 'data_processing/measurements/{0}/download-hirelpp/') 72 'data_processing/measurements/{0}/download-hirelpp/')
58 self.download_cloudmask_pattern = urlparse.urljoin(self.base_url, 73 self.download_cloudmask_pattern = urlparse.urljoin(self.base_url,
59 'data_processing/measurements/{0}/download-cloudmask/') 74 'data_processing/measurements/{0}/download-cloudmask/')
60 75
61 self.download_elpp_pattern = urlparse.urljoin(self.base_url, 76 self.download_elpp_pattern = urlparse.urljoin(self.base_url,
62 'data_processing/measurements/{0}/download-preprocessed/') 77 'data_processing/measurements/{0}/download-preprocessed/')
63 self.download_elda_pattern = urlparse.urljoin(self.base_url, 78 self.download_elda_pattern = urlparse.urljoin(self.base_url,
64 'data_processing/measurements/{0}/download-optical/') 79 'data_processing/measurements/{0}/download-optical/')
65 self.download_plots_pattern = urlparse.urljoin(self.base_url, 80 self.download_plots_pattern = urlparse.urljoin(self.base_url,
66 'data_processing/measurements/{0}/download-plots/') 81 'data_processing/measurements/{0}/download-plots/')
67 self.download_elic_pattern = urlparse.urljoin(self.base_url, 82 self.download_elic_pattern = urlparse.urljoin(self.base_url,
68 'data_processing/measurements/{0}/download-elic/') 83 'data_processing/measurements/{0}/download-elic/')
69 self.delete_measurement_pattern = urlparse.urljoin(self.base_url, 'admin/database/measurements/{0}/delete/') 84 self.delete_measurement_pattern = urlparse.urljoin(self.base_url, 'admin/database/measurements/{0}/delete/')
70 85
86 # Setup API URLs for later use
71 self.api_base_url = urlparse.urljoin(self.base_url, 'api/v1/') 87 self.api_base_url = urlparse.urljoin(self.base_url, 'api/v1/')
72 self.api_measurement_pattern = urlparse.urljoin(self.api_base_url, 'measurements/{0}/') 88 self.api_measurement_pattern = urlparse.urljoin(self.api_base_url, 'measurements/{0}/')
73 self.api_measurements_url = urlparse.urljoin(self.api_base_url, 'measurements') 89 self.api_measurements_url = urlparse.urljoin(self.api_base_url, 'measurements')
74 self.api_sounding_search_pattern = urlparse.urljoin(self.api_base_url, 'sounding_files/?filename={0}') 90 self.api_sounding_search_pattern = urlparse.urljoin(self.api_base_url, 'sounding_files/?filename={0}')
75 self.api_lidarratio_search_pattern = urlparse.urljoin(self.api_base_url, 'lidarratio_files/?filename={0}') 91 self.api_lidarratio_search_pattern = urlparse.urljoin(self.api_base_url, 'lidarratio_files/?filename={0}')
76 self.api_overlap_search_pattern = urlparse.urljoin(self.api_base_url, 'overlap_files/?filename={0}') 92 self.api_overlap_search_pattern = urlparse.urljoin(self.api_base_url, 'overlap_files/?filename={0}')
77 93
78 def login(self, credentials): 94 def login(self, credentials):
79 """ Login to SCC. """ 95 """ Login to the SCC.
96
97 Parameters
98 ----------
99 credentials : tuple or list
100 A list or tuple in the form (username, password).
101 """
80 logger.debug("Attempting to login to SCC, username %s." % credentials[0]) 102 logger.debug("Attempting to login to SCC, username %s." % credentials[0])
81 login_credentials = {'username': credentials[0], 103 login_credentials = {'username': credentials[0],
82 'password': credentials[1]} 104 'password': credentials[1]}
83 105
84 logger.debug("Accessing login page at %s." % self.login_url) 106 logger.debug("Accessing login page at %s." % self.login_url)
85 107
86 # Get upload form 108 # Get login form
87 login_page = self.session.get(self.login_url) 109 login_page = self.session.get(self.login_url)
88 110
89 if not login_page.ok: 111 if not login_page.ok:
90 raise self.PageNotAccessibleError('Could not access login pages. Status code %s' % login_page.status_code) 112 raise self.PageNotAccessibleError('Could not access login pages. Status code %s' % login_page.status_code)
91 113
96 headers={'X-CSRFToken': login_page.cookies['csrftoken'], 118 headers={'X-CSRFToken': login_page.cookies['csrftoken'],
97 'referer': self.login_url}) 119 'referer': self.login_url})
98 return login_submit 120 return login_submit
99 121
100 def logout(self): 122 def logout(self):
101 """ Logout from SCC """ 123 """ Logout from the SCC """
102 return self.session.get(self.logout_url, stream=True) 124 return self.session.get(self.logout_url, stream=True)
103 125
104 def upload_file(self, filename, system_id, force_upload, delete_related, delay=0, rs_filename=None, ov_filename=None, lr_filename=None): 126 def upload_file(self, filename, system_id, force_upload, delete_related, delay=0, rs_filename=None,
105 """ Upload a filename for processing with a specific system. If the 127 ov_filename=None, lr_filename=None):
106 upload is successful, it returns the measurement id. """ 128 """ Upload a file for processing.
129
130 If the upload is successful, it returns the measurement id.
131
132
133 Parameters
134 ----------
135 filename : str
136 File path of the file to upload
137 system_id : int
138 System id to be used in the processing
139 force_upload : bool
140 If True, if a measurement with the same ID is found on the server, it will be first deleted and the
141 file current file will be uploaded. If False, the file will not be uploaded if the measurement ID is
142 already present on the SCC server.
143 delete_related : bool
144 Answer to delete related question when deleting existing measurements from the SCC.
145 rs_filename, ov_filename, lr_filename : str
146 Ancillary files pahts to be uploaded.
147 """
148 # Get the measurement ID from the netcdf file
107 measurement_id = self.measurement_id_from_file(filename) 149 measurement_id = self.measurement_id_from_file(filename)
108 150
151 # Handle possible existing measurements with the same ID on the SCC server.
109 logger.debug('Checking if a measurement with the same id already exists on the SCC server.') 152 logger.debug('Checking if a measurement with the same id already exists on the SCC server.')
110 existing_measurement, _ = self.get_measurement(measurement_id) 153 existing_measurement, _ = self.get_measurement(measurement_id)
111 154
112 if existing_measurement: 155 if existing_measurement:
113 if force_upload: 156 if force_upload:
116 self.delete_measurement(measurement_id, delete_related) 159 self.delete_measurement(measurement_id, delete_related)
117 else: 160 else:
118 logger.error( 161 logger.error(
119 "Measurement with id {} already exists on the SCC. Use --force_upload flag to overwrite it.".format( 162 "Measurement with id {} already exists on the SCC. Use --force_upload flag to overwrite it.".format(
120 measurement_id)) 163 measurement_id))
121 # TODO: Implement handling at the proper place. This does not allow the SCC class to be used by external programs. 164 # TODO: Implement handling at the proper place. Exiting here does not allow the SCC class to be
165 # used by external programs. Instead an exception should be raised.
122 sys.exit(1) 166 sys.exit(1)
167
168 # Upload the file(s)
123 169
124 # Get submit page 170 # Get submit page
125 upload_page = self.session.get(self.upload_url) 171 upload_page = self.session.get(self.upload_url)
126 172
127 # Submit the data 173 # Submit the data
130 176
131 logger.debug("Submitted processing parameters - System: {}, Delay: {}".format(system_id, delay)) 177 logger.debug("Submitted processing parameters - System: {}, Delay: {}".format(system_id, delay))
132 178
133 files = {'data': open(filename, 'rb')} 179 files = {'data': open(filename, 'rb')}
134 180
181 # Add ancillary files to be uploaded
135 if rs_filename is not None: 182 if rs_filename is not None:
136 ancillary_file, _ = self.get_ancillary(rs_filename, 'sounding') 183 ancillary_file, _ = self.get_ancillary(rs_filename, 'sounding')
137 184
138 if ancillary_file.already_on_scc: 185 if ancillary_file.already_on_scc:
139 logger.warning("Sounding file {0.filename} already on the SCC with id {0.id}. Ignoring it.".format(ancillary_file)) 186 logger.warning(
187 "Sounding file {0.filename} already on the SCC with id {0.id}. Ignoring it.".format(ancillary_file))
140 else: 188 else:
141 logger.debug('Adding sounding file %s' % rs_filename) 189 logger.debug('Adding sounding file %s' % rs_filename)
142 files['sounding_file'] = open(rs_filename, 'rb') 190 files['sounding_file'] = open(rs_filename, 'rb')
143 191
144 if ov_filename is not None: 192 if ov_filename is not None:
145 ancillary_file, _ = self.get_ancillary(ov_filename, 'overlap') 193 ancillary_file, _ = self.get_ancillary(ov_filename, 'overlap')
146 194
147 if ancillary_file.already_on_scc: 195 if ancillary_file.already_on_scc:
148 logger.warning("Overlap file {0.filename} already on the SCC with id {0.id}. Ignoring it.".format(ancillary_file)) 196 logger.warning(
197 "Overlap file {0.filename} already on the SCC with id {0.id}. Ignoring it.".format(ancillary_file))
149 else: 198 else:
150 logger.debug('Adding overlap file %s' % ov_filename) 199 logger.debug('Adding overlap file %s' % ov_filename)
151 files['overlap_file'] = open(ov_filename, 'rb') 200 files['overlap_file'] = open(ov_filename, 'rb')
152 201
153 if lr_filename is not None: 202 if lr_filename is not None:
154 ancillary_file, _ = self.get_ancillary(lr_filename, 'lidarratio') 203 ancillary_file, _ = self.get_ancillary(lr_filename, 'lidarratio')
155 204
156 if ancillary_file.already_on_scc: 205 if ancillary_file.already_on_scc:
157 logger.warning( 206 logger.warning(
158 "Lidar ratio file {0.filename} already on the SCC with id {0.id}. Ignoring it.".format(ancillary_file)) 207 "Lidar ratio file {0.filename} already on the SCC with id {0.id}. Ignoring it.".format(
208 ancillary_file))
159 else: 209 else:
160 logger.debug('Adding lidar ratio file %s' % lr_filename) 210 logger.debug('Adding lidar ratio file %s' % lr_filename)
161 files['lidar_ratio_file'] = open(lr_filename, 'rb') 211 files['lidar_ratio_file'] = open(lr_filename, 'rb')
162 212
213 # Upload the files
163 logger.info("Uploading of file %s started." % filename) 214 logger.info("Uploading of file %s started." % filename)
164 215
165 upload_submit = self.session.post(self.upload_url, 216 upload_submit = self.session.post(self.upload_url,
166 data=upload_data, 217 data=upload_data,
167 files=files, 218 files=files,
170 221
171 if upload_submit.status_code != 200: 222 if upload_submit.status_code != 200:
172 logger.warning("Connection error. Status code: %s" % upload_submit.status_code) 223 logger.warning("Connection error. Status code: %s" % upload_submit.status_code)
173 return False 224 return False
174 225
175 # Check if there was a redirect to a new page. 226 # Check if there was a redirect to a new page. If not, something went wrong
176 if upload_submit.url == self.upload_url: 227 if upload_submit.url == self.upload_url:
177 measurement_id = False 228 measurement_id = False
178 logger.error("Uploaded file(s) rejected! Try to upload manually to see the error.") 229 logger.error("Uploaded file(s) rejected! Try to upload manually to see the error.")
179 else: 230 else:
180 measurement_id = re.findall(regex, upload_submit.text)[0] 231 # TODO: Check if this is needed. This was used when the measurement ID was not read from the input file.
232 measurement_id = re.findall(regex, upload_submit.text)[0] # Get the measurement ID from the output page
181 logger.info("Successfully uploaded measurement with id %s." % measurement_id) 233 logger.info("Successfully uploaded measurement with id %s." % measurement_id)
182 logger.info("You can monitor the processing progress online: {}".format(self.measurement_page_pattern.format(measurement_id))) 234 logger.info("You can monitor the processing progress online: {}".format(
235 self.measurement_page_pattern.format(measurement_id)))
183 return measurement_id 236 return measurement_id
184 237
185 @staticmethod 238 @staticmethod
186 def measurement_id_from_file(filename): 239 def measurement_id_from_file(filename):
187 """ Get the measurement id from the input file. """ 240 """ Get the measurement id from the input file.
241
242 Parameters
243 ----------
244 filename : str
245 File path of the input file.
246 """
188 247
189 if not os.path.isfile(filename): 248 if not os.path.isfile(filename):
190 logger.error("File {} does not exist.".format(filename)) 249 logger.error("File {} does not exist.".format(filename))
191 sys.exit(1) 250 sys.exit(1)
192 251
201 260
202 return measurement_id 261 return measurement_id
203 262
204 def download_files(self, measurement_id, subdir, download_url): 263 def download_files(self, measurement_id, subdir, download_url):
205 """ Downloads some files from the download_url to the specified 264 """ Downloads some files from the download_url to the specified
206 subdir. This method is used to download preprocessed file, optical 265 subdir.
207 files etc. 266
267 This is a general method used to download preprocessed file, optical
268 files by other, file-specific, methods.
208 """ 269 """
209 # TODO: Make downloading more robust (e.g. in case that files do not exist on server). 270 # TODO: Make downloading more robust (e.g. in case that files do not exist on server).
210 # Get the file 271 # Get the file
211 request = self.session.get(download_url, stream=True) 272 request = self.session.get(download_url, stream=True)
212 273
343 logger.info("Rerun-all command submitted successfully for id {}.".format(measurement_id)) 404 logger.info("Rerun-all command submitted successfully for id {}.".format(measurement_id))
344 405
345 if monitor: 406 if monitor:
346 self.monitor_processing(measurement_id) 407 self.monitor_processing(measurement_id)
347 408
348 def process(self, filename, system_id, monitor, force_upload, delete_related, delay=0, rs_filename=None, lr_filename=None, ov_filename=None): 409 def process(self, filename, system_id, monitor, force_upload, delete_related, delay=0, rs_filename=None,
410 lr_filename=None, ov_filename=None):
349 """ Upload a file for processing and wait for the processing to finish. 411 """ Upload a file for processing and wait for the processing to finish.
350 If the processing is successful, it will download all produced files. 412 If the processing is successful, it will download all produced files.
351 """ 413 """
352 logger.info("--- Processing started on %s. ---" % datetime.datetime.now()) 414 logger.info("--- Processing started on %s. ---" % datetime.datetime.now())
353 # Upload file 415 # Upload file
373 435
374 # try to deal with error 404 436 # try to deal with error 404
375 attempts_count = 0 437 attempts_count = 0
376 max_attempts = retry_max + 1 438 max_attempts = retry_max + 1
377 439
378 # try to wait for measurement to appear in API 440 # try to wait for measurement to appear in API. A user has reported that this does not happen immediately.
379 measurement = None 441 measurement = None
380 logger.info("Looking for measurement %s on the SCC.", measurement_id) 442 logger.info("Looking for measurement %s on the SCC.", measurement_id)
381 443
382 while attempts_count < max_attempts: 444 while attempts_count < max_attempts:
383 attempts_count += 1 445 attempts_count += 1
422 logger.info("Downloading ELDA plots.") 484 logger.info("Downloading ELDA plots.")
423 self.download_plots(measurement_id) 485 self.download_plots(measurement_id)
424 if measurement.elic == 127: 486 if measurement.elic == 127:
425 logger.info("Downloading ELIC files.") 487 logger.info("Downloading ELIC files.")
426 self.download_elic(measurement_id) 488 self.download_elic(measurement_id)
427 if measurement.is_calibration and measurement.eldec==0: 489 if measurement.is_calibration and measurement.eldec == 0:
428 logger.info("Downloading ELDEC files.") 490 logger.info("Downloading ELDEC files.")
429 self.download_eldec(measurement_id) 491 self.download_eldec(measurement_id)
430 logger.info("--- Processing finished. ---") 492 logger.info("--- Processing finished. ---")
431 493
432 return measurement 494 return measurement
433 495
434 def get_measurement(self, measurement_id): 496 def get_measurement(self, measurement_id):
435 497 """ Get a measurement information from the SCC API.
436 if measurement_id is None: # Is this still required? 498
499 Parameters
500 ----------
501 measurement_id : str
502 The measurement ID to search.
503
504 Returns
505 -------
506 : Measurement object or None
507 If the measurement is found, a Measurement object is returned. If not, it returns None
508 """
509 # TODO: Consider to homogenize with get_ancillary method (i.e. always return a Measurement object).
510
511 if measurement_id is None: # TODO: Is this still required?
437 return None 512 return None
438 513
514 # Access the API
439 measurement_url = self.api_measurement_pattern.format(measurement_id) 515 measurement_url = self.api_measurement_pattern.format(measurement_id)
440 logger.debug("Measurement API URL: %s" % measurement_url) 516 logger.debug("Measurement API URL: %s" % measurement_url)
441 517
442 response = self.session.get(measurement_url) 518 response = self.session.get(measurement_url)
443 519
459 535
460 return measurement, response.status_code 536 return measurement, response.status_code
461 537
462 def delete_measurement(self, measurement_id, delete_related): 538 def delete_measurement(self, measurement_id, delete_related):
463 """ Deletes a measurement with the provided measurement id. The user 539 """ Deletes a measurement with the provided measurement id. The user
464 should have the appropriate permissions. 540 should have the appropriate permissions (i.e. access to the admin site).
465 541
466 The procedures is performed directly through the web interface and 542 The procedures is performed directly through the web interface and
467 NOT through the API. 543 NOT through the API.
468 """ 544 """
469 # Get the measurement object 545 # Get the measurement object
504 580
505 logger.info("Deleted measurement {0}".format(measurement_id)) 581 logger.info("Deleted measurement {0}".format(measurement_id))
506 return True 582 return True
507 583
508 def available_measurements(self): 584 def available_measurements(self):
509 """ Get a list of available measurement on the SCC. """ 585 """ Get a list of available measurement on the SCC.
586
587 The methods is currently not used, could be merged with list_measurements.
588 """
510 response = self.session.get(self.api_measurements_url) 589 response = self.session.get(self.api_measurements_url)
511 response_dict = response.json() 590 response_dict = response.json()
512 591
513 if response_dict: 592 if response_dict:
514 measurement_list = response_dict['objects'] 593 measurement_list = response_dict['objects']
519 measurements = None 598 measurements = None
520 599
521 return measurements 600 return measurements
522 601
523 def list_measurements(self, id_exact=None, id_startswith=None): 602 def list_measurements(self, id_exact=None, id_startswith=None):
524 603 """ Get the response text from the API. """
525 # TODO: Change this to work through the API 604
605 # TODO: Add some error handling, e.g. as per available_measurements method
526 606
527 # Need to set to empty string if not specified, we won't get any results 607 # Need to set to empty string if not specified, we won't get any results
528 params = {} 608 params = {}
529 609
530 if id_exact is not None: 610 if id_exact is not None:
538 618
539 def measurement_id_for_date(self, t1, call_sign, base_number=0): 619 def measurement_id_for_date(self, t1, call_sign, base_number=0):
540 """ Give the first available measurement id on the SCC for the specific 620 """ Give the first available measurement id on the SCC for the specific
541 date. 621 date.
542 """ 622 """
623 # TODO: Check if this method needs updating to handle all measurement_ID formats.
543 date_str = t1.strftime('%Y%m%d') 624 date_str = t1.strftime('%Y%m%d')
544 base_id = "%s%s" % (date_str, call_sign) 625 base_id = "%s%s" % (date_str, call_sign)
545 search_url = urlparse.urljoin(self.api_base_url, 'measurements/?id__startswith=%s' % base_id) 626 search_url = urlparse.urljoin(self.api_base_url, 'measurements/?id__startswith=%s' % base_id)
546 627
547 response = self.session.get(search_url) 628 response = self.session.get(search_url)
740 821
741 822
742 class AncillaryFile(ApiObject): 823 class AncillaryFile(ApiObject):
743 """ This class represents the ancilalry file object as returned in the SCC API. 824 """ This class represents the ancilalry file object as returned in the SCC API.
744 """ 825 """
826
745 @property 827 @property
746 def already_on_scc(self): 828 def already_on_scc(self):
747 if self.exists is False: 829 if self.exists is False:
748 return False 830 return False
749 831
753 return "%s: %s, %s" % (self.id, 835 return "%s: %s, %s" % (self.id,
754 self.filename, 836 self.filename,
755 self.status) 837 self.status)
756 838
757 839
840 # Methods that use the SCC class to perform specific tasks.
758 def process_file(filename, system_id, settings, force_upload, delete_related, 841 def process_file(filename, system_id, settings, force_upload, delete_related,
759 delay=0, monitor=True, rs_filename=None, lr_filename=None, ov_filename=None): 842 delay=0, monitor=True, rs_filename=None, lr_filename=None, ov_filename=None):
760 """ Shortcut function to process a file to the SCC. """ 843 """ Shortcut function to process a file to the SCC. """
761 logger.info("Processing file %s, using system %s" % (filename, system_id)) 844 logger.info("Processing file %s, using system %s" % (filename, system_id))
762 845
879 parser.set_defaults(execute=rerun_processing_from_args) 962 parser.set_defaults(execute=rerun_processing_from_args)
880 963
881 964
882 def setup_upload_file(parser): 965 def setup_upload_file(parser):
883 """ Upload but do not monitor processing progress. """ 966 """ Upload but do not monitor processing progress. """
967
884 def upload_file_from_args(parsed): 968 def upload_file_from_args(parsed):
885 process_file(parsed.filename, parsed.system, parsed.config, 969 process_file(parsed.filename, parsed.system, parsed.config,
886 delay=parsed.delay, 970 delay=parsed.delay,
887 monitor=parsed.process, 971 monitor=parsed.process,
888 force_upload=parsed.force_upload, 972 force_upload=parsed.force_upload,
931 def setup_download_measurements(parser): 1015 def setup_download_measurements(parser):
932 def download_measurements_from_args(parsed): 1016 def download_measurements_from_args(parsed):
933 download_measurements(parsed.IDs, parsed.max_retries, parsed.ignore_errors, parsed.config) 1017 download_measurements(parsed.IDs, parsed.max_retries, parsed.ignore_errors, parsed.config)
934 1018
935 parser.add_argument("IDs", help="Measurement IDs that should be downloaded.", nargs="+") 1019 parser.add_argument("IDs", help="Measurement IDs that should be downloaded.", nargs="+")
936 parser.add_argument("--max_retries", help="Number of times to retry in cases of missing measurement id.", default=0, type=int) 1020 parser.add_argument("--max_retries", help="Number of times to retry in cases of missing measurement id.", default=0,
937 parser.add_argument("--ignore_errors", help="Ignore errors when downloading multiple measurements.", action="store_false") 1021 type=int)
1022 parser.add_argument("--ignore_errors", help="Ignore errors when downloading multiple measurements.",
1023 action="store_false")
938 parser.set_defaults(execute=download_measurements_from_args) 1024 parser.set_defaults(execute=download_measurements_from_args)
939 1025
940 1026
941 def main(): 1027 def main():
942 # Define the command line arguments. 1028 # Define the command line arguments.
943 parser = argparse.ArgumentParser() 1029 parser = argparse.ArgumentParser()
944 subparsers = parser.add_subparsers() 1030 subparsers = parser.add_subparsers()
945 1031
946 delete_parser = subparsers.add_parser("delete", help="Deletes a measurement.") 1032 delete_parser = subparsers.add_parser("delete", help="Deletes a measurement.")
947 rerun_all_parser = subparsers.add_parser("rerun-all", help="Rerun all processing steps for the provided measurement IDs.") 1033 rerun_all_parser = subparsers.add_parser("rerun-all",
1034 help="Rerun all processing steps for the provided measurement IDs.")
948 rerun_processing_parser = subparsers.add_parser("rerun-elpp", 1035 rerun_processing_parser = subparsers.add_parser("rerun-elpp",
949 help="Rerun low-resolution processing steps for the provided measurement ID.") 1036 help="Rerun low-resolution processing steps for the provided measurement ID.")
950 upload_file_parser = subparsers.add_parser("upload-file", help="Submit a file and, optionally, download the output products.") 1037 upload_file_parser = subparsers.add_parser("upload-file",
1038 help="Submit a file and, optionally, download the output products.")
951 list_parser = subparsers.add_parser("list", help="List measurements registered on the SCC.") 1039 list_parser = subparsers.add_parser("list", help="List measurements registered on the SCC.")
952 download_parser = subparsers.add_parser("download", help="Download selected measurements.") 1040 download_parser = subparsers.add_parser("download", help="Download selected measurements.")
953 1041
954 setup_delete(delete_parser) 1042 setup_delete(delete_parser)
955 setup_rerun_all(rerun_all_parser) 1043 setup_rerun_all(rerun_all_parser)

mercurial