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

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

diverse changes due to PEP8 style guide and eliminating grib2flexpart; removed unused parameter

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