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

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

added a function to submit a batch job

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