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

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

added history comments to previous commit changes and minor additional corrections

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