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

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

language corrections in comment sections and print commands

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