source: flex_extract.git/Source/Python/Mods/tools.py @ c77630a

ctbtodev
Last change on this file since c77630a was c77630a, checked in by Anne Philipp <anne.philipp@…>, 5 years ago

BUGFIX: for python3-eccodes the file openings need to be in binary mode! added binary marker in filemode

  • Property mode set to 100644
File size: 27.6 KB
Line 
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3#*******************************************************************************
4# @Author: Anne Philipp (University of Vienna)
5#
6# @Date: May 2018
7#
8# @Change History:
9#    October 2014 - Anne Fouilloux (University of Oslo)
10#        - created functions silent_remove and product (taken from ECMWF)
11#
12#    November 2015 - Leopold Haimberger (University of Vienna)
13#        - created functions: interpret_args_and_control, clean_up
14#          my_error, normal_exit, init128, to_param_id
15#
16#    April - December 2018 - Anne Philipp (University of Vienna):
17#        - applied PEP8 style guide
18#        - added documentation
19#        - moved all non class methods from former file Flexparttools in here
20#        - seperated args and control interpretation
21#        - added functions get_list_as_string, read_ecenv, send_mail, make_dir,
22#          put_file_to_ecserver, submit_job_to_ecserver, get_informations,
23#          get_dimensions, execute_subprocess, none_or_int, none_or_str
24#
25# @License:
26#    (C) Copyright 2014-2019.
27#    Anne Philipp, Leopold Haimberger
28#
29#    SPDX-License-Identifier: CC-BY-4.0
30#
31#    This work is licensed under the Creative Commons Attribution 4.0
32#    International License. To view a copy of this license, visit
33#    http://creativecommons.org/licenses/by/4.0/ or send a letter to
34#    Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
35#
36# @Methods:
37#    none_or_str
38#    none_or_int
39#    get_cmdline_args
40#    read_ecenv
41#    clean_up
42#    my_error
43#    send_mail
44#    normal_exit
45#    product
46#    silent_remove
47#    init128
48#    to_param_id
49#    get_list_as_string
50#    make_dir
51#    put_file_to_ecserver
52#    submit_job_to_ecserver
53#    get_informations
54#    get_dimensions
55#    execute_subprocess
56#*******************************************************************************
57'''This module contains a collection of diverse tasks within flex_extract.
58'''
59
60# ------------------------------------------------------------------------------
61# MODULES
62# ------------------------------------------------------------------------------
63from __future__ import print_function
64
65import os
66import errno
67import sys
68import glob
69import subprocess
70import traceback
71try:
72    import exceptions
73except ImportError:
74    import builtins as exceptions
75from datetime import datetime, timedelta
76from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
77
78# ------------------------------------------------------------------------------
79# METHODS
80# ------------------------------------------------------------------------------
81
82def setup_controldata():
83    '''Collects, stores and checks controlling arguments from command line,
84    CONTROL file and ECMWF_ENV file.
85
86    Parameters
87    ----------
88
89    Return
90    ------
91    c : ControlFile
92        Contains all the parameters of CONTROL file and
93        command line.
94
95    ppid : str
96        Parent process id.
97
98    queue : str
99        Name of queue for submission to ECMWF (e.g. ecgate or cca )
100
101    job_template : str
102        Name of the job template file for submission to ECMWF server.
103    '''
104    import _config
105    from Classes.ControlFile import ControlFile
106
107    args = get_cmdline_args()
108    c = ControlFile(args.controlfile)
109    c.assign_args_to_control(args)
110    if os.path.isfile(_config.PATH_ECMWF_ENV):
111        env_parameter = read_ecenv(_config.PATH_ECMWF_ENV)
112        c.assign_envs_to_control(env_parameter)
113    c.check_conditions(args.queue)
114
115    return c, args.ppid, args.queue, args.job_template
116
117def none_or_str(value):
118    '''Converts the input string into pythons None-type if the string
119    contains string "None".
120
121    Parameters
122    ----------
123    value : str
124        String to be checked for the "None" word.
125
126    Return
127    ------
128    None or value:
129        Return depends on the content of the input value. If it was "None",
130        then the python type None is returned. Otherwise the string itself.
131    '''
132    if value == 'None':
133        return None
134    return value
135
136def none_or_int(value):
137    '''Converts the input string into pythons None-type if the string
138    contains string "None". Otherwise it is converted to an integer value.
139
140    Parameters
141    ----------
142    value : str
143        String to be checked for the "None" word.
144
145    Return
146    ------
147    None or int(value):
148        Return depends on the content of the input value. If it was "None",
149        then the python type None is returned. Otherwise the string is
150        converted into an integer value.
151    '''
152    if value == 'None':
153        return None
154    return int(value)
155
156def get_cmdline_args():
157    '''Decomposes the command line arguments and assigns them to variables.
158    Apply default values for non mentioned arguments.
159
160    Parameters
161    ----------
162
163    Return
164    ------
165    args : Namespace
166        Contains the commandline arguments from script/program call.
167    '''
168
169    parser = ArgumentParser(description='Retrieve FLEXPART input from \
170                                ECMWF MARS archive',
171                            formatter_class=ArgumentDefaultsHelpFormatter)
172
173    # control parameters that override control file values
174    parser.add_argument("--start_date", dest="start_date",
175                        type=none_or_str, default=None,
176                        help="start date YYYYMMDD")
177    parser.add_argument("--end_date", dest="end_date",
178                        type=none_or_str, default=None,
179                        help="end_date YYYYMMDD")
180    parser.add_argument("--date_chunk", dest="date_chunk",
181                        type=none_or_int, default=None,
182                        help="# of days to be retrieved at once")
183    parser.add_argument("--job_chunk", dest="job_chunk",
184                        type=none_or_int, default=None,
185                        help="# of days to be retrieved within a single job")
186    parser.add_argument("--controlfile", dest="controlfile",
187                        type=none_or_str, default='CONTROL_EA5',
188                        help="The file with all CONTROL parameters.")
189    parser.add_argument("--basetime", dest="basetime",
190                        type=none_or_int, default=None,
191                        help="base such as 0 or 12 (for half day retrievals)")
192    parser.add_argument("--step", dest="step",
193                        type=none_or_str, default=None,
194                        help="Forecast steps such as 00/to/48")
195    parser.add_argument("--levelist", dest="levelist",
196                        type=none_or_str, default=None,
197                        help="Vertical levels to be retrieved, e.g. 30/to/60")
198    parser.add_argument("--area", dest="area",
199                        type=none_or_str, default=None,
200                        help="area defined as north/west/south/east")
201
202    # some switches
203    parser.add_argument("--debug", dest="debug",
204                        type=none_or_int, default=None,
205                        help="debug mode - leave temporary files intact")
206    parser.add_argument("--oper", dest="oper",
207                        type=none_or_int, default=None,
208                        help='operational mode - prepares dates with '
209                        'environment variables')
210    parser.add_argument("--request", dest="request",
211                        type=none_or_int, default=None,
212                        help="list all mars requests in file mars_requests.dat")
213    parser.add_argument("--public", dest="public",
214                        type=none_or_int, default=None,
215                        help="public mode - retrieves the public datasets")
216    parser.add_argument("--rrint", dest="rrint",
217                        type=none_or_int, default=None,
218                        help='Selection of old or new precipitation '
219                        'interpolation:\n'
220                        '     0 - old method\n'
221                        '     1 - new method (additional subgrid points)')
222
223    # set directories
224    parser.add_argument("--inputdir", dest="inputdir",
225                        type=none_or_str, default=None,
226                        help='Path to the temporary directory for the '
227                        'retrieval grib files and other processing files.')
228    parser.add_argument("--outputdir", dest="outputdir",
229                        type=none_or_str, default=None,
230                        help='Path to the final directory where the final '
231                        'FLEXPART ready input files are stored.')
232
233    # this is only used by prepare_flexpart.py to rerun a postprocessing step
234    parser.add_argument("--ppid", dest="ppid",
235                        type=none_or_str, default=None,
236                        help='This is the specify parent process id of a '
237                        'single flex_extract run to identify the files. '
238                        'It is the second number in the GRIB files.')
239
240    # arguments for job submission to ECMWF, only needed by submit.py
241    parser.add_argument("--job_template", dest='job_template',
242                        type=none_or_str, default="job.temp",
243                        help='The job template file which are adapted to be '
244                        'submitted to the batch system on ECMWF server.')
245    parser.add_argument("--queue", dest="queue",
246                        type=none_or_str, default=None,
247                        help='The ECMWF server name for submission of the '
248                        'job script to the batch system '
249                        '(e.g. ecgate | cca | ccb)')
250
251    args = parser.parse_args()
252
253    return args
254
255def read_ecenv(filepath):
256    '''Reads the file into a dictionary where the key values are the parameter
257    names.
258
259    Parameters
260    ----------
261    filepath : str
262        Path to file where the ECMWF environment parameters are stored.
263
264    Return
265    ------
266    envs : dict
267        Contains the environment parameter ecuid, ecgid, gateway
268        and destination for ECMWF server environments.
269    '''
270    envs= {}
271    try:
272        with open(filepath, 'r') as f:
273            for line in f:
274                data = line.strip().split()
275                envs[str(data[0])] = str(data[1])
276    except OSError as e:
277        print('... ERROR CODE: ' + str(e.errno))
278        print('... ERROR MESSAGE:\n \t ' + str(e.strerror))
279
280        sys.exit('\n... Error occured while trying to read ECMWF_ENV '
281                     'file: ' + str(filepath))
282
283    return envs
284
285def clean_up(c):
286    '''Remove files from the intermediate directory (inputdir).
287
288    It keeps the final FLEXPART input files if program runs without
289    ECMWF Api and keywords "ectrans" or "ecstorage" are set to "1".
290
291    Parameters
292    ----------
293    c : ControlFile
294        Contains all the parameters of CONTROL file and
295        command line.
296
297    Return
298    ------
299
300    '''
301
302    print("... clean inputdir!")
303
304    cleanlist = [file for file in glob.glob(os.path.join(c.inputdir, "*"))
305                 if not os.path.basename(file).startswith(c.prefix)]
306
307    if cleanlist:
308        for element in cleanlist:
309            silent_remove(element)
310        print("... done!")
311    else:
312        print("... nothing to clean!")
313
314    return
315
316
317def my_error(message='ERROR'):
318    '''Prints a specified error message which can be passed to the function
319    before exiting the program.
320
321    Parameters
322    ----------
323    message : str, optional
324        Error message. Default value is "ERROR".
325
326    Return
327    ------
328
329    '''
330
331    trace = '\n'.join(traceback.format_stack())
332    full_message = message + '\n\n' + trace
333
334    print(full_message)
335
336    sys.exit(1)
337
338    return
339
340
341def send_mail(users, success_mode, message):
342    '''Prints a specific exit message which can be passed to the function.
343
344    Parameters
345    ----------
346    users : list of str
347        Contains all email addresses which should be notified.
348        It might also contain just the ecmwf user name which wil trigger
349        mailing to the associated email address for this user.
350
351    success_mode : str
352        States the exit mode of the program to put into
353        the mail subject line.
354
355    message : str, optional
356        Message for exiting program. Default value is "Done!".
357
358    Return
359    ------
360
361    '''
362
363    for user in users:
364        if '${USER}' in user:
365            user = os.getenv('USER')
366        try:
367            p = subprocess.Popen(['mail', '-s flex_extract_v7.1 ' +
368                                  success_mode, os.path.expandvars(user)],
369                                 stdin=subprocess.PIPE,
370                                 stdout=subprocess.PIPE,
371                                 stderr=subprocess.PIPE,
372                                 bufsize=1)
373            pout = p.communicate(input=message.encode() + '\n\n')[0]
374        except ValueError as e:
375            print('... ERROR: ' + str(e))
376            sys.exit('... Email could not be sent!')
377        except OSError as e:
378            print('... ERROR CODE: ' + str(e.errno))
379            print('... ERROR MESSAGE:\n \t ' + str(e.strerror))
380            sys.exit('... Email could not be sent!')
381        else:
382            print('Email sent to ' + os.path.expandvars(user))
383
384    return
385
386
387def normal_exit(message='Done!'):
388    '''Prints a specific exit message which can be passed to the function.
389
390    Parameters
391    ----------
392    message : str, optional
393        Message for exiting program. Default value is "Done!".
394
395    Return
396    ------
397
398    '''
399
400    print(str(message))
401
402    return
403
404
405def product(*args, **kwds):
406    '''Creates combinations of all passed arguments.
407
408    This method combines the single characters of the passed arguments
409    with each other. So that each character of each argument value
410    will be combined with each character of the other arguments as a tuple.
411
412    Note
413    ----
414    This method is taken from an example at the ECMWF wiki website.
415    https://software.ecmwf.int/wiki/display/GRIB/index.py; 2018-03-16
416
417    Example
418    -------
419    product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
420
421    product(range(2), repeat = 3) --> 000 001 010 011 100 101 110 111
422
423    Parameters
424    ----------
425    \*args : list or str
426        Positional arguments (arbitrary number).
427
428    \*\*kwds : dict
429        Contains all the keyword arguments from \*args.
430
431    Return
432    ------
433    prod : :obj:`tuple`
434        Return will be done with "yield". A tuple of combined arguments.
435        See example in description above.
436    '''
437    try:
438        pools = [tuple(arg) for arg in args] * kwds.get('repeat', 1)
439        result = [[]]
440        for pool in pools:
441            result = [x + [y] for x in result for y in pool]
442        for prod in result:
443            yield tuple(prod)
444    except TypeError as e:
445        sys.exit('... PRODUCT GENERATION FAILED!')
446
447    return
448
449
450def silent_remove(filename):
451    '''Remove file if it exists.
452    The function does not fail if the file does not exist.
453
454    Parameters
455    ----------
456    filename : str
457        The name of the file to be removed without notification.
458
459    Return
460    ------
461
462    '''
463    try:
464        os.remove(filename)
465    except OSError as e:
466        # errno.ENOENT  =  no such file or directory
467        if e.errno == errno.ENOENT:
468            pass
469        else:
470            raise  # re-raise exception if a different error occured
471
472    return
473
474
475def init128(filepath):
476    '''Opens and reads the grib file with table 128 information.
477
478    Parameters
479    ----------
480    filepath : str
481        Path to file of ECMWF grib table number 128.
482
483    Return
484    ------
485    table128 : dict
486        Contains the ECMWF grib table 128 information.
487        The key is the parameter number and the value is the
488        short name of the parameter.
489    '''
490    table128 = dict()
491    try:
492        with open(filepath) as f:
493            fdata = f.read().split('\n')
494    except OSError as e:
495        print('... ERROR CODE: ' + str(e.errno))
496        print('... ERROR MESSAGE:\n \t ' + str(e.strerror))
497
498        sys.exit('\n... Error occured while trying to read parameter '
499                 'table file: ' + str(filepath))
500    else:
501        for data in fdata:
502            if data != '' and data[0] != '!':
503                table128[data[0:3]] = data[59:65].strip()
504
505    return table128
506
507
508def to_param_id(pars, table):
509    '''Transform parameter names to parameter ids with ECMWF grib table 128.
510
511    Parameters
512    ----------
513    pars : str
514        Addpar argument from CONTROL file in the format of
515        parameter names instead of ids. The parameter short
516        names are sepearted with "/" and they are passed as
517        one single string.
518
519    table : dict
520        Contains the ECMWF grib table 128 information.
521        The key is the parameter number and the value is the
522        short name of the parameter.
523
524    Return
525    ------
526    ipar : list of int
527        List of addpar parameters from CONTROL file transformed to
528        parameter ids in the format of integer.
529    '''
530    if not pars:
531        return []
532    if not isinstance(pars, str):
533        pars=str(pars)
534
535    cpar = pars.upper().split('/')
536    ipar = []
537    for par in cpar:
538        par = par.strip()
539        for k, v in table.items():
540            if par.isdigit():
541                par = str(int(par)).zfill(3)
542            if par == k or par == v:
543                ipar.append(int(k))
544                break
545        else:
546            print('Warning: par ' + par + ' not found in table 128')
547
548    return ipar
549
550def to_param_id_with_tablenumber(pars, table):
551    '''Transform parameter names to parameter ids and add table id.
552
553    Conversion with ECMWF grib table 128.
554
555    Parameters
556    ----------
557    pars : str
558        Addpar argument from CONTROL file in the format of
559        parameter names instead of ids. The parameter short
560        names are sepearted with "/" and they are passed as
561        one single string.
562
563    table : dict
564        Contains the ECMWF grib table 128 information.
565        The key is the parameter number and the value is the
566        short name of the parameter.
567
568    Return
569    ------
570    spar : str
571        List of addpar parameters from CONTROL file transformed to
572        parameter ids in the format of integer.
573    '''
574    if not pars:
575        return []
576    if not isinstance(pars, str):
577        pars=str(pars)
578
579    cpar = pars.upper().split('/')
580    spar = []
581    for par in cpar:
582        for k, v in table.items():
583            if par.isdigit():
584                par = str(int(par)).zfill(3)
585            if par == k or par == v:
586                spar.append(k + '.128')
587                break
588        else:
589            print('\n\n\t\tWarning: par ' + par + ' not found in table 128\n\n')
590
591    return '/'.join(spar)
592
593def get_list_as_string(list_obj, concatenate_sign=', '):
594    '''Converts a list of arbitrary content into a single string.
595
596    Parameters
597    ----------
598    list_obj : list of *
599        A list with arbitrary content.
600
601    concatenate_sign : str, optional
602        A string which is used to concatenate the single
603        list elements. Default value is ", ".
604
605    Return
606    ------
607    str_of_list : str
608        The content of the list as a single string.
609    '''
610
611    if not isinstance(list_obj, list):
612        list_obj = list(list_obj)
613    str_of_list = concatenate_sign.join(str(l) for l in list_obj)
614
615    return str_of_list
616
617def make_dir(directory):
618    '''Creates a directory.
619
620    It gives a warning if the directory already exists and skips process.
621    The program stops only if there is another problem.
622
623    Parameters
624    ----------
625    directory : str
626        The path to directory which should be created.
627
628    Return
629    ------
630
631    '''
632    try:
633        os.makedirs(directory)
634    except OSError as e:
635        # errno.EEXIST = directory already exists
636        if e.errno == errno.EEXIST:
637            print('WARNING: Directory {0} already exists!'.format(directory))
638        else:
639            raise # re-raise exception if a different error occured
640
641    return
642
643def put_file_to_ecserver(ecd, filename, target, ecuid, ecgid):
644    '''Uses the ecaccess-file-put command to send a file to the ECMWF servers.
645
646    Note
647    ----
648    The return value is just for testing reasons. It does not have
649    to be used from the calling function since the whole error handling
650    is done in here.
651
652    Parameters
653    ----------
654    ecd : str
655        The path were the file is stored.
656
657    filename : str
658        The name of the file to send to the ECMWF server.
659
660    target : str
661        The target queue where the file should be sent to.
662
663    ecuid : str
664        The user id on ECMWF server.
665
666    ecgid : str
667        The group id on ECMWF server.
668
669    Return
670    ------
671
672    '''
673
674    try:
675        subprocess.check_output(['ecaccess-file-put',
676                                 ecd + '/' + filename,
677                                 target + ':/home/ms/' +
678                                 ecgid + '/' + ecuid +
679                                 '/' + filename],
680                                stderr=subprocess.STDOUT)
681    except subprocess.CalledProcessError as e:
682        print('... ERROR CODE: ' + str(e.returncode))
683        print('... ERROR MESSAGE:\n \t ' + str(e))
684
685        print('\n... Do you have a valid ecaccess certification key?')
686        sys.exit('... ECACCESS-FILE-PUT FAILED!')
687    except OSError as e:
688        print('... ERROR CODE: ' + str(e.errno))
689        print('... ERROR MESSAGE:\n \t ' + str(e.strerror))
690
691        print('\n... Most likely the ECACCESS library is not available!')
692        sys.exit('... ECACCESS-FILE-PUT FAILED!')
693
694    return
695
696def submit_job_to_ecserver(target, jobname):
697    '''Uses ecaccess-job-submit command to submit a job to the ECMWF server.
698
699    Note
700    ----
701    The return value is just for testing reasons. It does not have
702    to be used from the calling function since the whole error handling
703    is done in here.
704
705    Parameters
706    ----------
707    target : str
708        The target where the file should be sent to, e.g. the queue.
709
710    jobname : str
711        The name of the jobfile to be submitted to the ECMWF server.
712
713    Return
714    ------
715    job_id : int
716        The id number of the job as a reference at the ecmwf server.
717    '''
718
719    try:
720        job_id = subprocess.check_output(['ecaccess-job-submit', '-queueName',
721                                          target, jobname])
722
723    except subprocess.CalledProcessError as e:
724        print('... ERROR CODE: ' + str(e.returncode))
725        print('... ERROR MESSAGE:\n \t ' + str(e))
726
727        print('\n... Do you have a valid ecaccess certification key?')
728        sys.exit('... ECACCESS-JOB-SUBMIT FAILED!')
729    except OSError as e:
730        print('... ERROR CODE: ' + str(e.errno))
731        print('... ERROR MESSAGE:\n \t ' + str(e.strerror))
732
733        print('\n... Most likely the ECACCESS library is not available!')
734        sys.exit('... ECACCESS-JOB-SUBMIT FAILED!')
735
736    return job_id.decode()
737
738
739def get_informations(filename):
740    '''Gets basic information from an example grib file.
741
742    These information are important for later use and the
743    initialization of numpy arrays for data storing.
744
745    Parameters
746    ----------
747    filename : str
748            Name of the file which will be opened to extract basic information.
749
750    Return
751    ------
752    data : dict
753        Contains basic informations of the ECMWF grib files, e.g.
754        'Ni', 'Nj', 'latitudeOfFirstGridPointInDegrees',
755        'longitudeOfFirstGridPointInDegrees', 'latitudeOfLastGridPointInDegrees',
756        'longitudeOfLastGridPointInDegrees', 'jDirectionIncrementInDegrees',
757        'iDirectionIncrementInDegrees', 'missingValue'
758    '''
759    from eccodes import codes_grib_new_from_file, codes_get, codes_release
760
761    data = {}
762
763    # --- open file ---
764    print("Opening file for getting information data --- %s" % filename)
765    with open(filename, 'rb') as f:
766        # load first message from file
767        gid = codes_grib_new_from_file(f)
768
769        # information needed from grib message
770        keys = [
771                'Ni',
772                'Nj',
773                'latitudeOfFirstGridPointInDegrees',
774                'longitudeOfFirstGridPointInDegrees',
775                'latitudeOfLastGridPointInDegrees',
776                'longitudeOfLastGridPointInDegrees',
777                'jDirectionIncrementInDegrees',
778                'iDirectionIncrementInDegrees',
779                'missingValue',
780               ]
781
782        print('\nInformations are: ')
783        for key in keys:
784            # Get the value of the key in a grib message.
785            data[key] = codes_get(gid,key)
786            print("%s = %s" % (key,data[key]))
787
788        # Free the memory for the message referred as gribid.
789        codes_release(gid)
790
791    return data
792
793
794def get_dimensions(info, purefc, dtime, index_vals, start_date, end_date):
795    '''This function specifies the correct dimensions for x, y and t.
796
797    Parameters
798    ----------
799    info : dict
800        Contains basic informations of the ECMWF grib files, e.g.
801        'Ni', 'Nj', 'latitudeOfFirstGridPointInDegrees',
802        'longitudeOfFirstGridPointInDegrees', 'latitudeOfLastGridPointInDegrees',
803        'longitudeOfLastGridPointInDegrees', 'jDirectionIncrementInDegrees',
804        'iDirectionIncrementInDegrees', 'missingValue'
805
806    purefc : int
807        Switch for definition of pure forecast mode or not.
808
809    dtime : str
810        Time step in hours.
811
812    index_vals : list of list of str
813        Contains the values from the keys used for a distinct selection
814        of grib messages in processing  the grib files.
815        Content looks like e.g.:
816        index_vals[0]: ('20171106', '20171107', '20171108') ; date
817        index_vals[1]: ('0', '1200', '1800', '600') ; time
818        index_vals[2]: ('0', '12', '3', '6', '9') ; stepRange
819
820    start_date : str
821        The start date of the retrieval job.
822
823    end_date : str
824        The end date of the retrieval job.
825
826    Return
827    ------
828    (ix, jy, it) : tuple of int
829        Dimension in x-direction, y-direction and in time.
830    '''
831
832    ix = info['Ni']
833
834    jy = info['Nj']
835
836    if not purefc:
837        it = ((end_date - start_date).days + 1) * 24 // int(dtime)
838    else:
839        # #no of step * #no of times * #no of days
840        it = len(index_vals[2]) * len(index_vals[1]) * len(index_vals[0])
841
842    return (ix, jy, it)
843
844
845def execute_subprocess(cmd_list, error_msg='SUBPROCESS FAILED!'):
846    '''Executes a command line instruction via a subprocess.
847
848    Error handling is done if an error occures.
849
850    Parameters
851    ----------
852    cmd_list : list of str
853        A list of the components for the command line execution. Each
854        list entry is a single part of the command which is seperated from
855        the rest by a blank space.
856        E.g. ['mv', file1, file2]
857
858    Return
859    ------
860    error_msg : str, optional
861        The possible error message if the subprocess failed.
862        By default it will just tell "SUBPROCESS FAILED!".
863    '''
864
865    try:
866        subprocess.check_call(cmd_list)
867    except subprocess.CalledProcessError as e:
868        print('... ERROR CODE: ' + str(e.returncode))
869        print('... ERROR MESSAGE:\n \t ' + str(e))
870
871        sys.exit('... ' + error_msg)
872    except OSError as e:
873        print('... ERROR CODE: ' + str(e.errno))
874        print('... ERROR MESSAGE:\n \t ' + str(e.strerror))
875
876        sys.exit('... ' + error_msg)
877
878    return
879
880
881def generate_retrieval_period_boundary(c):
882    '''Generates retrieval period boundary datetimes from CONTROL information.
883
884    Parameters
885    ----------
886    c : ControlFile
887        Contains all the parameters of CONTROL file and
888        command line.
889
890    Return
891    ------
892    start_period : datetime
893        The first timestamp of the actual retrieval period disregarding
894        the temporary times which were used for processing reasons.
895
896    end_period : datetime
897        The last timestamp of the actual retrieval period disregarding
898        the temporary times which were used for processing reasons.
899    '''
900    # generate start and end timestamp of the retrieval period
901    start_period = datetime.strptime(c.start_date + c.time[0], '%Y%m%d%H')
902    start_period = start_period + timedelta(hours=int(c.step[0]))
903    end_period = datetime.strptime(c.end_date + c.time[-1], '%Y%m%d%H')
904    end_period = end_period + timedelta(hours=int(c.step[-1]))
905
906
907    return start_period, end_period
Note: See TracBrowser for help on using the repository browser.
hosted by ZAMG