38 version no check is performed, and no feedback is given if the upload |
33 version no check is performed, and no feedback is given if the upload |
39 was successful. If everything is setup correctly, it will work. |
34 was successful. If everything is setup correctly, it will work. |
40 """ |
35 """ |
41 |
36 |
42 def __init__(self, auth, output_dir, base_url): |
37 def __init__(self, auth, output_dir, base_url): |
|
38 |
43 self.auth = auth |
39 self.auth = auth |
44 self.output_dir = output_dir |
40 self.output_dir = output_dir |
45 self.base_url = base_url |
41 self.base_url = base_url |
46 self.session = requests.Session() |
42 self.session = requests.Session() |
47 self.construct_urls() |
43 self.session.auth = auth |
48 |
44 self.session.verify = False |
49 def construct_urls(self): |
|
50 """ Construct all URLs needed for processing. """ |
|
51 # Construct the absolute URLs |
|
52 self.login_url = urlparse.urljoin(self.base_url, 'accounts/login/') |
45 self.login_url = urlparse.urljoin(self.base_url, 'accounts/login/') |
53 self.upload_url = urlparse.urljoin(self.base_url, 'data_processing/measurements/quick/') |
46 self.upload_url = urlparse.urljoin(self.base_url, 'data_processing/measurements/quick/') |
54 self.download_preprocessed_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/download-preprocessed/') |
47 self.download_preprocessed_pattern = urlparse.urljoin(self.base_url, |
55 self.download_optical_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/download-optical/') |
48 'data_processing/measurements/{0}/download-preprocessed/') |
56 self.download_graph_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/{0}/download-plots/') |
49 self.download_optical_pattern = urlparse.urljoin(self.base_url, |
|
50 'data_processing/measurements/{0}/download-optical/') |
|
51 self.download_graph_pattern = urlparse.urljoin(self.base_url, |
|
52 'data_processing/measurements/{0}/download-plots/') |
57 self.delete_measurement_pattern = urlparse.urljoin(self.base_url, 'admin/database/measurements/{0}/delete/') |
53 self.delete_measurement_pattern = urlparse.urljoin(self.base_url, 'admin/database/measurements/{0}/delete/') |
58 self.api_base_url = urlparse.urljoin(self.base_url, 'api/v1/') |
54 self.api_base_url = urlparse.urljoin(self.base_url, 'api/v1/') |
|
55 self.list_measurements_pattern = urlparse.urljoin(self.base_url, 'data_processing/measurements/') |
59 |
56 |
60 def login(self, credentials): |
57 def login(self, credentials): |
61 """ Login the the website. """ |
58 """ Login the the website. """ |
62 logger.debug("Attempting to login to SCC, username %s." % credentials[0]) |
59 logger.debug("Attempting to login to SCC, username %s." % credentials[0]) |
63 self.login_credentials = {'username': credentials[0], |
60 login_credentials = {'username': credentials[0], |
64 'password': credentials[1]} |
61 'password': credentials[1]} |
65 |
62 |
66 logger.debug("Accessing login page at %s." % self.login_url) |
63 logger.debug("Accessing login page at %s." % self.login_url) |
67 |
64 |
68 # Get upload form |
65 # Get upload form |
69 login_page = self.session.get(self.login_url, auth=self.auth, verify=False) |
66 login_page = self.session.get(self.login_url) |
70 |
67 |
|
68 # TODO: Do we need this? Mortiz removed it. |
71 if login_page.status_code != 200: |
69 if login_page.status_code != 200: |
72 logger.error('Could not access login pages. Status code %s' % login_page.status_code) |
70 logger.error('Could not access login pages. Status code %s' % login_page.status_code) |
73 sys.exit(1) |
71 sys.exit(1) |
74 |
72 |
75 logger.debug("Submiting credentials.") |
73 logger.debug("Submiting credentials.") |
76 |
|
77 # Submit the login data |
74 # Submit the login data |
78 login_submit = self.session.post(self.login_url, |
75 login_submit = self.session.post(self.login_url, |
79 data=self.login_credentials, |
76 data=login_credentials, |
80 headers={'X-CSRFToken': login_page.cookies['csrftoken'], |
77 headers={'X-CSRFToken': login_page.cookies['csrftoken'], |
81 'referer': self.login_url}, |
78 'referer': self.login_url}) |
82 verify=False, |
|
83 auth=self.auth) |
|
84 return login_submit |
79 return login_submit |
85 |
80 |
86 def logout(self): |
81 def logout(self): |
87 pass |
82 pass |
88 |
83 |
89 def upload_file(self, filename, system_id, rs_filename=None): |
84 def upload_file(self, filename, system_id, rs_filename=None): |
90 """ Upload a filename for processing with a specific system. If the |
85 """ Upload a filename for processing with a specific system. If the |
91 upload is successful, it returns the measurement id. """ |
86 upload is successful, it returns the measurement id. """ |
92 # Get submit page |
87 # Get submit page |
93 upload_page = self.session.get(self.upload_url, |
88 upload_page = self.session.get(self.upload_url) |
94 auth=self.auth, |
|
95 verify=False) |
|
96 |
89 |
97 # Submit the data |
90 # Submit the data |
98 upload_data = {'system': system_id} |
91 upload_data = {'system': system_id} |
99 files = {'data': open(filename, 'rb')} |
92 files = {'data': open(filename, 'rb')} |
100 |
93 |
478 measurement = scc.process(filename, system_id, rs_filename=rs_filename) |
484 measurement = scc.process(filename, system_id, rs_filename=rs_filename) |
479 scc.logout() |
485 scc.logout() |
480 return measurement |
486 return measurement |
481 |
487 |
482 |
488 |
483 def delete_measurement(measurement_id, settings): |
489 def delete_measurement(measurement_ids, settings): |
484 """ Shortcut function to delete a measurement from the SCC. """ |
490 """ Shortcut function to delete measurements from the SCC. """ |
485 logger.info("Deleting %s" % measurement_id) |
|
486 |
|
487 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) |
491 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) |
488 scc.login(settings['website_credentials']) |
492 scc.login(settings['website_credentials']) |
489 scc.delete_measurement(measurement_id) |
493 for m_id in measurement_ids: |
|
494 logger.info("Deleting %s" % m_id) |
|
495 scc.delete_measurement(m_id) |
490 scc.logout() |
496 scc.logout() |
491 |
497 |
492 |
498 |
493 def rerun_all(measurement_id, monitor, settings): |
499 def rerun_all(measurement_ids, monitor, settings): |
494 """ Shortcut function to delete a measurement from the SCC. """ |
500 """ Shortcut function to rerun measurements from the SCC. """ |
495 logger.info("Rerunning all products for %s" % measurement_id) |
|
496 |
501 |
497 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) |
502 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) |
498 scc.login(settings['website_credentials']) |
503 scc.login(settings['website_credentials']) |
499 scc.rerun_all(measurement_id, monitor) |
504 for m_id in measurement_ids: |
|
505 logger.info("Rerunning all products for %s" % m_id) |
|
506 scc.rerun_all(m_id, monitor) |
500 scc.logout() |
507 scc.logout() |
501 |
508 |
502 |
509 |
503 def rerun_processing(measurement_id, monitor, settings): |
510 def rerun_processing(measurement_ids, monitor, settings): |
504 """ Shortcut function to delete a measurement from the SCC. """ |
511 """ Shortcut function to delete a measurement from the SCC. """ |
505 logger.info("Rerunning (optical) processing for %s" % measurement_id) |
|
506 |
512 |
507 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) |
513 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) |
508 scc.login(settings['website_credentials']) |
514 scc.login(settings['website_credentials']) |
509 scc.rerun_processing(measurement_id, monitor) |
515 for m_id in measurement_ids: |
|
516 logger.info("Rerunning (optical) processing for %s" % m_id) |
|
517 scc.rerun_processing(m_id, monitor) |
510 scc.logout() |
518 scc.logout() |
511 |
519 |
512 |
520 |
513 def import_settings(config_file_path): |
521 def list_measurements(settings, station=None, system=None, start=None, stop=None, upload_status=None, |
|
522 preprocessing_status=None, |
|
523 optical_processing=None): |
|
524 """List all available measurements""" |
|
525 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) |
|
526 scc.login(settings['website_credentials']) |
|
527 ret = scc.list_measurements(station=station, system=system, start=start, stop=stop, upload_status=upload_status, |
|
528 processing_status=preprocessing_status, optical_processing=optical_processing) |
|
529 for entry in ret: |
|
530 print("%s" % entry.id) |
|
531 scc.logout() |
|
532 |
|
533 |
|
534 def download_measurements(measurement_ids, download_preproc, download_optical, download_graph, settings): |
|
535 """Download all measurements for the specified IDs""" |
|
536 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) |
|
537 scc.login(settings['website_credentials']) |
|
538 for m_id in measurement_ids: |
|
539 if download_preproc: |
|
540 logger.info("Downloading preprocessed files for '%s'" % m_id) |
|
541 scc.download_preprocessed(m_id) |
|
542 logger.info("Complete") |
|
543 if download_optical: |
|
544 logger.info("Downloading optical files for '%s'" % m_id) |
|
545 scc.download_optical(m_id) |
|
546 logger.info("Complete") |
|
547 if download_graph: |
|
548 logger.info("Downloading profile graph files for '%s'" % m_id) |
|
549 scc.download_graphs(m_id) |
|
550 logger.info("Complete") |
|
551 |
|
552 |
|
553 def settings_from_path(config_file_path): |
514 """ Read the configuration file. |
554 """ Read the configuration file. |
515 |
555 |
516 The file should be in YAML syntax.""" |
556 The file should be in YAML syntax.""" |
517 |
557 |
518 if not os.path.isfile(config_file_path): |
558 if not os.path.isfile(config_file_path): |
519 logger.error("Wrong path for configuration file (%s)" % config_file_path) |
559 raise argparse.ArgumentTypeError("Wrong path for configuration file (%s)" % config_file_path) |
520 sys.exit(1) |
|
521 |
560 |
522 with open(config_file_path) as yaml_file: |
561 with open(config_file_path) as yaml_file: |
523 try: |
562 try: |
524 settings = yaml.safe_load(yaml_file) |
563 settings = yaml.safe_load(yaml_file) |
525 logger.debug("Read settings file(%s)" % config_file_path) |
564 logger.debug("Read settings file(%s)" % config_file_path) |
526 except: |
565 except Exception: |
527 logger.error("Could not parse YAML file (%s)" % config_file_path) |
566 raise argparse.ArgumentTypeError("Could not parse YAML file (%s)" % config_file_path) |
528 sys.exit(1) |
|
529 |
567 |
530 # YAML limitation: does not read tuples |
568 # YAML limitation: does not read tuples |
531 settings['basic_credentials'] = tuple(settings['basic_credentials']) |
569 settings['basic_credentials'] = tuple(settings['basic_credentials']) |
532 settings['website_credentials'] = tuple(settings['website_credentials']) |
570 settings['website_credentials'] = tuple(settings['website_credentials']) |
533 return settings |
571 return settings |
534 |
572 |
535 |
573 |
|
574 # Setup for command specific parsers |
|
575 def setup_delete(parser): |
|
576 def delete_from_args(parsed): |
|
577 delete_measurement(parsed.IDs, parsed.config) |
|
578 |
|
579 parser.add_argument("IDs", nargs="+", help="measurement IDs to delete.") |
|
580 parser.set_defaults(execute=delete_from_args) |
|
581 |
|
582 |
|
583 def setup_rerun_all(parser): |
|
584 def rerun_all_from_args(parsed): |
|
585 rerun_all(parsed.IDs, parsed.process, parsed.config) |
|
586 |
|
587 parser.add_argument("IDs", nargs="+", help="Measurement IDs to rerun.") |
|
588 parser.add_argument("-p", "--process", help="Wait for the results of the processing.", |
|
589 action="store_true") |
|
590 parser.set_defaults(execute=rerun_all_from_args) |
|
591 |
|
592 |
|
593 def setup_rerun_processing(parser): |
|
594 def rerun_processing_from_args(parsed): |
|
595 rerun_processing(parsed.IDs, parsed.process, parsed.config) |
|
596 |
|
597 parser.add_argument("IDs", nargs="+", help="Measurement IDs to rerun the processing on.") |
|
598 parser.add_argument("-p", "--process", help="Wait for the results of the processing.", |
|
599 action="store_true") |
|
600 parser.set_defaults(execute=rerun_processing_from_args) |
|
601 |
|
602 |
|
603 def setup_process_file(parser): |
|
604 def process_file_from_args(parsed): |
|
605 process_file(parsed.file, parsed.system, parsed.process, parsed.config, parsed.radiosounding) |
|
606 |
|
607 parser.add_argument("filename", help="Measurement file name or path.") |
|
608 parser.add_argument("system", help="Processing system id.") |
|
609 parser.add_argument("--radiosounding", default=None, help="Radiosounding file name or path") |
|
610 parser.add_argument("-p", "--process", help="Wait for the results of the processing.", |
|
611 action="store_true") |
|
612 parser.set_defaults(execute=process_file_from_args) |
|
613 |
|
614 |
|
615 def setup_upload_file(parser): |
|
616 def upload_file_from_args(parsed): |
|
617 upload_file(parsed.file, parsed.system, parsed.config, parsed.radiosounding) |
|
618 |
|
619 parser.add_argument("filename", help="Measurement file name or path.") |
|
620 parser.add_argument("system", help="Processing system id.") |
|
621 parser.add_argument("--radiosounding", default=None, help="Radiosounding file name or path") |
|
622 |
|
623 parser.set_defaults(execute=upload_file_from_args) |
|
624 |
|
625 |
|
626 def setup_list_measurements(parser): |
|
627 def list_measurements_from_args(parsed): |
|
628 list_measurements(parsed.config, station=parsed.station, system=parsed.system, start=parsed.start, |
|
629 stop=parsed.stop, |
|
630 upload_status=parsed.upload_status, preprocessing_status=parsed.preprocessing_status, |
|
631 optical_processing=parsed.optical_processing_status) |
|
632 |
|
633 def status(arg): |
|
634 if -127 <= int(arg) <= 127: |
|
635 return arg |
|
636 else: |
|
637 raise argparse.ArgumentTypeError("Status must be between -127 and 127") |
|
638 |
|
639 def date(arg): |
|
640 if re.match(r'\d{4}-\d{2}-\d{2}', arg): |
|
641 return arg |
|
642 else: |
|
643 raise argparse.ArgumentTypeError("Date must be in format 'YYYY-MM-DD'") |
|
644 |
|
645 parser.add_argument("--station", help="Filter for only the selected station") |
|
646 parser.add_argument("--system", help="Filter for only the selected station") |
|
647 parser.add_argument("--start", help="Filter for only the selected station", type=date) |
|
648 parser.add_argument("--stop", help="Filter for only the selected station", type=date) |
|
649 parser.add_argument("--upload-status", help="Filter for only the selected station", type=status) |
|
650 parser.add_argument("--preprocessing-status", help="Filter for only the selected station", type=status) |
|
651 parser.add_argument("--optical-processing-status", help="Filter for only the selected station", type=status) |
|
652 parser.set_defaults(execute=list_measurements_from_args) |
|
653 |
|
654 |
|
655 def setup_download_measurements(parser): |
|
656 def download_measurements_from_args(parsed): |
|
657 preproc = parsed.download_preprocessed |
|
658 optical = parsed.download_optical |
|
659 graphs = parsed.download_profile_graphs |
|
660 if not preproc and not graphs: |
|
661 optical = True |
|
662 download_measurements(parsed.IDs, preproc, optical, graphs, parsed.config) |
|
663 |
|
664 parser.add_argument("IDs", help="Measurement IDs that should be downloaded.", nargs="+") |
|
665 parser.add_argument("--download-preprocessed", action="store_true", help="Download preprocessed files.") |
|
666 parser.add_argument("--download-optical", action="store_true", |
|
667 help="Download optical files (default if no other download is used).") |
|
668 parser.add_argument("--download-profile-graphs", action="store_true", help="Download profile graph files.") |
|
669 parser.set_defaults(execute=download_measurements_from_args) |
|
670 |
|
671 |
536 def main(): |
672 def main(): |
537 # Define the command line arguments. |
673 # Define the command line arguments. |
538 parser = argparse.ArgumentParser() |
674 parser = argparse.ArgumentParser() |
539 parser.add_argument("config", help="Path to configuration file") |
675 subparsers = parser.add_subparsers() |
540 parser.add_argument("filename", nargs='?', help="Measurement file name or path.", default='') |
676 |
541 parser.add_argument("system", nargs='?', help="Processing system id.", default=0) |
677 delete_parser = subparsers.add_parser("delete", help="Deletes a measurement.") |
542 parser.add_argument("-p", "--process", help="Wait for the results of the processing.", |
678 rerun_all_parser = subparsers.add_parser("rerun-all", help="Rerun a measurement.") |
543 action="store_true") |
679 rerun_processing_parser = subparsers.add_parser("rerun-processing", |
544 parser.add_argument("--delete", help="Measurement ID to delete.") |
680 help="Rerun processing routings for a measurement.") |
545 parser.add_argument("--rerun-all", help="Measurement ID to rerun.") |
681 process_file_parser = subparsers.add_parser("process-file", help="Process a file.") |
546 parser.add_argument("--rerun-processing", help="Measurement ID to rerun processing routines.") |
682 upload_file_parser = subparsers.add_parser("upload-file", help="Upload a file.") |
547 |
683 list_parser = subparsers.add_parser("list", help="List all measurements.") |
548 # others files |
684 download_parser = subparsers.add_parser("download", help="Download selected measurements.") |
549 parser.add_argument("--radiosounding", default=None, help="Radiosounding file name or path") |
685 |
|
686 setup_delete(delete_parser) |
|
687 setup_rerun_all(rerun_all_parser) |
|
688 setup_rerun_processing(rerun_processing_parser) |
|
689 setup_process_file(process_file_parser) |
|
690 setup_upload_file(upload_file_parser) |
|
691 setup_list_measurements(list_parser) |
|
692 setup_download_measurements(download_parser) |
550 |
693 |
551 # Verbosity settings from http://stackoverflow.com/a/20663028 |
694 # Verbosity settings from http://stackoverflow.com/a/20663028 |
552 parser.add_argument('-d', '--debug', help="Print debugging information.", action="store_const", |
695 parser.add_argument('-d', '--debug', help="Print debugging information.", action="store_const", |
553 dest="loglevel", const=logging.DEBUG, default=logging.INFO, |
696 dest="loglevel", const=logging.DEBUG, default=logging.INFO, |
554 ) |
697 ) |
555 parser.add_argument('-s', '--silent', help="Show only warning and error messages.", action="store_const", |
698 parser.add_argument('-s', '--silent', help="Show only warning and error messages.", action="store_const", |
556 dest="loglevel", const=logging.WARNING |
699 dest="loglevel", const=logging.WARNING |
557 ) |
700 ) |
558 |
701 |
|
702 home = os.path.expanduser("~") |
|
703 default_config_location = os.path.abspath(os.path.join(home, ".scc_access.yaml")) |
|
704 parser.add_argument("-c", "--config", help="Path to the config file.", type=settings_from_path, |
|
705 default=default_config_location) |
|
706 |
559 args = parser.parse_args() |
707 args = parser.parse_args() |
560 |
708 |
561 # Get the logger with the appropriate level |
709 # Get the logger with the appropriate level |
562 logging.basicConfig(format='%(levelname)s: %(message)s', level=args.loglevel) |
710 logging.basicConfig(format='%(levelname)s: %(message)s', level=args.loglevel) |
563 |
711 |
564 settings = import_settings(args.config) |
712 # Dispatch to appropriate function |
565 |
713 args.execute(args) |
566 # If the arguments are OK, try to log-in to SCC and upload. |
714 |
567 if args.delete: |
715 |
568 # If the delete is provided, do nothing else |
716 # When running through terminal |
569 delete_measurement(args.delete, settings) |
717 if __name__ == '__main__': |
570 elif args.rerun_all: |
718 main() |
571 rerun_all(args.rerun_all, args.process, settings) |
|
572 elif args.rerun_processing: |
|
573 rerun_processing(args.rerun_processing, args.process, settings) |
|
574 else: |
|
575 if (args.filename == '') or (args.system == 0): |
|
576 parser.error('Provide a valid filename and system parameters.\nRun with -h for help.\n') |
|
577 |
|
578 if args.process: |
|
579 process_file(args.filename, args.system, settings, rs_filename=args.radiosounding) |
|
580 else: |
|
581 upload_file(args.filename, args.system, settings, rs_filename=args.radiosounding) |
|