scc_access/scc_access.py

changeset 43
0151be380f3c
parent 16
ddaea4327bd5
child 51
a4ca2b6d67f5
equal deleted inserted replaced
42:c95ee9720e7b 43:0151be380f3c
1 import sys
2
1 import requests 3 import requests
2 requests.packages.urllib3.disable_warnings() 4
3 5 # Python 2 and 3 support
4 import sys 6 try:
5 import urlparse 7 import urllib.parse as urlparse # Python 3
8 except ImportError:
9 from urlparse import urlparse # Python 2
10
6 import argparse 11 import argparse
7 import os 12 import os
8 import re 13 import re
9 import time 14 import time
10 import StringIO 15 from io import BytesIO
11 from zipfile import ZipFile 16 from zipfile import ZipFile
12 import datetime 17 import datetime
13 import logging 18 import logging
14 import yaml 19 import yaml
15 20
21 import netCDF4 as netcdf
22
23 requests.packages.urllib3.disable_warnings()
16 24
17 logger = logging.getLogger(__name__) 25 logger = logging.getLogger(__name__)
18 26
19 # The regex to find the measurement id from the measurement page 27 # The regex to find the measurement id from the measurement page
20 # This should be read from the uploaded file, but would require an extra NetCDF module. 28 # This should be read from the uploaded file, but would require an extra NetCDF module.
32 def __init__(self, auth, output_dir, base_url): 40 def __init__(self, auth, output_dir, base_url):
33 self.auth = auth 41 self.auth = auth
34 self.output_dir = output_dir 42 self.output_dir = output_dir
35 self.base_url = base_url 43 self.base_url = base_url
36 self.session = requests.Session() 44 self.session = requests.Session()
37 self.construct_urls() 45
38
39 def construct_urls(self):
40 """ Construct all URLs needed for processing. """
41 # Construct the absolute URLs 46 # Construct the absolute URLs
42 self.login_url = urlparse.urljoin(self.base_url, 'accounts/login/') 47 self.login_url = urlparse.urljoin(self.base_url, 'accounts/login/')
43 self.upload_url = urlparse.urljoin(self.base_url, 'data_processing/measurements/quick/') 48 self.upload_url = urlparse.urljoin(self.base_url, 'data_processing/measurements/quick/')
44 self.download_preprocessed_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/download-preprocessed/') 49 self.download_hirelpp_pattern = urlparse.urljoin(self.base_url,
45 self.download_optical_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/download-optical/') 50 'data_processing/measurements/{0}/download-hirelpp/')
46 self.download_graph_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/download-plots/') 51 self.download_cloudmask_pattern = urlparse.urljoin(self.base_url,
52 'data_processing/measurements/{0}/download-cloudmask/')
53 self.download_elpp_pattern = urlparse.urljoin(self.base_url,
54 'data_processing/measurements/{0}/download-preprocessed/')
55 self.download_elda_pattern = urlparse.urljoin(self.base_url,
56 'data_processing/measurements/{0}/download-optical/')
57 self.download_plot_pattern = urlparse.urljoin(self.base_url,
58 'data_processing/measurements/{0}/download-plots/')
59 self.download_elic_pattern = urlparse.urljoin(self.base_url,
60 'data_processing/measurements/{0}/download-elic/')
47 self.delete_measurement_pattern = urlparse.urljoin(self.base_url, 'admin/database/measurements/{0}/delete/') 61 self.delete_measurement_pattern = urlparse.urljoin(self.base_url, 'admin/database/measurements/{0}/delete/')
48 self.api_base_url = urlparse.urljoin(self.base_url, 'api/v1/') 62 self.api_base_url = urlparse.urljoin(self.base_url, 'api/v1/')
63
64 self.login_credentials = None
49 65
50 def login(self, credentials): 66 def login(self, credentials):
51 """ Login the the website. """ 67 """ Login the the website. """
52 logger.debug("Attempting to login to SCC, username %s." % credentials[0]) 68 logger.debug("Attempting to login to SCC, username %s." % credentials[0])
53 self.login_credentials = {'username': credentials[0], 69 self.login_credentials = {'username': credentials[0],
61 if login_page.status_code != 200: 77 if login_page.status_code != 200:
62 logger.error('Could not access login pages. Status code %s' % login_page.status_code) 78 logger.error('Could not access login pages. Status code %s' % login_page.status_code)
63 sys.exit(1) 79 sys.exit(1)
64 80
65 logger.debug("Submiting credentials.") 81 logger.debug("Submiting credentials.")
66 82
67 # Submit the login data 83 # Submit the login data
68 login_submit = self.session.post(self.login_url, 84 login_submit = self.session.post(self.login_url,
69 data=self.login_credentials, 85 data=self.login_credentials,
70 headers={'X-CSRFToken': login_page.cookies['csrftoken'], 86 headers={'X-CSRFToken': login_page.cookies['csrftoken'],
71 'referer': self.login_url}, 87 'referer': self.login_url},
74 return login_submit 90 return login_submit
75 91
76 def logout(self): 92 def logout(self):
77 pass 93 pass
78 94
79 def upload_file(self, filename, system_id): 95 def upload_file(self, filename, system_id, force_upload, delete_related):
80 """ Upload a filename for processing with a specific system. If the 96 """ Upload a filename for processing with a specific system. If the
81 upload is successful, it returns the measurement id. """ 97 upload is successful, it returns the measurement id. """
98
99 measurement_id = self.measurement_id_from_file(filename)
100
101 logger.debug('Checking if a measurement with the same id already exists on the SCC server.')
102 existing_measurement = self.get_measurement(measurement_id)
103
104 if existing_measurement:
105 if force_upload:
106 logger.info(
107 "Measurement with id {} already exists on the SCC. Trying to delete it...".format(measurement_id))
108 self.delete_measurement(measurement_id, delete_related)
109 else:
110 logger.error(
111 "Measurement with id {} already exists on the SCC. Use --force_upload flag to overwrite it.".format(
112 measurement_id))
113 sys.exit(1)
114
82 # Get submit page 115 # Get submit page
83 upload_page = self.session.get(self.upload_url, 116 upload_page = self.session.get(self.upload_url,
84 auth=self.auth, 117 auth=self.auth,
85 verify=False) 118 verify=False)
86 119
87 # Submit the data 120 # Submit the data
88 upload_data = {'system': system_id} 121 upload_data = {'system': system_id}
89 files = {'data': open(filename, 'rb')} 122 files = {'data': open(filename, 'rb')}
90 123
91 logger.info("Uploading of file %s started." % filename) 124 logger.info("Uploading of file %s started." % filename)
92 125 logger.debug("URL: {0}, data: {1}, 'X-CSRFToken': {2}".format(self.upload_url,
126 upload_data,
127 upload_page.cookies['csrftoken']))
93 upload_submit = self.session.post(self.upload_url, 128 upload_submit = self.session.post(self.upload_url,
94 data=upload_data, 129 data=upload_data,
95 files=files, 130 files=files,
96 headers={'X-CSRFToken': upload_page.cookies['csrftoken'], 131 headers={'X-CSRFToken': upload_page.cookies['csrftoken'],
97 'referer': self.upload_url,}, 132 'referer': self.upload_url, },
98 verify=False, 133 verify=False,
99 auth=self.auth) 134 auth=self.auth)
100 135
101 if upload_submit.status_code != 200: 136 if upload_submit.status_code != 200:
102 logger.warning("Connection error. Status code: %s" % upload_submit.status_code) 137 logger.warning("Connection error. Status code: %s" % upload_submit.status_code)
106 if upload_submit.url == self.upload_url: 141 if upload_submit.url == self.upload_url:
107 measurement_id = False 142 measurement_id = False
108 logger.error("Uploaded file rejected! Try to upload manually to see the error.") 143 logger.error("Uploaded file rejected! Try to upload manually to see the error.")
109 else: 144 else:
110 measurement_id = re.findall(regex, upload_submit.text)[0] 145 measurement_id = re.findall(regex, upload_submit.text)[0]
111 logger.error("Successfully uploaded measurement with id %s." % measurement_id) 146 logger.info("Successfully uploaded measurement with id %s." % measurement_id)
147
148 return measurement_id
149
150 @staticmethod
151 def measurement_id_from_file(filename):
152 """ Get the measurement id from the input file. """
153
154 if not os.path.isfile(filename):
155 logger.error("File {} does not exist.".format(filename))
156 sys.exit(1)
157
158 with netcdf.Dataset(filename) as f:
159 try:
160 measurement_id = f.Measurement_ID
161 except AttributeError:
162 logger.error(
163 "Input file {} does not contain a Measurement_ID global attribute. Wrong file format?".format(
164 filename))
165 sys.exit(1)
112 166
113 return measurement_id 167 return measurement_id
114 168
115 def download_files(self, measurement_id, subdir, download_url): 169 def download_files(self, measurement_id, subdir, download_url):
116 """ Downloads some files from the download_url to the specified 170 """ Downloads some files from the download_url to the specified
126 local_dir = os.path.join(self.output_dir, measurement_id, subdir) 180 local_dir = os.path.join(self.output_dir, measurement_id, subdir)
127 if not os.path.exists(local_dir): 181 if not os.path.exists(local_dir):
128 os.makedirs(local_dir) 182 os.makedirs(local_dir)
129 183
130 # Save the file by chunk, needed if the file is big. 184 # Save the file by chunk, needed if the file is big.
131 memory_file = StringIO.StringIO() 185 memory_file = BytesIO()
132 186
133 for chunk in request.iter_content(chunk_size=1024): 187 for chunk in request.iter_content(chunk_size=1024):
134 if chunk: # filter out keep-alive new chunks 188 if chunk: # filter out keep-alive new chunks
135 memory_file.write(chunk) 189 memory_file.write(chunk)
136 memory_file.flush() 190 memory_file.flush()
143 local_file = os.path.join(local_dir, basename) 197 local_file = os.path.join(local_dir, basename)
144 198
145 with open(local_file, 'wb') as f: 199 with open(local_file, 'wb') as f:
146 f.write(zip_file.read(ziped_name)) 200 f.write(zip_file.read(ziped_name))
147 201
148 def download_preprocessed(self, measurement_id): 202 def download_hirelpp(self, measurement_id):
203 """ Download hirelpp files for the measurement id. """
204 # Construct the download url
205 download_url = self.download_hirelpp_pattern.format(measurement_id)
206 try:
207 self.download_files(measurement_id, 'scc_hirelpp', download_url)
208 except Exception as e:
209 logger.error("Could not download HiRElPP files. Error message: {}".format(e))
210 logger.debug('Download exception:', exc_info=True)
211
212 def download_cloudmask(self, measurement_id):
213 """ Download cloudmask files for the measurement id. """
214 # Construct the download url
215 download_url = self.download_cloudmask_pattern.format(measurement_id)
216 try:
217 self.download_files(measurement_id, 'scc_cloudscreen', download_url)
218 except Exception as e:
219 logger.error("Could not download cloudscreen files. Error message: {}".format(e))
220 logger.debug('Download exception:', exc_info=True)
221
222 def download_elpp(self, measurement_id):
149 """ Download preprocessed files for the measurement id. """ 223 """ Download preprocessed files for the measurement id. """
150 # Construct the download url 224 # Construct the download url
151 download_url = self.download_preprocessed_pattern.format(measurement_id) 225 download_url = self.download_elpp_pattern.format(measurement_id)
152 self.download_files(measurement_id, 'scc_preprocessed', download_url) 226 try:
153 227 self.download_files(measurement_id, 'scc_preprocessed', download_url)
154 def download_optical(self, measurement_id): 228 except Exception as e:
229 logger.error("Could not download ElPP files. Error message: {}".format(e))
230 logger.debug('Download exception:', exc_info=True)
231
232 def download_elda(self, measurement_id):
155 """ Download optical files for the measurement id. """ 233 """ Download optical files for the measurement id. """
156 # Construct the download url 234 # Construct the download url
157 download_url = self.download_optical_pattern.format(measurement_id) 235 download_url = self.download_elda_pattern.format(measurement_id)
158 self.download_files(measurement_id, 'scc_optical', download_url) 236 try:
159 237 self.download_files(measurement_id, 'scc_optical', download_url)
160 def download_graphs(self, measurement_id): 238 except Exception as e:
239 logger.error("Could not download ELDA files. Error message: {}".format(e))
240 logger.debug('Download exception:', exc_info=True)
241
242 def download_plots(self, measurement_id):
161 """ Download profile graphs for the measurement id. """ 243 """ Download profile graphs for the measurement id. """
162 # Construct the download url 244 # Construct the download url
163 download_url = self.download_graph_pattern.format(measurement_id) 245 download_url = self.download_plot_pattern.format(measurement_id)
164 self.download_files(measurement_id, 'scc_plots', download_url) 246 try:
165 247 self.download_files(measurement_id, 'scc_plots', download_url)
166 def rerun_processing(self, measurement_id, monitor=True): 248 except Exception as e:
249 logger.error("Could not download ELDA plots. Error message: {}".format(e))
250 logger.debug('Download exception:', exc_info=True)
251
252 def download_elic(self, measurement_id):
253 """ Download ELIC files for the measurement id. """
254 # Construct the download url
255 download_url = self.download_elic_pattern.format(measurement_id)
256 try:
257 self.download_files(measurement_id, 'scc_elic', download_url)
258 except Exception as e:
259 logger.error("Could not download ELIC files. Error message: {}".format(e))
260 logger.debug('Download exception:', exc_info=True)
261
262 def download_eldec(self, measurement_id):
263 """ Download ELDEC files for the measurement id. """
264 # Construct the download url
265 download_url = self.download_elda_pattern.format(measurement_id) # ELDA patter is used for now
266 try:
267 self.download_files(measurement_id, 'scc_eldec', download_url)
268 except Exception as e:
269 logger.error("Could not download EDELC files. Error message: {}".format(e))
270 logger.debug('Download exception:', exc_info=True)
271
272 def rerun_elpp(self, measurement_id, monitor=True):
167 measurement = self.get_measurement(measurement_id) 273 measurement = self.get_measurement(measurement_id)
168 274
169 if measurement: 275 if measurement:
170 request = self.session.get(measurement.rerun_processing_url, auth=self.auth, 276 request = self.session.get(measurement.rerun_elpp_url, auth=self.auth,
171 verify=False, 277 verify=False,
172 stream=True) 278 stream=True)
173 279
174 if request.status_code != 200: 280 if request.status_code != 200:
175 logger.error( 281 logger.error(
176 "Could not rerun processing for %s. Status code: %s" % (measurement_id, request.status_code)) 282 "Could not rerun ELPP for %s. Status code: %s" % (measurement_id, request.status_code))
177 return 283 return
178 284
179 if monitor: 285 if monitor:
180 self.monitor_processing(measurement_id) 286 self.monitor_processing(measurement_id)
181 287
198 return 304 return
199 305
200 if monitor: 306 if monitor:
201 self.monitor_processing(measurement_id) 307 self.monitor_processing(measurement_id)
202 308
203 def process(self, filename, system_id): 309 def process(self, filename, system_id, force_upload, delete_related):
204 """ Upload a file for processing and wait for the processing to finish. 310 """ Upload a file for processing and wait for the processing to finish.
205 If the processing is successful, it will download all produced files. 311 If the processing is successful, it will download all produced files.
206 """ 312 """
207 logger.info("--- Processing started on %s. ---" % datetime.datetime.now()) 313 logger.info("--- Processing started on %s. ---" % datetime.datetime.now())
314
208 # Upload file 315 # Upload file
209 measurement_id = self.upload_file(filename, system_id) 316 measurement_id = self.upload_file(filename, system_id, force_upload, delete_related)
210 317
211 measurement = self.monitor_processing(measurement_id) 318 measurement = self.monitor_processing(measurement_id)
212 return measurement 319 return measurement
213 320
214 def monitor_processing(self, measurement_id): 321 def monitor_processing(self, measurement_id):
215 """ Monitor the processing progress of a measurement id""" 322 """ Monitor the processing progress of a measurement id"""
216 323
217 measurement = self.get_measurement(measurement_id) 324 measurement = self.get_measurement(measurement_id)
218 if measurement is not None: 325 if measurement is not None:
219 while measurement.is_running: 326 while measurement.is_running:
220 logger.info("Measurement is being processed (status: %s, %s, %s). Please wait." % (measurement.upload, 327 logger.info("Measurement is being processed (status: {}, {}, {}, {}, {}, {}). Please wait.".format(
221 measurement.pre_processing, 328 measurement.upload,
222 measurement.processing)) 329 measurement.hirelpp,
330 measurement.cloudmask,
331 measurement.elpp,
332 measurement.elda,
333 measurement.elic))
223 time.sleep(10) 334 time.sleep(10)
224 measurement = self.get_measurement(measurement_id) 335 measurement = self.get_measurement(measurement_id)
225 logger.info("Measurement processing finished (status: %s, %s, %s)." % (measurement.upload, 336 logger.info("Measurement processing finished (status: {}, {}, {}, {}, {}, {}). Please wait.".format(
226 measurement.pre_processing, 337 measurement.upload,
227 measurement.processing)) 338 measurement.hirelpp,
228 if measurement.pre_processing == 127: 339 measurement.cloudmask,
229 logger.info("Downloading preprocessed files.") 340 measurement.elpp,
230 self.download_preprocessed(measurement_id) 341 measurement.elda,
231 if measurement.processing == 127: 342 measurement.elic))
232 logger.info("Downloading optical files.") 343 if measurement.hirelpp == 127:
233 self.download_optical(measurement_id) 344 logger.info("Downloading HiRElPP files.")
345 self.download_hirelpp(measurement_id)
346 if measurement.cloudmask == 127:
347 logger.info("Downloading cloud screening files.")
348 self.download_cloudmask(measurement_id)
349 if measurement.elpp == 127:
350 logger.info("Downloading ELPP files.")
351 self.download_elpp(measurement_id)
352 if measurement.elda == 127:
353 logger.info("Downloading ELDA files.")
354 self.download_elda(measurement_id)
234 logger.info("Downloading graphs.") 355 logger.info("Downloading graphs.")
235 self.download_graphs(measurement_id) 356 self.download_plots(measurement_id)
357 if measurement.elic == 127:
358 logger.info("Downloading ELIC files.")
359 self.download_elic(measurement_id)
360
361 # TODO: Need to check ELDEC code (when it becomes available in the API)
362 if measurement.is_calibration:
363 logger.info("Downloading ELDEC files.")
364 self.download_eldec(measurement_id)
365
236 logger.info("--- Processing finished. ---") 366 logger.info("--- Processing finished. ---")
237 return measurement 367 return measurement
238 368
239 def get_status(self, measurement_id): 369 def get_measurement(self, measurement_id):
240 """ Get the processing status for a measurement id through the API. """ 370
241 measurement_url = urlparse.urljoin(self.api_base_url, 'measurements/?id__exact=%s' % measurement_id) 371 if measurement_id is None:
372 return None
373
374 measurement_url = urlparse.urljoin(self.api_base_url, 'measurements/%s/' % measurement_id)
242 375
243 response = self.session.get(measurement_url, 376 response = self.session.get(measurement_url,
244 auth=self.auth, 377 auth=self.auth,
245 verify=False) 378 verify=False)
246 379
247 response_dict = response.json() 380 response_dict = None
248 381 if response.status_code == 200:
249 if response_dict['objects']: 382 response_dict = response.json()
250 measurement_list = response_dict['objects'] 383 if response.status_code == 404:
251 measurement = Measurement(self.base_url, measurement_list[0]) 384 logger.info("No measurement with id %s found on the SCC." % measurement_id)
252 return measurement.upload, measurement.pre_processing, measurement.processing 385 elif response.status_code != 200:
253 else:
254 logger.error("No measurement with id %s found on the SCC." % measurement_id)
255 return None
256
257 def get_measurement(self, measurement_id):
258 measurement_url = urlparse.urljoin(self.api_base_url, 'measurements/%s/' % measurement_id)
259
260 response = self.session.get(measurement_url,
261 auth=self.auth,
262 verify=False)
263
264 if response.status_code != 200:
265 logger.error('Could not access API. Status code %s.' % response.status_code) 386 logger.error('Could not access API. Status code %s.' % response.status_code)
266 sys.exit(1) 387 sys.exit(1)
267 388
268 response_dict = response.json() 389 logger.debug("Response dictionary: {}".format(response_dict))
269 390
270 if response_dict: 391 if response_dict:
271 measurement = Measurement(self.base_url,response_dict) 392 measurement = Measurement(self.base_url, response_dict)
272 return measurement 393 return measurement
273 else: 394 else:
274 logger.error("No measurement with id %s found on the SCC." % measurement_id)
275 return None 395 return None
276 396
277 def delete_measurement(self, measurement_id): 397 def delete_measurement(self, measurement_id, delete_related=False):
278 """ Deletes a measurement with the provided measurement id. The user 398 """ Deletes a measurement with the provided measurement id. The user
279 should have the appropriate permissions. 399 should have the appropriate permissions.
280 400
281 The procedures is performed directly through the web interface and 401 The procedures is performed directly through the web interface and
282 NOT through the API. 402 NOT through the API.
290 return None 410 return None
291 411
292 # Go the the page confirming the deletion 412 # Go the the page confirming the deletion
293 delete_url = self.delete_measurement_pattern.format(measurement.id) 413 delete_url = self.delete_measurement_pattern.format(measurement.id)
294 414
415 logger.debug("Delete url: {}".format(delete_url))
416
295 confirm_page = self.session.get(delete_url, 417 confirm_page = self.session.get(delete_url,
296 auth=self.auth, 418 auth=self.auth,
297 verify=False) 419 verify=False)
298 420
299 # Check that the page opened properly 421 # Check that the page opened properly
300 if confirm_page.status_code != 200: 422 if confirm_page.status_code != 200:
301 logger.warning("Could not open delete page. Status: {0}".format(confirm_page.status_code)) 423 logger.warning("Could not open delete page. Status: {0}".format(confirm_page.status_code))
302 return None 424 return None
303 425
426 # Get the delete related value
427 if delete_related:
428 delete_related_option = 'delete_related'
429 else:
430 delete_related_option = 'not_delete_related'
431
304 # Delete the measurement 432 # Delete the measurement
305 delete_page = self.session.post(delete_url, 433 delete_page = self.session.post(delete_url,
306 auth=self.auth, 434 auth=self.auth,
307 verify=False, 435 verify=False,
308 data={'post': 'yes'}, 436 data={'post': 'yes',
437 'select_delete_related_measurements': delete_related_option},
309 headers={'X-CSRFToken': confirm_page.cookies['csrftoken'], 438 headers={'X-CSRFToken': confirm_page.cookies['csrftoken'],
310 'referer': delete_url} 439 'referer': delete_url}
311 ) 440 )
312 if delete_page.status_code != 200: 441 if delete_page.status_code != 200:
313 logger.warning("Something went wrong. Delete page status: {0}".format( 442 logger.warning("Something went wrong. Delete page status: {0}".format(
314 delete_page.status_code)) 443 delete_page.status_code))
315 return None 444 return None
316 445
317 logger.info("Deleted measurement {0}".format(measurement_id)) 446 logger.info("Deleted measurement {0}.".format(measurement_id))
318 return True 447 return True
319 448
320 def available_measurements(self): 449 def available_measurements(self):
321 """ Get a list of available measurement on the SCC. """ 450 """ Get a list of available measurement on the SCC. """
322 measurement_url = urlparse.urljoin(self.api_base_url, 'measurements') 451 measurement_url = urlparse.urljoin(self.api_base_url, 'measurements')
323 response = self.session.get(measurement_url, 452 response = self.session.get(measurement_url,
324 auth=self.auth, 453 auth=self.auth,
325 verify=False) 454 verify=False)
326 response_dict = response.json() 455 response_dict = response.json()
327 456
328 measurements = None
329 if response_dict: 457 if response_dict:
330 measurement_list = response_dict['objects'] 458 measurement_list = response_dict['objects']
331 measurements = [Measurement(self.base_url, measurement_dict) for measurement_dict in measurement_list] 459 measurements = [Measurement(self.base_url, measurement_dict) for measurement_dict in measurement_list]
332 logger.info("Found %s measurements on the SCC." % len(measurements)) 460 logger.info("Found %s measurements on the SCC." % len(measurements))
333 else: 461 else:
462 measurements = None
334 logger.warning("No response received from the SCC when asked for available measurements.") 463 logger.warning("No response received from the SCC when asked for available measurements.")
335 464
336 return measurements 465 return measurements
337 466
338 def measurement_id_for_date(self, t1, call_sign='bu', base_number=0): 467 def measurement_id_for_date(self, t1, call_sign, base_number=0):
339 """ Give the first available measurement id on the SCC for the specific 468 """ Give the first available measurement id on the SCC for the specific
340 date. 469 date.
341 """ 470 """
342 date_str = t1.strftime('%Y%m%d') 471 date_str = t1.strftime('%Y%m%d')
343 search_url = urlparse.urljoin(self.api_base_url, 'measurements/?id__startswith=%s' % date_str) 472 search_url = urlparse.urljoin(self.api_base_url, 'measurements/?id__startswith=%s' % date_str)
353 if response_dict: 482 if response_dict:
354 measurement_list = response_dict['objects'] 483 measurement_list = response_dict['objects']
355 existing_ids = [measurement_dict['id'] for measurement_dict in measurement_list] 484 existing_ids = [measurement_dict['id'] for measurement_dict in measurement_list]
356 485
357 measurement_number = base_number 486 measurement_number = base_number
358 measurement_id = "%s%s%02i" % (date_str, call_sign, measurement_number) 487 measurement_id = "%s%s%04i" % (date_str, call_sign, measurement_number)
359 488
360 while measurement_id in existing_ids: 489 while measurement_id in existing_ids:
361 measurement_number = measurement_number + 1 490 measurement_number = measurement_number + 1
362 measurement_id = "%s%s%02i" % (date_str, call_sign, measurement_number) 491 measurement_id = "%s%s%04i" % (date_str, call_sign, measurement_number)
363 if measurement_number == 100: 492 if measurement_number == 1000:
364 raise ValueError('No available measurement id found.') 493 raise ValueError('No available measurement id found.')
365 494
366 return measurement_id 495 return measurement_id
367 496
368 497 def __enter__(self):
369 class ApiObject: 498 return self
370 """ A generic class object. """ 499
500 def __exit__(self, *args):
501 logger.debug("Closing SCC connection session.")
502 self.session.close()
503
504
505 class Measurement:
506 """ This class represents the measurement object as returned in the SCC API.
507 """
371 508
372 def __init__(self, base_url, dict_response): 509 def __init__(self, base_url, dict_response):
373 self.base_url = base_url 510 self.base_url = base_url
511
512 # Define expected attributes to assist debuggin
513 self.cloudmask = None
514 self.elda = None
515 self.elic = None
516 self.elpp = None
517 self.hirelpp = None
518 self.id = None
519 self.is_calibration = None
520 self.is_running = None
521 self.pre_processing_exit_code = None
522 self.processing_exit_code = None
523 self.resource_uri = None
524 self.start = None
525 self.stop = None
526 self.system = None
527 self.upload = None
374 528
375 if dict_response: 529 if dict_response:
376 # Add the dictionary key value pairs as object properties 530 # Add the dictionary key value pairs as object properties
377 for key, value in dict_response.items(): 531 for key, value in dict_response.items():
378 setattr(self, key, value) 532 setattr(self, key, value)
379 self.exists = True 533 self.exists = True
380 else: 534 else:
381 self.exists = False 535 self.exists = False
382 536
383
384 class Measurement(ApiObject):
385 """ This class represents the measurement object as returned in the SCC API.
386 """
387
388 @property 537 @property
389 def is_running(self): 538 def rerun_elda_url(self):
390 """ Returns True if the processing has not finished. 539 url_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/rerun-elda/')
391 """ 540 return url_pattern.format(self.id)
392 if self.upload == 0:
393 return False
394 if self.pre_processing == -127:
395 return False
396 if self.pre_processing == 127:
397 if self.processing in [127, -127]:
398 return False
399 return True
400 541
401 @property 542 @property
402 def rerun_processing_url(self): 543 def rerun_elpp_url(self):
403 url_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/rerun-optical/') 544 url_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/rerun-elpp/')
404 return url_pattern.format(self.id) 545 return url_pattern.format(self.id)
405 546
406 @property 547 @property
407 def rerun_all_url(self): 548 def rerun_all_url(self):
408 ulr_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/rerun-all/') 549 ulr_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/rerun-all/')
409 return ulr_pattern.format(self.id) 550 return ulr_pattern.format(self.id)
410 551
411 def __str__(self): 552 def __str__(self):
412 return "%s: %s, %s, %s" % (self.id, 553 return "Measurement {}".format(self.id)
413 self.upload, 554
414 self.pre_processing, 555
415 self.processing) 556 def upload_file(filename, system_id, force_upload, delete_related, settings):
416
417
418 def upload_file(filename, system_id, settings):
419 """ Shortcut function to upload a file to the SCC. """ 557 """ Shortcut function to upload a file to the SCC. """
420 logger.info("Uploading file %s, using sytem %s" % (filename, system_id)) 558 logger.info("Uploading file %s, using system %s." % (filename, system_id))
421 559
422 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) 560 with SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) as scc:
423 scc.login(settings['website_credentials']) 561 scc.login(settings['website_credentials'])
424 measurement_id = scc.upload_file(filename, system_id) 562 measurement_id = scc.upload_file(filename, system_id, force_upload, delete_related)
425 scc.logout() 563 scc.logout()
564
426 return measurement_id 565 return measurement_id
427 566
428 567
429 def process_file(filename, system_id, settings): 568 def process_file(filename, system_id, force_upload, delete_related, settings):
430 """ Shortcut function to process a file to the SCC. """ 569 """ Shortcut function to process a file to the SCC. """
431 logger.info("Processing file %s, using sytem %s" % (filename, system_id)) 570 logger.info("Processing file %s, using system %s." % (filename, system_id))
432 571
433 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) 572 with SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) as scc:
434 scc.login(settings['website_credentials']) 573 scc.login(settings['website_credentials'])
435 measurement = scc.process(filename, system_id) 574 measurement = scc.process(filename, system_id, force_upload, delete_related)
436 scc.logout() 575 scc.logout()
576
437 return measurement 577 return measurement
438 578
439 579
440 def delete_measurement(measurement_id, settings): 580 def delete_measurement(measurement_id, settings, delete_related):
441 """ Shortcut function to delete a measurement from the SCC. """ 581 """ Shortcut function to delete a measurement from the SCC. """
442 logger.info("Deleting %s" % measurement_id) 582 logger.info("Deleting %s." % measurement_id)
443 583
444 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) 584 with SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) as scc:
445 scc.login(settings['website_credentials']) 585 scc.login(settings['website_credentials'])
446 scc.delete_measurement(measurement_id) 586 scc.delete_measurement(measurement_id, delete_related)
447 scc.logout() 587 scc.logout()
448 588
449 589
450 def rerun_all(measurement_id, monitor, settings): 590 def rerun_all(measurement_id, monitor, settings):
451 """ Shortcut function to delete a measurement from the SCC. """ 591 """ Shortcut function to delete a measurement from the SCC. """
452 logger.info("Rerunning all products for %s" % measurement_id) 592 logger.info("Rerunning all products for %s" % measurement_id)
453 593
454 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) 594 with SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) as scc:
455 scc.login(settings['website_credentials']) 595 scc.login(settings['website_credentials'])
456 scc.rerun_all(measurement_id, monitor) 596 scc.rerun_all(measurement_id, monitor)
457 scc.logout() 597 scc.logout()
458 598
459 599
460 def rerun_processing(measurement_id, monitor, settings): 600 def rerun_elpp(measurement_id, monitor, settings):
461 """ Shortcut function to delete a measurement from the SCC. """ 601 """ Shortcut function to delete a measurement from the SCC. """
462 logger.info("Rerunning (optical) processing for %s" % measurement_id) 602 logger.info("Rerunning (optical) processing for %s" % measurement_id)
463 603
464 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) 604 with SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) as scc:
465 scc.login(settings['website_credentials']) 605 scc.login(settings['website_credentials'])
466 scc.rerun_processing(measurement_id, monitor) 606 scc.rerun_elpp(measurement_id, monitor)
467 scc.logout() 607 scc.logout()
468 608
469 609
470 def import_settings(config_file_path): 610 def import_settings(config_file_path):
471 """ Read the configuration file. 611 """ Read the configuration file.
472 612
478 618
479 with open(config_file_path) as yaml_file: 619 with open(config_file_path) as yaml_file:
480 try: 620 try:
481 settings = yaml.safe_load(yaml_file) 621 settings = yaml.safe_load(yaml_file)
482 logger.debug("Read settings file(%s)" % config_file_path) 622 logger.debug("Read settings file(%s)" % config_file_path)
483 except: 623 except Exception as e:
484 logger.error("Could not parse YAML file (%s)" % config_file_path) 624 logger.error("Could not parse YAML file (%s)" % config_file_path)
625 logger.debug("Error message: {}".format(e))
485 sys.exit(1) 626 sys.exit(1)
486 627
487 # YAML limitation: does not read tuples 628 # YAML limitation: does not read tuples
488 settings['basic_credentials'] = tuple(settings['basic_credentials']) 629 settings['basic_credentials'] = tuple(settings['basic_credentials'])
489 settings['website_credentials'] = tuple(settings['website_credentials']) 630 settings['website_credentials'] = tuple(settings['website_credentials'])
497 parser.add_argument("filename", nargs='?', help="Measurement file name or path.", default='') 638 parser.add_argument("filename", nargs='?', help="Measurement file name or path.", default='')
498 parser.add_argument("system", nargs='?', help="Processing system id.", default=0) 639 parser.add_argument("system", nargs='?', help="Processing system id.", default=0)
499 parser.add_argument("-p", "--process", help="Wait for the results of the processing.", 640 parser.add_argument("-p", "--process", help="Wait for the results of the processing.",
500 action="store_true") 641 action="store_true")
501 parser.add_argument("--delete", help="Measurement ID to delete.") 642 parser.add_argument("--delete", help="Measurement ID to delete.")
502 parser.add_argument("--rerun-all", help="Measurement ID to rerun.") 643 # parser.add_argument("--delete_related", help=
503 parser.add_argument("--rerun-processing", help="Measurement ID to rerun processing routines.") 644 # "Delete all related measurements. Use only if you know what you are doing!",
645 # action="store_true")
646 parser.add_argument("--force_upload", help="If measurement ID exists on SCC, delete before uploading.",
647 action="store_true")
648 parser.add_argument("--rerun-all", help="Rerun all processing steps for the provided measurement ID.")
649 parser.add_argument("--rerun-elpp", help="Rerun low-resolution processing steps for the provided measurement ID.")
504 650
505 # Verbosity settings from http://stackoverflow.com/a/20663028 651 # Verbosity settings from http://stackoverflow.com/a/20663028
506 parser.add_argument('-d', '--debug', help="Print debugging information.", action="store_const", 652 parser.add_argument('-d', '--debug', help="Print debugging information.", action="store_const",
507 dest="loglevel", const=logging.DEBUG, default=logging.INFO, 653 dest="loglevel", const=logging.DEBUG, default=logging.INFO,
508 ) 654 )
510 dest="loglevel", const=logging.WARNING 656 dest="loglevel", const=logging.WARNING
511 ) 657 )
512 658
513 args = parser.parse_args() 659 args = parser.parse_args()
514 660
661 # For now, don to allow to delete related measurements
662 delete_related = False
663
515 # Get the logger with the appropriate level 664 # Get the logger with the appropriate level
516 logging.basicConfig(format='%(levelname)s: %(message)s', level=args.loglevel) 665 logging.basicConfig(format='%(levelname)s: %(message)s', level=args.loglevel)
517 666
518 settings = import_settings(args.config) 667 settings = import_settings(args.config)
519 668
520 # If the arguments are OK, try to log-in to SCC and upload. 669 # If the arguments are OK, try to log-in to SCC and upload.
521 if args.delete: 670 if args.delete:
522 # If the delete is provided, do nothing else 671 # If the delete is provided, do nothing else
523 delete_measurement(args.delete, settings) 672 delete_measurement(args.delete, settings, delete_related)
524 elif args.rerun_all: 673 elif args.rerun_all:
525 rerun_all(args.rerun_all, args.process, settings) 674 rerun_all(args.rerun_all, args.process, settings)
526 elif args.rerun_processing: 675 elif args.rerun_elpp:
527 rerun_processing(args.rerun_processing, args.process, settings) 676 rerun_elpp(args.rerun_elpp, args.process, settings)
528 else: 677 else:
529 if (args.filename == '') or (args.system == 0): 678 if (args.filename == '') or (args.system == 0):
530 parser.error('Provide a valid filename and system parameters.\nRun with -h for help.\n') 679 parser.error('Provide a valid filename and system parameters.\nRun with -h for help.\n')
531 680
532 if args.process: 681 if args.process:
533 process_file(args.filename, args.system, settings) 682 process_file(args.filename, args.system, args.force_upload, delete_related, settings)
534 else: 683 else:
535 upload_file(args.filename, args.system, settings) 684 upload_file(args.filename, args.system, args.force_upload, delete_related, settings)

mercurial