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
RevLine 
[8463d78]1#!/usr/bin/env python3
[efdb01a]2# -*- coding: utf-8 -*-
[991df6a]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)
[ff99eae]10#        - created functions silent_remove and product (taken from ECMWF)
[991df6a]11#
12#    November 2015 - Leopold Haimberger (University of Vienna)
[ff99eae]13#        - created functions: interpret_args_and_control, clean_up
14#          my_error, normal_exit, init128, to_param_id
[991df6a]15#
[6f951ca]16#    April - December 2018 - Anne Philipp (University of Vienna):
[991df6a]17#        - applied PEP8 style guide
18#        - added documentation
[6f951ca]19#        - moved all non class methods from former file Flexparttools in here
[8209738]20#        - separated args and control interpretation
[6f951ca]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
[991df6a]24#
[75db9b0]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#
[01bd8aa]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
[73d2e4e]32#        - added function to submit a batch job
[01bd8aa]33#
[991df6a]34# @License:
[026b359]35#    (C) Copyright 2014-2020.
[6f951ca]36#    Anne Philipp, Leopold Haimberger
[991df6a]37#
[44174de]38#    SPDX-License-Identifier: CC-BY-4.0
39#
[6f951ca]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.
[991df6a]44#
[6f951ca]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
[991df6a]65#*******************************************************************************
[6f951ca]66'''This module contains a collection of diverse tasks within flex_extract.
67'''
[efdb01a]68
69# ------------------------------------------------------------------------------
70# MODULES
71# ------------------------------------------------------------------------------
[6470422]72from __future__ import print_function
73
[efdb01a]74import os
75import errno
76import sys
77import glob
[ff99eae]78import subprocess
[991df6a]79import traceback
[0f89116]80# pylint: disable=unused-import
[f20af73]81try:
82    import exceptions
83except ImportError:
84    import builtins as exceptions
[0f89116]85# pylint: enable=unused-import
[d727af2]86from datetime import datetime, timedelta
[991df6a]87from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
[efdb01a]88
89# ------------------------------------------------------------------------------
[6f951ca]90# METHODS
[efdb01a]91# ------------------------------------------------------------------------------
92
[f20af73]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
[ba99230]116    from Classes.ControlFile import ControlFile
[f20af73]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
[ca867de]128def none_or_str(value):
[8209738]129    '''Converts the input string into Pythons None type if it
130    contains the string "None".
[274f9ef]131
132    Parameters
133    ----------
[6f951ca]134    value : str
[274f9ef]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",
[8209738]141        then the Python type None is returned, otherwise the string itself.
[ca867de]142    '''
143    if value == 'None':
144        return None
145    return value
146
147def none_or_int(value):
[8209738]148    '''Converts the input string into Pythons None-type if it
149    contains string "None"; otherwise it is converted to an integer value.
[274f9ef]150
151    Parameters
152    ----------
[6f951ca]153    value : str
[274f9ef]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.
[ca867de]162    '''
163    if value == 'None':
164        return None
165    return int(value)
166
[9aefaad]167def get_cmdline_args():
[274f9ef]168    '''Decomposes the command line arguments and assigns them to variables.
[8209738]169    Apply default values for arguments not present.
[efdb01a]170
[274f9ef]171    Parameters
172    ----------
[efdb01a]173
[274f9ef]174    Return
175    ------
[6f951ca]176    args : Namespace
[8209738]177        Contains the command line arguments from the script / program call.
[efdb01a]178    '''
[54a8a01]179
[efdb01a]180    parser = ArgumentParser(description='Retrieve FLEXPART input from \
[54a8a01]181                                ECMWF MARS archive',
[efdb01a]182                            formatter_class=ArgumentDefaultsHelpFormatter)
183
[095dc73]184    # control parameters that override control file values
[ca867de]185    parser.add_argument("--start_date", dest="start_date",
186                        type=none_or_str, default=None,
[efdb01a]187                        help="start date YYYYMMDD")
[ca867de]188    parser.add_argument("--end_date", dest="end_date",
189                        type=none_or_str, default=None,
[efdb01a]190                        help="end_date YYYYMMDD")
[ca867de]191    parser.add_argument("--date_chunk", dest="date_chunk",
192                        type=none_or_int, default=None,
[efdb01a]193                        help="# of days to be retrieved at once")
[f2616a3]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")
[ca867de]197    parser.add_argument("--controlfile", dest="controlfile",
[6470422]198                        type=none_or_str, default='CONTROL_EA5',
199                        help="The file with all CONTROL parameters.")
[ca867de]200    parser.add_argument("--basetime", dest="basetime",
201                        type=none_or_int, default=None,
[8209738]202                        help="base time such as 0 or 12 (for half day retrievals)")
[ca867de]203    parser.add_argument("--step", dest="step",
204                        type=none_or_str, default=None,
[6470422]205                        help="Forecast steps such as 00/to/48")
[ca867de]206    parser.add_argument("--levelist", dest="levelist",
207                        type=none_or_str, default=None,
[efdb01a]208                        help="Vertical levels to be retrieved, e.g. 30/to/60")
[ca867de]209    parser.add_argument("--area", dest="area",
210                        type=none_or_str, default=None,
[8209738]211                        help="area, defined by north/west/south/east")
[efdb01a]212
[095dc73]213    # some switches
214    parser.add_argument("--debug", dest="debug",
215                        type=none_or_int, default=None,
[8209738]216                        help="debug mode - temporary files will be conserved")
[d4696e0]217    parser.add_argument("--oper", dest="oper",
218                        type=none_or_int, default=None,
[8209738]219                        help='operational mode - prepares dates from '
[6470422]220                        'environment variables')
[095dc73]221    parser.add_argument("--request", dest="request",
222                        type=none_or_int, default=None,
[8209738]223                        help="list all MARS requests in file mars_requests.dat")
[095dc73]224    parser.add_argument("--public", dest="public",
225                        type=none_or_int, default=None,
[8209738]226                        help="public mode - retrieves public datasets")
[095dc73]227    parser.add_argument("--rrint", dest="rrint",
228                        type=none_or_int, default=None,
[8209738]229                        help='Selection of old or new  '
230                        'interpolation method for precipitation:\n'
[6470422]231                        '     0 - old method\n'
232                        '     1 - new method (additional subgrid points)')
[095dc73]233
234    # set directories
[ca867de]235    parser.add_argument("--inputdir", dest="inputdir",
236                        type=none_or_str, default=None,
[8209738]237                        help='Path to temporary directory for '
238                        'retrieved grib files and other processing files.')
[ca867de]239    parser.add_argument("--outputdir", dest="outputdir",
240                        type=none_or_str, default=None,
[8209738]241                        help='Path to final directory where '
242                        'FLEXPART input files will be stored.')
[efdb01a]243
[ff99eae]244    # this is only used by prepare_flexpart.py to rerun a postprocessing step
[ca867de]245    parser.add_argument("--ppid", dest="ppid",
[786cfd6]246                        type=none_or_str, default=None,
[8209738]247                        help='This is the specify the parent process id of a '
[6470422]248                        'single flex_extract run to identify the files. '
249                        'It is the second number in the GRIB files.')
[efdb01a]250
251    # arguments for job submission to ECMWF, only needed by submit.py
252    parser.add_argument("--job_template", dest='job_template',
[ca867de]253                        type=none_or_str, default="job.temp",
[8209738]254                        help='Job template file. Will be used for submission '
255                        'to the batch system on the ECMWF server after '
256                        'modification.')
[ca867de]257    parser.add_argument("--queue", dest="queue",
258                        type=none_or_str, default=None,
[8209738]259                        help='The name of the ECMWF server name where the'
[47be2684]260                        'job script is to be submitted '
[6470422]261                        '(e.g. ecgate | cca | ccb)')
[efdb01a]262
263    args = parser.parse_args()
264
[54a8a01]265    return args
[efdb01a]266
[96e1533]267def read_ecenv(filepath):
[274f9ef]268    '''Reads the file into a dictionary where the key values are the parameter
269    names.
270
271    Parameters
272    ----------
[6f951ca]273    filepath : str
[274f9ef]274        Path to file where the ECMWF environment parameters are stored.
275
276    Return
277    ------
[6f951ca]278    envs : dict
[274f9ef]279        Contains the environment parameter ecuid, ecgid, gateway
280        and destination for ECMWF server environments.
[54a8a01]281    '''
[0f89116]282    envs = {}
[96e1533]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))
[2fb99de]291
[96e1533]292        sys.exit('\n... Error occured while trying to read ECMWF_ENV '
[0f89116]293                 'file: ' + str(filepath))
[efdb01a]294
[54a8a01]295    return envs
[efdb01a]296
[ff99eae]297def clean_up(c):
[96e1533]298    '''Remove files from the intermediate directory (inputdir).
299
300    It keeps the final FLEXPART input files if program runs without
[8209738]301    ECMWF API and keywords "ectrans" or "ecstorage" are set to "1".
[274f9ef]302
303    Parameters
304    ----------
[6f951ca]305    c : ControlFile
[274f9ef]306        Contains all the parameters of CONTROL file and
307        command line.
308
309    Return
310    ------
311
[efdb01a]312    '''
313
[96e1533]314    print("... clean inputdir!")
[efdb01a]315
[0f89116]316    cleanlist = [filename for filename in
317                 glob.glob(os.path.join(c.inputdir, "*"))
318                 if not os.path.basename(filename).startswith(c.prefix)]
[efdb01a]319
[96e1533]320    if cleanlist:
321        for element in cleanlist:
[f20af73]322            silent_remove(element)
[96e1533]323        print("... done!")
324    else:
325        print("... nothing to clean!")
[efdb01a]326
327    return
328
329
[f20af73]330def my_error(message='ERROR'):
[274f9ef]331    '''Prints a specified error message which can be passed to the function
332    before exiting the program.
333
334    Parameters
335    ----------
[6f951ca]336    message : str, optional
[274f9ef]337        Error message. Default value is "ERROR".
[efdb01a]338
[274f9ef]339    Return
340    ------
[efdb01a]341
342    '''
[ff99eae]343
[96e1533]344    trace = '\n'.join(traceback.format_stack())
345    full_message = message + '\n\n' + trace
[ff99eae]346
[96e1533]347    print(full_message)
[54a8a01]348
349    sys.exit(1)
[efdb01a]350
351    return
352
353
[96e1533]354def send_mail(users, success_mode, message):
[274f9ef]355    '''Prints a specific exit message which can be passed to the function.
[efdb01a]356
[274f9ef]357    Parameters
358    ----------
[6f951ca]359    users : list of str
[274f9ef]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.
[efdb01a]363
[6f951ca]364    success_mode : str
[96e1533]365        States the exit mode of the program to put into
366        the mail subject line.
367
[6f951ca]368    message : str, optional
[274f9ef]369        Message for exiting program. Default value is "Done!".
[efdb01a]370
[274f9ef]371    Return
372    ------
[efdb01a]373
374    '''
[ff99eae]375
[54a8a01]376    for user in users:
377        if '${USER}' in user:
378            user = os.getenv('USER')
379        try:
[96e1533]380            p = subprocess.Popen(['mail', '-s flex_extract_v7.1 ' +
381                                  success_mode, os.path.expandvars(user)],
[ff99eae]382                                 stdin=subprocess.PIPE,
383                                 stdout=subprocess.PIPE,
384                                 stderr=subprocess.PIPE,
385                                 bufsize=1)
[0f89116]386            pout = p.communicate(input=message + '\n\n')[0]
[54a8a01]387        except ValueError as e:
[96e1533]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!')
[54a8a01]394        else:
[96e1533]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    ----------
[6f951ca]405    message : str, optional
[96e1533]406        Message for exiting program. Default value is "Done!".
407
408    Return
409    ------
410
411    '''
412
413    print(str(message))
[efdb01a]414
415    return
416
417
418def product(*args, **kwds):
[96e1533]419    '''Creates combinations of all passed arguments.
420
421    This method combines the single characters of the passed arguments
[8209738]422    with each other in a way that each character of each argument value
[274f9ef]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
[d1bfa24]430    It was released under the following license:
431    https://confluence.ecmwf.int/display/ECC/License
432
[274f9ef]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    ----------
[6f951ca]441    \*args : list or str
[274f9ef]442        Positional arguments (arbitrary number).
443
[6f951ca]444    \*\*kwds : dict
[274f9ef]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.
[efdb01a]452    '''
[96e1533]453    try:
[79729d5]454        pools = [tuple(arg) for arg in args] * kwds.get('repeat', 1)
[96e1533]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!')
[efdb01a]462
463    return
464
465
[ff99eae]466def silent_remove(filename):
[274f9ef]467    '''Remove file if it exists.
468    The function does not fail if the file does not exist.
469
470    Parameters
471    ----------
[6f951ca]472    filename : str
[274f9ef]473        The name of the file to be removed without notification.
[efdb01a]474
[274f9ef]475    Return
476    ------
[efdb01a]477
478    '''
479    try:
480        os.remove(filename)
481    except OSError as e:
[96e1533]482        # errno.ENOENT  =  no such file or directory
483        if e.errno == errno.ENOENT:
484            pass
485        else:
[efdb01a]486            raise  # re-raise exception if a different error occured
487
488    return
489
490
[ff99eae]491def init128(filepath):
[274f9ef]492    '''Opens and reads the grib file with table 128 information.
493
494    Parameters
495    ----------
[6f951ca]496    filepath : str
[274f9ef]497        Path to file of ECMWF grib table number 128.
498
499    Return
500    ------
[6f951ca]501    table128 : dict
[274f9ef]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.
[efdb01a]505    '''
506    table128 = dict()
[96e1533]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:
[f20af73]518            if data != '' and data[0] != '!':
519                table128[data[0:3]] = data[59:65].strip()
[efdb01a]520
521    return table128
522
523
[ff99eae]524def to_param_id(pars, table):
[274f9ef]525    '''Transform parameter names to parameter ids with ECMWF grib table 128.
526
527    Parameters
528    ----------
[6f951ca]529    pars : str
[274f9ef]530        Addpar argument from CONTROL file in the format of
[8209738]531        parameter names instead of IDs. The parameter short
532        names are separated by "/" and passed as one single string.
[274f9ef]533
[6f951ca]534    table : dict
[274f9ef]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    ------
[6f951ca]541    ipar : list of int
[274f9ef]542        List of addpar parameters from CONTROL file transformed to
543        parameter ids in the format of integer.
[efdb01a]544    '''
[96e1533]545    if not pars:
546        return []
547    if not isinstance(pars, str):
[0f89116]548        pars = str(pars)
[96e1533]549
[efdb01a]550    cpar = pars.upper().split('/')
551    ipar = []
552    for par in cpar:
[6470422]553        par = par.strip()
[79729d5]554        for k, v in table.items():
[6470422]555            if par.isdigit():
556                par = str(int(par)).zfill(3)
[efdb01a]557            if par == k or par == v:
558                ipar.append(int(k))
559                break
[ff99eae]560        else:
[2fb99de]561            print('Warning: par ' + par + ' not found in table 128')
[efdb01a]562
563    return ipar
564
[f20af73]565def to_param_id_with_tablenumber(pars, table):
[8209738]566    '''Transform parameter names to parameter IDs and add table ID.
[f20af73]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
[8209738]574        parameter names instead of ID. The parameter short
575        names are separated by "/" and passed as one single string.
[f20af73]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
[8209738]586        parameter IDs in the format of integer.
[f20af73]587    '''
588    if not pars:
589        return []
590    if not isinstance(pars, str):
[0f89116]591        pars = str(pars)
[f20af73]592
593    cpar = pars.upper().split('/')
594    spar = []
595    for par in cpar:
[79729d5]596        for k, v in table.items():
[6470422]597            if par.isdigit():
598                par = str(int(par)).zfill(3)
[f20af73]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
[ff99eae]607def get_list_as_string(list_obj, concatenate_sign=', '):
[8209738]608    '''Converts a list of arbitrary content into a single string using a given
609    concatenation character.
[991df6a]610
[274f9ef]611    Parameters
612    ----------
[6f951ca]613    list_obj : list of *
[274f9ef]614        A list with arbitrary content.
[991df6a]615
[6f951ca]616    concatenate_sign : str, optional
[274f9ef]617        A string which is used to concatenate the single
618        list elements. Default value is ", ".
[ff99eae]619
[274f9ef]620    Return
621    ------
[6f951ca]622    str_of_list : str
[274f9ef]623        The content of the list as a single string.
[efdb01a]624    '''
[991df6a]625
[96e1533]626    if not isinstance(list_obj, list):
627        list_obj = list(list_obj)
[ff99eae]628    str_of_list = concatenate_sign.join(str(l) for l in list_obj)
[991df6a]629
[ff99eae]630    return str_of_list
[54a8a01]631
632def make_dir(directory):
[96e1533]633    '''Creates a directory.
634
[47be2684]635    If the directory already exists, an information is printed and the creation
[8209738]636    skipped. The program stops only if there is another problem.
[54a8a01]637
[274f9ef]638    Parameters
639    ----------
[6f951ca]640    directory : str
[96e1533]641        The path to directory which should be created.
[274f9ef]642
643    Return
644    ------
[54a8a01]645
646    '''
647    try:
648        os.makedirs(directory)
649    except OSError as e:
[96e1533]650        # errno.EEXIST = directory already exists
651        if e.errno == errno.EEXIST:
[8209738]652            print('INFORMATION: Directory {0} already exists!'.format(directory))
[96e1533]653        else:
654            raise # re-raise exception if a different error occured
[54a8a01]655
656    return
657
[b6ea29e]658def put_file_to_ecserver(ecd, filename):
[274f9ef]659    '''Uses the ecaccess-file-put command to send a file to the ECMWF servers.
[97e09f4]660
[274f9ef]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.
[54a8a01]666
[274f9ef]667    Parameters
668    ----------
[6f951ca]669    ecd : str
[274f9ef]670        The path were the file is stored.
[54a8a01]671
[6f951ca]672    filename : str
[274f9ef]673        The name of the file to send to the ECMWF server.
[54a8a01]674
[274f9ef]675    Return
676    ------
[96e1533]677
[54a8a01]678    '''
679
680    try:
[96e1533]681        subprocess.check_output(['ecaccess-file-put',
[b6ea29e]682                                 ecd + '/' + filename],
[96e1533]683                                stderr=subprocess.STDOUT)
[54a8a01]684    except subprocess.CalledProcessError as e:
[96e1533]685        print('... ERROR CODE: ' + str(e.returncode))
686        print('... ERROR MESSAGE:\n \t ' + str(e))
[54a8a01]687
[2fb99de]688        print('\n... Do you have a valid ecaccess certification key?')
[97e09f4]689        sys.exit('... ECACCESS-FILE-PUT FAILED!')
[96e1533]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!')
[97e09f4]696
[96e1533]697    return
[54a8a01]698
[73d2e4e]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
[b1d07c9]707def submit_job_to_ecserver(target, jobname):
[274f9ef]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    ----------
[6f951ca]718    target : str
[274f9ef]719        The target where the file should be sent to, e.g. the queue.
720
[6f951ca]721    jobname : str
[274f9ef]722        The name of the jobfile to be submitted to the ECMWF server.
723
724    Return
725    ------
[6f951ca]726    job_id : int
[8209738]727        The id number of the job as a reference at the ECMWF server.
[54a8a01]728    '''
729
730    try:
[96e1533]731        job_id = subprocess.check_output(['ecaccess-job-submit', '-queueName',
732                                          target, jobname])
[2fb99de]733
[96e1533]734    except subprocess.CalledProcessError as e:
735        print('... ERROR CODE: ' + str(e.returncode))
736        print('... ERROR MESSAGE:\n \t ' + str(e))
[97e09f4]737
[2fb99de]738        print('\n... Do you have a valid ecaccess certification key?')
[8209738]739        sys.exit('... ecaccess-job-submit FAILED!')
[96e1533]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!')
[8209738]745        sys.exit('... ecaccess-job-submit FAILED!')
[54a8a01]746
[79729d5]747    return job_id.decode()
[095dc73]748
749
750def get_informations(filename):
[8209738]751    '''Extracts basic information from a sample grib file.
[095dc73]752
[8209738]753    This information is needed for later use and the
754    initialization of numpy arrays where data are stored.
[095dc73]755
756    Parameters
757    ----------
[6f951ca]758    filename : str
[095dc73]759            Name of the file which will be opened to extract basic information.
760
761    Return
762    ------
[6f951ca]763    data : dict
[095dc73]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    '''
[2fef1f2]770    from eccodes import codes_grib_new_from_file, codes_get, codes_release
[095dc73]771
772    data = {}
773
774    # --- open file ---
[8209738]775    print("Opening grib file for extraction of information --- %s" % filename)
[c77630a]776    with open(filename, 'rb') as f:
[095dc73]777        # load first message from file
778        gid = codes_grib_new_from_file(f)
779
780        # information needed from grib message
[0f89116]781        keys = ['Ni',
[095dc73]782                'Nj',
783                'latitudeOfFirstGridPointInDegrees',
784                'longitudeOfFirstGridPointInDegrees',
785                'latitudeOfLastGridPointInDegrees',
786                'longitudeOfLastGridPointInDegrees',
787                'jDirectionIncrementInDegrees',
788                'iDirectionIncrementInDegrees',
789                'missingValue',
790               ]
791
[8209738]792        print('\nInformation extracted: ')
[095dc73]793        for key in keys:
794            # Get the value of the key in a grib message.
[0f89116]795            data[key] = codes_get(gid, key)
796            print("%s = %s" % (key, data[key]))
[095dc73]797
798        # Free the memory for the message referred as gribid.
799        codes_release(gid)
800
801    return data
802
803
[2fef1f2]804def get_dimensions(info, purefc, dtime, index_vals, start_date, end_date):
[8209738]805    '''This function specifies the correct dimensions for x, y, and t.
[095dc73]806
807    Parameters
808    ----------
[6f951ca]809    info : dict
[095dc73]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
[6f951ca]816    purefc : int
[2fef1f2]817        Switch for definition of pure forecast mode or not.
818
[6f951ca]819    dtime : str
[2fef1f2]820        Time step in hours.
821
[6f951ca]822    index_vals : list of list of str
[2fef1f2]823        Contains the values from the keys used for a distinct selection
[8209738]824        of GRIB messages in processing the grib files.
[2fef1f2]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
[6f951ca]830    start_date : str
[2fef1f2]831        The start date of the retrieval job.
832
[6f951ca]833    end_date : str
[2fef1f2]834        The end date of the retrieval job.
835
[095dc73]836    Return
837    ------
[6f951ca]838    (ix, jy, it) : tuple of int
[095dc73]839        Dimension in x-direction, y-direction and in time.
840    '''
841
842    ix = info['Ni']
843
844    jy = info['Nj']
845
[2fef1f2]846    if not purefc:
[79729d5]847        it = ((end_date - start_date).days + 1) * 24 // int(dtime)
[2fef1f2]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])
[095dc73]851
852    return (ix, jy, it)
[bf48c8a]853
854
855def execute_subprocess(cmd_list, error_msg='SUBPROCESS FAILED!'):
[8209738]856    '''Executes a command via a subprocess.
[bf48c8a]857
858    Error handling is done if an error occures.
859
860    Parameters
861    ----------
[6f951ca]862    cmd_list : list of str
[47be2684]863        A list of the components for the command line execution.
864        They will be concatenated with blank space for the command
[8209738]865        to be submitted, like ['mv', file1, file2] for mv file1 file2.
[bf48c8a]866
867    Return
868    ------
[6f951ca]869    error_msg : str, optional
[8209738]870        Error message if the subprocess fails.
871        By default it will just say "SUBPROCESS FAILED!".
[bf48c8a]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
[d727af2]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
[47be2684]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