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

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

added ECMWF license for ECMWF python routine 'products'

  • Property mode set to 100644
File size: 27.8 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    It was released under the following license:
421    https://confluence.ecmwf.int/display/ECC/License
422
423    Example
424    -------
425    product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
426
427    product(range(2), repeat = 3) --> 000 001 010 011 100 101 110 111
428
429    Parameters
430    ----------
431    \*args : list or str
432        Positional arguments (arbitrary number).
433
434    \*\*kwds : dict
435        Contains all the keyword arguments from \*args.
436
437    Return
438    ------
439    prod : :obj:`tuple`
440        Return will be done with "yield". A tuple of combined arguments.
441        See example in description above.
442    '''
443    try:
444        pools = [tuple(arg) for arg in args] * kwds.get('repeat', 1)
445        result = [[]]
446        for pool in pools:
447            result = [x + [y] for x in result for y in pool]
448        for prod in result:
449            yield tuple(prod)
450    except TypeError as e:
451        sys.exit('... PRODUCT GENERATION FAILED!')
452
453    return
454
455
456def silent_remove(filename):
457    '''Remove file if it exists.
458    The function does not fail if the file does not exist.
459
460    Parameters
461    ----------
462    filename : str
463        The name of the file to be removed without notification.
464
465    Return
466    ------
467
468    '''
469    try:
470        os.remove(filename)
471    except OSError as e:
472        # errno.ENOENT  =  no such file or directory
473        if e.errno == errno.ENOENT:
474            pass
475        else:
476            raise  # re-raise exception if a different error occured
477
478    return
479
480
481def init128(filepath):
482    '''Opens and reads the grib file with table 128 information.
483
484    Parameters
485    ----------
486    filepath : str
487        Path to file of ECMWF grib table number 128.
488
489    Return
490    ------
491    table128 : dict
492        Contains the ECMWF grib table 128 information.
493        The key is the parameter number and the value is the
494        short name of the parameter.
495    '''
496    table128 = dict()
497    try:
498        with open(filepath) as f:
499            fdata = f.read().split('\n')
500    except OSError as e:
501        print('... ERROR CODE: ' + str(e.errno))
502        print('... ERROR MESSAGE:\n \t ' + str(e.strerror))
503
504        sys.exit('\n... Error occured while trying to read parameter '
505                 'table file: ' + str(filepath))
506    else:
507        for data in fdata:
508            if data != '' and data[0] != '!':
509                table128[data[0:3]] = data[59:65].strip()
510
511    return table128
512
513
514def to_param_id(pars, table):
515    '''Transform parameter names to parameter ids with ECMWF grib table 128.
516
517    Parameters
518    ----------
519    pars : str
520        Addpar argument from CONTROL file in the format of
521        parameter names instead of ids. The parameter short
522        names are sepearted with "/" and they are passed as
523        one single string.
524
525    table : dict
526        Contains the ECMWF grib table 128 information.
527        The key is the parameter number and the value is the
528        short name of the parameter.
529
530    Return
531    ------
532    ipar : list of int
533        List of addpar parameters from CONTROL file transformed to
534        parameter ids in the format of integer.
535    '''
536    if not pars:
537        return []
538    if not isinstance(pars, str):
539        pars = str(pars)
540
541    cpar = pars.upper().split('/')
542    ipar = []
543    for par in cpar:
544        par = par.strip()
545        for k, v in table.items():
546            if par.isdigit():
547                par = str(int(par)).zfill(3)
548            if par == k or par == v:
549                ipar.append(int(k))
550                break
551        else:
552            print('Warning: par ' + par + ' not found in table 128')
553
554    return ipar
555
556def to_param_id_with_tablenumber(pars, table):
557    '''Transform parameter names to parameter ids and add table id.
558
559    Conversion with ECMWF grib table 128.
560
561    Parameters
562    ----------
563    pars : str
564        Addpar argument from CONTROL file in the format of
565        parameter names instead of ids. The parameter short
566        names are sepearted with "/" and they are passed as
567        one single string.
568
569    table : dict
570        Contains the ECMWF grib table 128 information.
571        The key is the parameter number and the value is the
572        short name of the parameter.
573
574    Return
575    ------
576    spar : str
577        List of addpar parameters from CONTROL file transformed to
578        parameter ids in the format of integer.
579    '''
580    if not pars:
581        return []
582    if not isinstance(pars, str):
583        pars = str(pars)
584
585    cpar = pars.upper().split('/')
586    spar = []
587    for par in cpar:
588        for k, v in table.items():
589            if par.isdigit():
590                par = str(int(par)).zfill(3)
591            if par == k or par == v:
592                spar.append(k + '.128')
593                break
594        else:
595            print('\n\n\t\tWarning: par ' + par + ' not found in table 128\n\n')
596
597    return '/'.join(spar)
598
599def get_list_as_string(list_obj, concatenate_sign=', '):
600    '''Converts a list of arbitrary content into a single string.
601
602    Parameters
603    ----------
604    list_obj : list of *
605        A list with arbitrary content.
606
607    concatenate_sign : str, optional
608        A string which is used to concatenate the single
609        list elements. Default value is ", ".
610
611    Return
612    ------
613    str_of_list : str
614        The content of the list as a single string.
615    '''
616
617    if not isinstance(list_obj, list):
618        list_obj = list(list_obj)
619    str_of_list = concatenate_sign.join(str(l) for l in list_obj)
620
621    return str_of_list
622
623def make_dir(directory):
624    '''Creates a directory.
625
626    It gives a warning if the directory already exists and skips process.
627    The program stops only if there is another problem.
628
629    Parameters
630    ----------
631    directory : str
632        The path to directory which should be created.
633
634    Return
635    ------
636
637    '''
638    try:
639        os.makedirs(directory)
640    except OSError as e:
641        # errno.EEXIST = directory already exists
642        if e.errno == errno.EEXIST:
643            print('WARNING: Directory {0} already exists!'.format(directory))
644        else:
645            raise # re-raise exception if a different error occured
646
647    return
648
649def put_file_to_ecserver(ecd, filename, target, ecuid, ecgid):
650    '''Uses the ecaccess-file-put command to send a file to the ECMWF servers.
651
652    Note
653    ----
654    The return value is just for testing reasons. It does not have
655    to be used from the calling function since the whole error handling
656    is done in here.
657
658    Parameters
659    ----------
660    ecd : str
661        The path were the file is stored.
662
663    filename : str
664        The name of the file to send to the ECMWF server.
665
666    target : str
667        The target queue where the file should be sent to.
668
669    ecuid : str
670        The user id on ECMWF server.
671
672    ecgid : str
673        The group id on ECMWF server.
674
675    Return
676    ------
677
678    '''
679
680    try:
681        subprocess.check_output(['ecaccess-file-put',
682                                 ecd + '/' + filename,
683                                 target + ':/home/ms/' +
684                                 ecgid + '/' + ecuid +
685                                 '/' + filename],
686                                stderr=subprocess.STDOUT)
687    except subprocess.CalledProcessError as e:
688        print('... ERROR CODE: ' + str(e.returncode))
689        print('... ERROR MESSAGE:\n \t ' + str(e))
690
691        print('\n... Do you have a valid ecaccess certification key?')
692        sys.exit('... ECACCESS-FILE-PUT FAILED!')
693    except OSError as e:
694        print('... ERROR CODE: ' + str(e.errno))
695        print('... ERROR MESSAGE:\n \t ' + str(e.strerror))
696
697        print('\n... Most likely the ECACCESS library is not available!')
698        sys.exit('... ECACCESS-FILE-PUT FAILED!')
699
700    return
701
702def submit_job_to_ecserver(target, jobname):
703    '''Uses ecaccess-job-submit command to submit a job to the ECMWF server.
704
705    Note
706    ----
707    The return value is just for testing reasons. It does not have
708    to be used from the calling function since the whole error handling
709    is done in here.
710
711    Parameters
712    ----------
713    target : str
714        The target where the file should be sent to, e.g. the queue.
715
716    jobname : str
717        The name of the jobfile to be submitted to the ECMWF server.
718
719    Return
720    ------
721    job_id : int
722        The id number of the job as a reference at the ecmwf server.
723    '''
724
725    try:
726        job_id = subprocess.check_output(['ecaccess-job-submit', '-queueName',
727                                          target, jobname])
728
729    except subprocess.CalledProcessError as e:
730        print('... ERROR CODE: ' + str(e.returncode))
731        print('... ERROR MESSAGE:\n \t ' + str(e))
732
733        print('\n... Do you have a valid ecaccess certification key?')
734        sys.exit('... ECACCESS-JOB-SUBMIT FAILED!')
735    except OSError as e:
736        print('... ERROR CODE: ' + str(e.errno))
737        print('... ERROR MESSAGE:\n \t ' + str(e.strerror))
738
739        print('\n... Most likely the ECACCESS library is not available!')
740        sys.exit('... ECACCESS-JOB-SUBMIT FAILED!')
741
742    return job_id.decode()
743
744
745def get_informations(filename):
746    '''Gets basic information from an example grib file.
747
748    These information are important for later use and the
749    initialization of numpy arrays for data storing.
750
751    Parameters
752    ----------
753    filename : str
754            Name of the file which will be opened to extract basic information.
755
756    Return
757    ------
758    data : dict
759        Contains basic informations of the ECMWF grib files, e.g.
760        'Ni', 'Nj', 'latitudeOfFirstGridPointInDegrees',
761        'longitudeOfFirstGridPointInDegrees', 'latitudeOfLastGridPointInDegrees',
762        'longitudeOfLastGridPointInDegrees', 'jDirectionIncrementInDegrees',
763        'iDirectionIncrementInDegrees', 'missingValue'
764    '''
765    from eccodes import codes_grib_new_from_file, codes_get, codes_release
766
767    data = {}
768
769    # --- open file ---
770    print("Opening file for getting information data --- %s" % filename)
771    with open(filename, 'rb') as f:
772        # load first message from file
773        gid = codes_grib_new_from_file(f)
774
775        # information needed from grib message
776        keys = ['Ni',
777                'Nj',
778                'latitudeOfFirstGridPointInDegrees',
779                'longitudeOfFirstGridPointInDegrees',
780                'latitudeOfLastGridPointInDegrees',
781                'longitudeOfLastGridPointInDegrees',
782                'jDirectionIncrementInDegrees',
783                'iDirectionIncrementInDegrees',
784                'missingValue',
785               ]
786
787        print('\nInformations are: ')
788        for key in keys:
789            # Get the value of the key in a grib message.
790            data[key] = codes_get(gid, key)
791            print("%s = %s" % (key, data[key]))
792
793        # Free the memory for the message referred as gribid.
794        codes_release(gid)
795
796    return data
797
798
799def get_dimensions(info, purefc, dtime, index_vals, start_date, end_date):
800    '''This function specifies the correct dimensions for x, y and t.
801
802    Parameters
803    ----------
804    info : dict
805        Contains basic informations of the ECMWF grib files, e.g.
806        'Ni', 'Nj', 'latitudeOfFirstGridPointInDegrees',
807        'longitudeOfFirstGridPointInDegrees', 'latitudeOfLastGridPointInDegrees',
808        'longitudeOfLastGridPointInDegrees', 'jDirectionIncrementInDegrees',
809        'iDirectionIncrementInDegrees', 'missingValue'
810
811    purefc : int
812        Switch for definition of pure forecast mode or not.
813
814    dtime : str
815        Time step in hours.
816
817    index_vals : list of list of str
818        Contains the values from the keys used for a distinct selection
819        of grib messages in processing  the grib files.
820        Content looks like e.g.:
821        index_vals[0]: ('20171106', '20171107', '20171108') ; date
822        index_vals[1]: ('0', '1200', '1800', '600') ; time
823        index_vals[2]: ('0', '12', '3', '6', '9') ; stepRange
824
825    start_date : str
826        The start date of the retrieval job.
827
828    end_date : str
829        The end date of the retrieval job.
830
831    Return
832    ------
833    (ix, jy, it) : tuple of int
834        Dimension in x-direction, y-direction and in time.
835    '''
836
837    ix = info['Ni']
838
839    jy = info['Nj']
840
841    if not purefc:
842        it = ((end_date - start_date).days + 1) * 24 // int(dtime)
843    else:
844        # #no of step * #no of times * #no of days
845        it = len(index_vals[2]) * len(index_vals[1]) * len(index_vals[0])
846
847    return (ix, jy, it)
848
849
850def execute_subprocess(cmd_list, error_msg='SUBPROCESS FAILED!'):
851    '''Executes a command line instruction via a subprocess.
852
853    Error handling is done if an error occures.
854
855    Parameters
856    ----------
857    cmd_list : list of str
858        A list of the components for the command line execution. Each
859        list entry is a single part of the command which is seperated from
860        the rest by a blank space.
861        E.g. ['mv', file1, file2]
862
863    Return
864    ------
865    error_msg : str, optional
866        The possible error message if the subprocess failed.
867        By default it will just tell "SUBPROCESS FAILED!".
868    '''
869
870    try:
871        subprocess.check_call(cmd_list)
872    except subprocess.CalledProcessError as e:
873        print('... ERROR CODE: ' + str(e.returncode))
874        print('... ERROR MESSAGE:\n \t ' + str(e))
875
876        sys.exit('... ' + error_msg)
877    except OSError as e:
878        print('... ERROR CODE: ' + str(e.errno))
879        print('... ERROR MESSAGE:\n \t ' + str(e.strerror))
880
881        sys.exit('... ' + error_msg)
882
883    return
884
885
886def generate_retrieval_period_boundary(c):
887    '''Generates retrieval period boundary datetimes from CONTROL information.
888
889    Parameters
890    ----------
891    c : ControlFile
892        Contains all the parameters of CONTROL file and
893        command line.
894
895    Return
896    ------
897    start_period : datetime
898        The first timestamp of the actual retrieval period disregarding
899        the temporary times which were used for processing reasons.
900
901    end_period : datetime
902        The last timestamp of the actual retrieval period disregarding
903        the temporary times which were used for processing reasons.
904    '''
905    # generate start and end timestamp of the retrieval period
906    start_period = datetime.strptime(c.start_date + c.time[0], '%Y%m%d%H')
907    start_period = start_period + timedelta(hours=int(c.step[0]))
908    end_period = datetime.strptime(c.end_date + c.time[-1], '%Y%m%d%H')
909    end_period = end_period + timedelta(hours=int(c.step[-1]))
910
911
912    return start_period, end_period
Note: See TracBrowser for help on using the repository browser.
hosted by ZAMG