source: flex_extract.git/source/python/mods/tools.py @ 6470422

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

python2 downgrade/optimized description of command line parameters/correction of paramId conversion

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