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

dev
Last change on this file since b6ea29e was b6ea29e, checked in by Anne Tipka <anne.tipka@…>, 22 months ago

Adaptations for Bologna servers in Python part

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