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

ctbtodev
Last change on this file since 44174de was 44174de, checked in by Anne Philipp <anne.philipp@…>, 5 years ago

commented out the WRF parts since they are still under construction and added License SPDX tags

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