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

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

language corrections in comment sections and print commands

  • 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#        - separated 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 it
121    contains the 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 it
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 arguments not present.
161
162    Parameters
163    ----------
164
165    Return
166    ------
167    args : Namespace
168        Contains the command line arguments from the 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 time 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 by 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 - temporary files will be conserved")
208    parser.add_argument("--oper", dest="oper",
209                        type=none_or_int, default=None,
210                        help='operational mode - prepares dates from '
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 public datasets")
218    parser.add_argument("--rrint", dest="rrint",
219                        type=none_or_int, default=None,
220                        help='Selection of old or new  '
221                        'interpolation method for precipitation:\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 temporary directory for '
229                        'retrieved grib files and other processing files.')
230    parser.add_argument("--outputdir", dest="outputdir",
231                        type=none_or_str, default=None,
232                        help='Path to final directory where '
233                        'FLEXPART input files will be 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 the 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='Job template file. Will be used for submission '
246                        'to the batch system on the ECMWF server after '
247                        'modification.')
248    parser.add_argument("--queue", dest="queue",
249                        type=none_or_str, default=None,
250                        help='The name of the ECMWF server name where the'
251                        'job script is to be submitted ' 
252                        '(e.g. ecgate | cca | ccb)')
253
254    args = parser.parse_args()
255
256    return args
257
258def read_ecenv(filepath):
259    '''Reads the file into a dictionary where the key values are the parameter
260    names.
261
262    Parameters
263    ----------
264    filepath : str
265        Path to file where the ECMWF environment parameters are stored.
266
267    Return
268    ------
269    envs : dict
270        Contains the environment parameter ecuid, ecgid, gateway
271        and destination for ECMWF server environments.
272    '''
273    envs = {}
274    try:
275        with open(filepath, 'r') as f:
276            for line in f:
277                data = line.strip().split()
278                envs[str(data[0])] = str(data[1])
279    except OSError as e:
280        print('... ERROR CODE: ' + str(e.errno))
281        print('... ERROR MESSAGE:\n \t ' + str(e.strerror))
282
283        sys.exit('\n... Error occured while trying to read ECMWF_ENV '
284                 'file: ' + str(filepath))
285
286    return envs
287
288def clean_up(c):
289    '''Remove files from the intermediate directory (inputdir).
290
291    It keeps the final FLEXPART input files if program runs without
292    ECMWF API and keywords "ectrans" or "ecstorage" are set to "1".
293
294    Parameters
295    ----------
296    c : ControlFile
297        Contains all the parameters of CONTROL file and
298        command line.
299
300    Return
301    ------
302
303    '''
304
305    print("... clean inputdir!")
306
307    cleanlist = [filename for filename in
308                 glob.glob(os.path.join(c.inputdir, "*"))
309                 if not os.path.basename(filename).startswith(c.prefix)]
310
311    if cleanlist:
312        for element in cleanlist:
313            silent_remove(element)
314        print("... done!")
315    else:
316        print("... nothing to clean!")
317
318    return
319
320
321def my_error(message='ERROR'):
322    '''Prints a specified error message which can be passed to the function
323    before exiting the program.
324
325    Parameters
326    ----------
327    message : str, optional
328        Error message. Default value is "ERROR".
329
330    Return
331    ------
332
333    '''
334
335    trace = '\n'.join(traceback.format_stack())
336    full_message = message + '\n\n' + trace
337
338    print(full_message)
339
340    sys.exit(1)
341
342    return
343
344
345def send_mail(users, success_mode, message):
346    '''Prints a specific exit message which can be passed to the function.
347
348    Parameters
349    ----------
350    users : list of str
351        Contains all email addresses which should be notified.
352        It might also contain just the ecmwf user name which wil trigger
353        mailing to the associated email address for this user.
354
355    success_mode : str
356        States the exit mode of the program to put into
357        the mail subject line.
358
359    message : str, optional
360        Message for exiting program. Default value is "Done!".
361
362    Return
363    ------
364
365    '''
366
367    for user in users:
368        if '${USER}' in user:
369            user = os.getenv('USER')
370        try:
371            p = subprocess.Popen(['mail', '-s flex_extract_v7.1 ' +
372                                  success_mode, os.path.expandvars(user)],
373                                 stdin=subprocess.PIPE,
374                                 stdout=subprocess.PIPE,
375                                 stderr=subprocess.PIPE,
376                                 bufsize=1)
377            pout = p.communicate(input=message + '\n\n')[0]
378        except ValueError as e:
379            print('... ERROR: ' + str(e))
380            sys.exit('... Email could not be sent!')
381        except OSError as e:
382            print('... ERROR CODE: ' + str(e.errno))
383            print('... ERROR MESSAGE:\n \t ' + str(e.strerror))
384            sys.exit('... Email could not be sent!')
385        else:
386            print('Email sent to ' + os.path.expandvars(user))
387
388    return
389
390
391def normal_exit(message='Done!'):
392    '''Prints a specific exit message which can be passed to the function.
393
394    Parameters
395    ----------
396    message : str, optional
397        Message for exiting program. Default value is "Done!".
398
399    Return
400    ------
401
402    '''
403
404    print(str(message))
405
406    return
407
408
409def product(*args, **kwds):
410    '''Creates combinations of all passed arguments.
411
412    This method combines the single characters of the passed arguments
413    with each other in a way that each character of each argument value
414    will be combined with each character of the other arguments as a tuple.
415
416    Note
417    ----
418    This method is taken from an example at the ECMWF wiki website.
419    https://software.ecmwf.int/wiki/display/GRIB/index.py; 2018-03-16
420
421    It was released under the following license:
422    https://confluence.ecmwf.int/display/ECC/License
423
424    Example
425    -------
426    product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
427
428    product(range(2), repeat = 3) --> 000 001 010 011 100 101 110 111
429
430    Parameters
431    ----------
432    \*args : list or str
433        Positional arguments (arbitrary number).
434
435    \*\*kwds : dict
436        Contains all the keyword arguments from \*args.
437
438    Return
439    ------
440    prod : :obj:`tuple`
441        Return will be done with "yield". A tuple of combined arguments.
442        See example in description above.
443    '''
444    try:
445        pools = [tuple(arg) for arg in args] * kwds.get('repeat', 1)
446        result = [[]]
447        for pool in pools:
448            result = [x + [y] for x in result for y in pool]
449        for prod in result:
450            yield tuple(prod)
451    except TypeError as e:
452        sys.exit('... PRODUCT GENERATION FAILED!')
453
454    return
455
456
457def silent_remove(filename):
458    '''Remove file if it exists.
459    The function does not fail if the file does not exist.
460
461    Parameters
462    ----------
463    filename : str
464        The name of the file to be removed without notification.
465
466    Return
467    ------
468
469    '''
470    try:
471        os.remove(filename)
472    except OSError as e:
473        # errno.ENOENT  =  no such file or directory
474        if e.errno == errno.ENOENT:
475            pass
476        else:
477            raise  # re-raise exception if a different error occured
478
479    return
480
481
482def init128(filepath):
483    '''Opens and reads the grib file with table 128 information.
484
485    Parameters
486    ----------
487    filepath : str
488        Path to file of ECMWF grib table number 128.
489
490    Return
491    ------
492    table128 : dict
493        Contains the ECMWF grib table 128 information.
494        The key is the parameter number and the value is the
495        short name of the parameter.
496    '''
497    table128 = dict()
498    try:
499        with open(filepath) as f:
500            fdata = f.read().split('\n')
501    except OSError as e:
502        print('... ERROR CODE: ' + str(e.errno))
503        print('... ERROR MESSAGE:\n \t ' + str(e.strerror))
504
505        sys.exit('\n... Error occured while trying to read parameter '
506                 'table file: ' + str(filepath))
507    else:
508        for data in fdata:
509            if data != '' and data[0] != '!':
510                table128[data[0:3]] = data[59:65].strip()
511
512    return table128
513
514
515def to_param_id(pars, table):
516    '''Transform parameter names to parameter ids with ECMWF grib table 128.
517
518    Parameters
519    ----------
520    pars : str
521        Addpar argument from CONTROL file in the format of
522        parameter names instead of IDs. The parameter short
523        names are separated by "/" and passed as 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 ID. The parameter short
566        names are separated by "/" and passed as one single string.
567
568    table : dict
569        Contains the ECMWF grib table 128 information.
570        The key is the parameter number and the value is the
571        short name of the parameter.
572
573    Return
574    ------
575    spar : str
576        List of addpar parameters from CONTROL file transformed to
577        parameter IDs in the format of integer.
578    '''
579    if not pars:
580        return []
581    if not isinstance(pars, str):
582        pars = str(pars)
583
584    cpar = pars.upper().split('/')
585    spar = []
586    for par in cpar:
587        for k, v in table.items():
588            if par.isdigit():
589                par = str(int(par)).zfill(3)
590            if par == k or par == v:
591                spar.append(k + '.128')
592                break
593        else:
594            print('\n\n\t\tWarning: par ' + par + ' not found in table 128\n\n')
595
596    return '/'.join(spar)
597
598def get_list_as_string(list_obj, concatenate_sign=', '):
599    '''Converts a list of arbitrary content into a single string using a given
600    concatenation character.
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    If the directory already exists, an information is printed and the creation
627    skipped. 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('INFORMATION: 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    '''Extracts basic information from a sample grib file.
747
748    This information is needed for later use and the
749    initialization of numpy arrays where data are stored.
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 grib file for extraction of information --- %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('\nInformation extracted: ')
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 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.
859        They will be concatenated with blank space for the command
860        to be submitted, like ['mv', file1, file2] for mv file1 file2.
861
862    Return
863    ------
864    error_msg : str, optional
865        Error message if the subprocess fails.
866        By default it will just say "SUBPROCESS FAILED!".
867    '''
868
869    try:
870        subprocess.check_call(cmd_list)
871    except subprocess.CalledProcessError as e:
872        print('... ERROR CODE: ' + str(e.returncode))
873        print('... ERROR MESSAGE:\n \t ' + str(e))
874
875        sys.exit('... ' + error_msg)
876    except OSError as e:
877        print('... ERROR CODE: ' + str(e.errno))
878        print('... ERROR MESSAGE:\n \t ' + str(e.strerror))
879
880        sys.exit('... ' + error_msg)
881
882    return
883
884
885def generate_retrieval_period_boundary(c):
886    '''Generates retrieval period boundary datetimes from CONTROL information.
887
888    Parameters
889    ----------
890    c : ControlFile
891        Contains all the parameters of CONTROL file and
892        command line.
893
894    Return
895    ------
896    start_period : datetime
897        The first timestamp of the actual retrieval period disregarding
898        the temporary times which were used for processing reasons.
899
900    end_period : datetime
901        The last timestamp of the actual retrieval period disregarding
902        the temporary times which were used for processing reasons.
903    '''
904    # generate start and end timestamp of the retrieval period
905    start_period = datetime.strptime(c.start_date + c.time[0], '%Y%m%d%H')
906    start_period = start_period + timedelta(hours=int(c.step[0]))
907    end_period = datetime.strptime(c.end_date + c.time[-1], '%Y%m%d%H')
908    end_period = end_period + timedelta(hours=int(c.step[-1]))
909
910
911    return start_period, end_period
Note: See TracBrowser for help on using the repository browser.
hosted by ZAMG