source: flex_extract.git/python/pythontest/TestInstallTar/flex_extract_v7.1/python/tools.py @ 2fb99de

ctbtodev
Last change on this file since 2fb99de was 2fb99de, checked in by Anne Philipp <anne.philipp@…>, 6 years ago

introduced config with path definitions and changed py files accordingly; Installation works; some tests were added for tarball making; Problems in submission to ecgate

  • Property mode set to 100644
File size: 17.1 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#*******************************************************************************
4# @Author: Anne Philipp (University of Vienna)
5#
6# @Date: May 2018
7#
8# @Change History:
9#    October 2014 - Anne Fouilloux (University of Oslo)
10#        - created functions silent_remove and product (taken from ECMWF)
11#
12#    November 2015 - Leopold Haimberger (University of Vienna)
13#        - created functions: interpret_args_and_control, clean_up
14#          my_error, normal_exit, init128, to_param_id
15#
16#    April 2018 - Anne Philipp (University of Vienna):
17#        - applied PEP8 style guide
18#        - added documentation
19#        - moved all functions from file Flexparttools to this file tools
20#        - added function get_list_as_string
21#        - seperated args and control interpretation
22#
23# @License:
24#    (C) Copyright 2014-2018.
25#
26#    This software is licensed under the terms of the Apache Licence Version 2.0
27#    which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
28#
29# @Modul Description:
30#    This module contains a couple of helpful functions which are
31#    used in different places in flex_extract.
32#
33# @Module Content:
34#    - get_cmdline_arguments
35#    - clean_up
36#    - my_error
37#    - normal_exit
38#    - product
39#    - silent_remove
40#    - init128
41#    - to_param_id
42#    - get_list_as_string
43#    - make_dir
44#
45#*******************************************************************************
46
47# ------------------------------------------------------------------------------
48# MODULES
49# ------------------------------------------------------------------------------
50import os
51import errno
52import sys
53import glob
54import subprocess
55import traceback
56from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
57
58# ------------------------------------------------------------------------------
59# FUNCTIONS
60# ------------------------------------------------------------------------------
61
62def get_cmdline_arguments():
63    '''
64    @Description:
65        Decomposes the command line arguments and assigns them to variables.
66        Apply default values for non mentioned arguments.
67
68    @Input:
69        <nothing>
70
71    @Return:
72        args: instance of ArgumentParser
73            Contains the commandline arguments from script/program call.
74    '''
75
76    parser = ArgumentParser(description='Retrieve FLEXPART input from \
77                                ECMWF MARS archive',
78                            formatter_class=ArgumentDefaultsHelpFormatter)
79
80    # the most important arguments
81    parser.add_argument("--start_date", dest="start_date", default=None,
82                        help="start date YYYYMMDD")
83    parser.add_argument("--end_date", dest="end_date", default=None,
84                        help="end_date YYYYMMDD")
85    parser.add_argument("--date_chunk", dest="date_chunk", default=None,
86                        help="# of days to be retrieved at once")
87
88    # some arguments that override the default in the CONTROL file
89    parser.add_argument("--basetime", dest="basetime", default=None,
90                        help="base such as 00/12 (for half day retrievals)")
91    parser.add_argument("--step", dest="step", default=None,
92                        help="steps such as 00/to/48")
93    parser.add_argument("--levelist", dest="levelist", default=None,
94                        help="Vertical levels to be retrieved, e.g. 30/to/60")
95    parser.add_argument("--area", dest="area", default=None,
96                        help="area defined as north/west/south/east")
97
98    # set the working directories
99    parser.add_argument("--inputdir", dest="inputdir", default=None,
100                        help="root directory for storing intermediate files")
101    parser.add_argument("--outputdir", dest="outputdir", default=None,
102                        help="root directory for storing output files")
103    parser.add_argument("--flexpart_root_scripts", dest="flexpart_root_scripts",
104                        default=None,
105                        help="FLEXPART root directory (to find grib2flexpart \
106                        and COMMAND file)\n Normally flex_extract resides in \
107                        the scripts directory of the FLEXPART distribution")
108
109    # this is only used by prepare_flexpart.py to rerun a postprocessing step
110    parser.add_argument("--ppid", dest="ppid", default=None,
111                        help="specify parent process id for \
112                        rerun of prepare_flexpart")
113
114    # arguments for job submission to ECMWF, only needed by submit.py
115    parser.add_argument("--job_template", dest='job_template',
116                        default="job.temp",
117                        help="job template file for submission to ECMWF")
118    parser.add_argument("--queue", dest="queue", default=None,
119                        help="queue for submission to ECMWF \
120                        (e.g. ecgate or cca )")
121    parser.add_argument("--controlfile", dest="controlfile",
122                        default='CONTROL.temp',
123                        help="file with CONTROL parameters")
124    parser.add_argument("--debug", dest="debug", default=None,
125                        help="debug mode - leave temporary files intact")
126    parser.add_argument("--request", dest="request", default=None,
127                        help="list all mars request in file mars_requests.dat \
128                        and skip submission to mars")
129
130    args = parser.parse_args()
131
132    return args
133
134def read_ecenv(filename):
135    '''
136    @Description:
137        Reads the file into a dictionary where the key values are the parameter
138        names.
139
140    @Input:
141        filename: string
142            Name of file where the ECMWV environment parameters are stored.
143
144    @Return:
145        envs: dict
146    '''
147    envs= {}
148    print filename
149    with open(filename, 'r') as f:
150        for line in f:
151            data = line.strip().split()
152            envs[str(data[0])] = str(data[1])
153
154    return envs
155
156def clean_up(c):
157    '''
158    @Description:
159        Remove all files from intermediate directory
160        (inputdir from CONTROL file).
161
162    @Input:
163        c: instance of class ControlFile
164            Contains all the parameters of CONTROL file, which are e.g.:
165            DAY1(start_date), DAY2(end_date), DTIME, MAXSTEP, TYPE, TIME,
166            STEP, CLASS(marsclass), STREAM, NUMBER, EXPVER, GRID, LEFT,
167            LOWER, UPPER, RIGHT, LEVEL, LEVELIST, RESOL, GAUSS, ACCURACY,
168            OMEGA, OMEGADIFF, ETA, ETADIFF, DPDETA, SMOOTH, FORMAT,
169            ADDPAR, WRF, CWC, PREFIX, ECSTORAGE, ECTRANS, ECFSDIR,
170            MAILOPS, MAILFAIL, GRIB2FLEXPART, FLEXPARTDIR, BASETIME
171            DATE_CHUNK, DEBUG, INPUTDIR, OUTPUTDIR, FLEXPART_ROOT_SCRIPTS
172
173            For more information about format and content of the parameter
174            see documentation.
175
176    @Return:
177        <nothing>
178    '''
179
180    print "clean_up"
181
182    cleanlist = glob.glob(c.inputdir + "/*")
183    for clist in cleanlist:
184        if c.prefix not in clist:
185            silent_remove(clist)
186        if c.ecapi is False and (c.ectrans == '1' or c.ecstorage == '1'):
187            silent_remove(clist)
188
189    print "Done"
190
191    return
192
193
194def my_error(users, message='ERROR'):
195    '''
196    @Description:
197        Prints a specified error message which can be passed to the function
198        before exiting the program.
199
200    @Input:
201        user: list of strings
202            Contains all email addresses which should be notified.
203            It might also contain just the ecmwf user name which wil trigger
204            mailing to the associated email address for this user.
205
206        message: string, optional
207            Error message. Default value is "ERROR".
208
209    @Return:
210        <nothing>
211    '''
212
213    print message
214
215    # comment if user does not want email notification directly from python
216    for user in users:
217        if '${USER}' in user:
218            user = os.getenv('USER')
219        try:
220            p = subprocess.Popen(['mail', '-s flex_extract_v7.1 ERROR',
221                                  os.path.expandvars(user)],
222                                 stdin=subprocess.PIPE,
223                                 stdout=subprocess.PIPE,
224                                 stderr=subprocess.PIPE,
225                                 bufsize=1)
226            trace = '\n'.join(traceback.format_stack())
227            pout = p.communicate(input=message + '\n\n' + trace)[0]
228        except ValueError as e:
229            print 'ERROR: ', e
230            sys.exit('Email could not be sent!')
231        else:
232            print 'Email sent to ' + os.path.expandvars(user) + ' ' + \
233                  pout.decode()
234
235    sys.exit(1)
236
237    return
238
239
240def normal_exit(users, message='Done!'):
241    '''
242    @Description:
243        Prints a specific exit message which can be passed to the function.
244
245    @Input:
246        user: list of strings
247            Contains all email addresses which should be notified.
248            It might also contain just the ecmwf user name which wil trigger
249            mailing to the associated email address for this user.
250
251        message: string, optional
252            Message for exiting program. Default value is "Done!".
253
254    @Return:
255        <nothing>
256
257    '''
258    print message
259
260    # comment if user does not want notification directly from python
261    for user in users:
262        if '${USER}' in user:
263            user = os.getenv('USER')
264        try:
265            p = subprocess.Popen(['mail', '-s flex_extract_v7.1 normal exit',
266                                  os.path.expandvars(user)],
267                                 stdin=subprocess.PIPE,
268                                 stdout=subprocess.PIPE,
269                                 stderr=subprocess.PIPE,
270                                 bufsize=1)
271            pout = p.communicate(input=message+'\n\n')[0]
272        except ValueError as e:
273            print 'ERROR: ', e
274            print 'Email could not be sent!'
275        else:
276            print 'Email sent to ' + os.path.expandvars(user) + ' ' + \
277                  pout.decode()
278
279    return
280
281
282def product(*args, **kwds):
283    '''
284    @Description:
285        This method is taken from an example at the ECMWF wiki website.
286        https://software.ecmwf.int/wiki/display/GRIB/index.py; 2018-03-16
287
288        This method combines the single characters of the passed arguments
289        with each other. So that each character of each argument value
290        will be combined with each character of the other arguments as a tuple.
291
292        Example:
293        product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
294        product(range(2), repeat = 3) --> 000 001 010 011 100 101 110 111
295
296    @Input:
297        *args: tuple
298            Positional arguments (arbitrary number).
299
300        **kwds: dictionary
301            Contains all the keyword arguments from *args.
302
303    @Return:
304        prod: tuple
305            Return will be done with "yield". A tuple of combined arguments.
306            See example in description above.
307    '''
308    pools = map(tuple, args) * kwds.get('repeat', 1)
309    result = [[]]
310    for pool in pools:
311        result = [x + [y] for x in result for y in pool]
312    for prod in result:
313        yield tuple(prod)
314
315    return
316
317
318def silent_remove(filename):
319    '''
320    @Description:
321        Remove file if it exists.
322        The function does not fail if the file does not exist.
323
324    @Input:
325        filename: string
326            The name of the file to be removed without notification.
327
328    @Return:
329        <nothing>
330    '''
331    try:
332        os.remove(filename)
333    except OSError as e:
334        if e.errno != errno.ENOENT:
335            # errno.ENOENT  =  no such file or directory
336            raise  # re-raise exception if a different error occured
337
338    return
339
340
341def init128(filepath):
342    '''
343    @Description:
344        Opens and reads the grib file with table 128 information.
345
346    @Input:
347        filepath: string
348            Path to file of ECMWF grib table number 128.
349
350    @Return:
351        table128: dictionary
352            Contains the ECMWF grib table 128 information.
353            The key is the parameter number and the value is the
354            short name of the parameter.
355    '''
356    table128 = dict()
357    with open(filepath) as f:
358        fdata = f.read().split('\n')
359    for data in fdata:
360        if data[0] != '!':
361            table128[data[0:3]] = data[59:64].strip()
362
363    return table128
364
365
366def to_param_id(pars, table):
367    '''
368    @Description:
369        Transform parameter names to parameter ids
370        with ECMWF grib table 128.
371
372    @Input:
373        pars: string
374            Addpar argument from CONTROL file in the format of
375            parameter names instead of ids. The parameter short
376            names are sepearted with "/" and they are passed as
377            one single string.
378
379        table: dictionary
380            Contains the ECMWF grib table 128 information.
381            The key is the parameter number and the value is the
382            short name of the parameter.
383
384    @Return:
385        ipar: list of integer
386            List of addpar parameters from CONTROL file transformed to
387            parameter ids in the format of integer.
388    '''
389    cpar = pars.upper().split('/')
390    ipar = []
391    for par in cpar:
392        for k, v in table.iteritems():
393            if par == k or par == v:
394                ipar.append(int(k))
395                break
396        else:
397            print 'Warning: par ' + par + ' not found in table 128'
398
399    return ipar
400
401def get_list_as_string(list_obj, concatenate_sign=', '):
402    '''
403    @Description:
404        Converts a list of arbitrary content into a single string.
405
406    @Input:
407        list_obj: list
408            A list with arbitrary content.
409
410        concatenate_sign: string, optional
411            A string which is used to concatenate the single
412            list elements. Default value is ", ".
413
414    @Return:
415        str_of_list: string
416            The content of the list as a single string.
417    '''
418
419    str_of_list = concatenate_sign.join(str(l) for l in list_obj)
420
421    return str_of_list
422
423def make_dir(directory):
424    '''
425    @Description:
426        Creates a directory and gives a warning if the directory
427        already exists. The program stops only if there is another problem.
428
429    @Input:
430        directory: string
431            The directory name including the path which should be created.
432
433    @Return:
434        <nothing>
435    '''
436    try:
437        os.makedirs(directory)
438    except OSError as e:
439        if e.errno != errno.EEXIST:
440            # errno.EEXIST = directory already exists
441            raise # re-raise exception if a different error occured
442        else:
443            print 'WARNING: Directory {0} already exists!'.format(directory)
444
445    return
446
447def put_file_to_ecserver(ecd, filename, target, ecuid, ecgid):
448    '''
449    @Description:
450        Uses the ecaccess-file-put command to send a file to the ECMWF servers.
451
452        NOTE:
453        The return value is just for testing reasons. It does not have
454        to be used from the calling function since the whole error handling
455        is done in here.
456
457    @Input:
458        ecd: string
459            The path were the file is stored.
460
461        filename: string
462            The name of the file to send to the ECMWF server.
463
464        target: string
465            The target queue where the file should be sent to.
466
467        ecuid: string
468            The user id on ECMWF server.
469
470        ecgid: string
471            The group id on ECMWF server.
472
473    @Return:
474        rcode: string
475            Resulting code of command execution. If successful the string
476            will be empty.
477    '''
478
479    try:
480        rcode = subprocess.check_output(['ecaccess-file-put',
481                                          ecd + '/' + filename,
482                                          target + ':/home/ms/' +
483                                          ecgid + '/' + ecuid +
484                                          '/' + filename],
485                                         stderr=subprocess.STDOUT)
486    except subprocess.CalledProcessError as e:
487        print('... ERROR CODE:\n ... ' + str(e.returncode))
488        print('... ERROR MESSAGE:\n ... ' + str(e))
489
490        print('\n... Do you have a valid ecaccess certification key?')
491        sys.exit('... ECACCESS-FILE-PUT FAILED!')
492
493    return rcode
494
495def submit_job_to_ecserver(target, jobname):
496    '''
497    @Description:
498        Uses ecaccess-job-submit command to submit a job to the ECMWF server.
499
500        NOTE:
501        The return value is just for testing reasons. It does not have
502        to be used from the calling function since the whole error handling
503        is done in here.
504
505    @Input:
506        target: string
507            The target where the file should be sent to, e.g. the queue.
508
509        jobname: string
510            The name of the jobfile to be submitted to the ECMWF server.
511
512    @Return:
513        rcode: string
514            Resulting code of command execution. If successful the string
515            will contain an integer number, representing the id of the job
516            at the ecmwf server.
517    '''
518
519    try:
520        rcode = subprocess.check_output(['ecaccess-job-submit',
521                                         '-queueName', target,
522                                         jobname])
523    except subprocess.CalledProcessError as e:
524        print('... ERROR CODE:\n ... ' + str(e.returncode))
525        print('... ERROR MESSAGE:\n ... ' + str(e))
526
527
528        print('\n... Do you have a valid ecaccess certification key?')
529        sys.exit('... ECACCESS-JOB-SUBMIT FAILED!')
530
531    return rcode
Note: See TracBrowser for help on using the repository browser.
hosted by ZAMG