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

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

added history information of changes to v7.1.2

  • Property mode set to 100644
File size: 29.2 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, target, ecuid, ecgid):
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    target : str
671        The target queue where the file should be sent to.
672
673    ecuid : str
674        The user id on ECMWF server.
675
676    ecgid : str
677        The group id on ECMWF server.
678
679    Return
680    ------
681
682    '''
683
684    try:
685        subprocess.check_output(['ecaccess-file-put',
686                                 ecd + '/' + filename,
687                                 target + ':/home/ms/' +
688                                 ecgid + '/' + ecuid +
689                                 '/' + filename],
690                                stderr=subprocess.STDOUT)
691    except subprocess.CalledProcessError as e:
692        print('... ERROR CODE: ' + str(e.returncode))
693        print('... ERROR MESSAGE:\n \t ' + str(e))
694
695        print('\n... Do you have a valid ecaccess certification key?')
696        sys.exit('... ECACCESS-FILE-PUT FAILED!')
697    except OSError as e:
698        print('... ERROR CODE: ' + str(e.errno))
699        print('... ERROR MESSAGE:\n \t ' + str(e.strerror))
700
701        print('\n... Most likely the ECACCESS library is not available!')
702        sys.exit('... ECACCESS-FILE-PUT FAILED!')
703
704    return
705
706def submit_job_to_ecserver(target, jobname):
707    '''Uses ecaccess-job-submit command to submit a job to the ECMWF server.
708
709    Note
710    ----
711    The return value is just for testing reasons. It does not have
712    to be used from the calling function since the whole error handling
713    is done in here.
714
715    Parameters
716    ----------
717    target : str
718        The target where the file should be sent to, e.g. the queue.
719
720    jobname : str
721        The name of the jobfile to be submitted to the ECMWF server.
722
723    Return
724    ------
725    job_id : int
726        The id number of the job as a reference at the ECMWF server.
727    '''
728
729    try:
730        job_id = subprocess.check_output(['ecaccess-job-submit', '-queueName',
731                                          target, jobname])
732
733    except subprocess.CalledProcessError as e:
734        print('... ERROR CODE: ' + str(e.returncode))
735        print('... ERROR MESSAGE:\n \t ' + str(e))
736
737        print('\n... Do you have a valid ecaccess certification key?')
738        sys.exit('... ecaccess-job-submit FAILED!')
739    except OSError as e:
740        print('... ERROR CODE: ' + str(e.errno))
741        print('... ERROR MESSAGE:\n \t ' + str(e.strerror))
742
743        print('\n... Most likely the ECACCESS library is not available!')
744        sys.exit('... ecaccess-job-submit FAILED!')
745
746    return job_id.decode()
747
748
749def get_informations(filename):
750    '''Extracts basic information from a sample grib file.
751
752    This information is needed for later use and the
753    initialization of numpy arrays where data are stored.
754
755    Parameters
756    ----------
757    filename : str
758            Name of the file which will be opened to extract basic information.
759
760    Return
761    ------
762    data : dict
763        Contains basic informations of the ECMWF grib files, e.g.
764        'Ni', 'Nj', 'latitudeOfFirstGridPointInDegrees',
765        'longitudeOfFirstGridPointInDegrees', 'latitudeOfLastGridPointInDegrees',
766        'longitudeOfLastGridPointInDegrees', 'jDirectionIncrementInDegrees',
767        'iDirectionIncrementInDegrees', 'missingValue'
768    '''
769    from eccodes import codes_grib_new_from_file, codes_get, codes_release
770
771    data = {}
772
773    # --- open file ---
774    print("Opening grib file for extraction of information --- %s" % filename)
775    with open(filename, 'rb') as f:
776        # load first message from file
777        gid = codes_grib_new_from_file(f)
778
779        # information needed from grib message
780        keys = ['Ni',
781                'Nj',
782                'latitudeOfFirstGridPointInDegrees',
783                'longitudeOfFirstGridPointInDegrees',
784                'latitudeOfLastGridPointInDegrees',
785                'longitudeOfLastGridPointInDegrees',
786                'jDirectionIncrementInDegrees',
787                'iDirectionIncrementInDegrees',
788                'missingValue',
789               ]
790
791        print('\nInformation extracted: ')
792        for key in keys:
793            # Get the value of the key in a grib message.
794            data[key] = codes_get(gid, key)
795            print("%s = %s" % (key, data[key]))
796
797        # Free the memory for the message referred as gribid.
798        codes_release(gid)
799
800    return data
801
802
803def get_dimensions(info, purefc, dtime, index_vals, start_date, end_date):
804    '''This function specifies the correct dimensions for x, y, and t.
805
806    Parameters
807    ----------
808    info : dict
809        Contains basic informations of the ECMWF grib files, e.g.
810        'Ni', 'Nj', 'latitudeOfFirstGridPointInDegrees',
811        'longitudeOfFirstGridPointInDegrees', 'latitudeOfLastGridPointInDegrees',
812        'longitudeOfLastGridPointInDegrees', 'jDirectionIncrementInDegrees',
813        'iDirectionIncrementInDegrees', 'missingValue'
814
815    purefc : int
816        Switch for definition of pure forecast mode or not.
817
818    dtime : str
819        Time step in hours.
820
821    index_vals : list of list of str
822        Contains the values from the keys used for a distinct selection
823        of GRIB messages in processing the grib files.
824        Content looks like e.g.:
825        index_vals[0]: ('20171106', '20171107', '20171108') ; date
826        index_vals[1]: ('0', '1200', '1800', '600') ; time
827        index_vals[2]: ('0', '12', '3', '6', '9') ; stepRange
828
829    start_date : str
830        The start date of the retrieval job.
831
832    end_date : str
833        The end date of the retrieval job.
834
835    Return
836    ------
837    (ix, jy, it) : tuple of int
838        Dimension in x-direction, y-direction and in time.
839    '''
840
841    ix = info['Ni']
842
843    jy = info['Nj']
844
845    if not purefc:
846        it = ((end_date - start_date).days + 1) * 24 // int(dtime)
847    else:
848        # #no of step * #no of times * #no of days
849        it = len(index_vals[2]) * len(index_vals[1]) * len(index_vals[0])
850
851    return (ix, jy, it)
852
853
854def execute_subprocess(cmd_list, error_msg='SUBPROCESS FAILED!'):
855    '''Executes a command via a subprocess.
856
857    Error handling is done if an error occures.
858
859    Parameters
860    ----------
861    cmd_list : list of str
862        A list of the components for the command line execution.
863        They will be concatenated with blank space for the command
864        to be submitted, like ['mv', file1, file2] for mv file1 file2.
865
866    Return
867    ------
868    error_msg : str, optional
869        Error message if the subprocess fails.
870        By default it will just say "SUBPROCESS FAILED!".
871    '''
872
873    try:
874        subprocess.check_call(cmd_list)
875    except subprocess.CalledProcessError as e:
876        print('... ERROR CODE: ' + str(e.returncode))
877        print('... ERROR MESSAGE:\n \t ' + str(e))
878
879        sys.exit('... ' + error_msg)
880    except OSError as e:
881        print('... ERROR CODE: ' + str(e.errno))
882        print('... ERROR MESSAGE:\n \t ' + str(e.strerror))
883
884        sys.exit('... ' + error_msg)
885
886    return
887
888
889def generate_retrieval_period_boundary(c):
890    '''Generates retrieval period boundary datetimes from CONTROL information.
891
892    Parameters
893    ----------
894    c : ControlFile
895        Contains all the parameters of CONTROL file and
896        command line.
897
898    Return
899    ------
900    start_period : datetime
901        The first timestamp of the actual retrieval period disregarding
902        the temporary times which were used for processing reasons.
903
904    end_period : datetime
905        The last timestamp of the actual retrieval period disregarding
906        the temporary times which were used for processing reasons.
907    '''
908    # generate start and end timestamp of the retrieval period
909    start_period = datetime.strptime(c.start_date + c.time[0], '%Y%m%d%H')
910    start_period = start_period + timedelta(hours=int(c.step[0]))
911    end_period = datetime.strptime(c.end_date + c.time[-1], '%Y%m%d%H')
912    end_period = end_period + timedelta(hours=int(c.step[-1]))
913
914
915    return start_period, end_period
916
917
918def check_for_string_in_file(filepath, search_string):
919    """
920    Search for a specific string in a file and return True if
921    the string was found.
922
923    Parameters
924    ----------
925    filepath : str
926        The full file path which is to be examined.
927
928    search_string : str
929        The string which is looked up for in the file.
930
931    Return
932    ------
933    Boolean :
934        True : String was found
935        False : String was not found
936    """
937    with open(filepath, 'r') as fio:
938        for line in fio:
939            if search_string in line:
940                return True
941    return False
942
943
944def overwrite_lines_in_file(filepath, search_string, sub_string):
945    """
946    Overwrites lines which contain the given search string with the
947    substitution string.
948
949    Parameters
950    ----------
951    search_string : str
952        The string which is looked up for in the file.
953
954    sub_string : str
955        The string which overwrites the search string.
956
957    Return
958    ------
959    """
960    with open(filepath, 'r') as fio:
961        data = fio.readlines()
962
963    with open(filepath, 'w') as fio:
964        for line in data:
965            if search_string in line:
966                fio.write(sub_string)
967            else:
968                fio.write(line)
969
970    return
971
Note: See TracBrowser for help on using the repository browser.
hosted by ZAMG