source: flex_extract.git/Source/Python/Mods/checks.py @ 0f89116

ctbtodev
Last change on this file since 0f89116 was 0f89116, checked in by Anne Philipp <anne.philipp@…>, 4 years ago

diverse changes due to PEP8 style guide and eliminating grib2flexpart; removed unused parameter

  • Property mode set to 100644
File size: 25.4 KB
Line 
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3#*******************************************************************************
4# @Author: Anne Philipp (University of Vienna)
5#
6# @Date: November 2018
7#
8# @Change History:
9#
10# @License:
11#    (C) Copyright 2014-2019.
12#    Anne Philipp, Leopold Haimberger
13#
14#    SPDX-License-Identifier: CC-BY-4.0
15#
16#    This work is licensed under the Creative Commons Attribution 4.0
17#    International License. To view a copy of this license, visit
18#    http://creativecommons.org/licenses/by/4.0/ or send a letter to
19#    Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
20#*******************************************************************************
21'''This module contains check methods for the CONTROL paramaters.
22'''
23
24# ------------------------------------------------------------------------------
25# MODULES
26# ------------------------------------------------------------------------------
27
28from __future__ import print_function
29import os
30import sys
31from datetime import datetime
32# pylint: disable=unused-import
33try:
34    import exceptions
35except ImportError:
36    import builtins as exceptions
37# pylint: enable=unused-import
38
39# software specific classes and modules from flex_extract
40import _config
41from Mods.tools import my_error, silent_remove
42# ------------------------------------------------------------------------------
43# FUNCTIONS
44# ------------------------------------------------------------------------------
45
46def check_logicals_type(c, logicals):
47    '''Check that the logical variables have correct type integer.
48
49    Parameters
50    ----------
51    c : ControlFile
52        Contains all the parameters of CONTROL file and
53        command line.
54
55    logicals : list of (str or int)
56        Names of the switches that are used to control the flow of the
57        program.
58
59    Return
60    ------
61
62    '''
63
64    for var in logicals:
65        if not isinstance(getattr(c, var), int):
66            setattr(c, var, int(getattr(c, var)))
67
68    return
69
70def check_grid(grid):
71    '''Convert grid into correct Lat/Lon format. E.g. '0.5/0.5'
72
73    Checks on format of original grid. Wether it is in the order of 1000 or 1.
74    Convert to correct grid format and substitute into "Lat/Lon" format string.
75
76    Parameters
77    ----------
78    grid : str
79        Contains grid information
80
81    Return
82    ------
83    grid : str
84        Contains grid in format Lat/lon. E.g. 0.1/0.1
85    '''
86
87    if 'N' in grid:
88        return grid
89    if '/' in grid:
90        gridx, gridy = grid.split('/')
91        if gridx == gridy:
92            grid = gridx
93        else:
94            raise ValueError('GRID parameter contains two '
95                             'different values: %s' (grid))
96    # # determine grid format
97    # if float(grid) / 100. >= 0.5:
98    #    # grid is defined in 1/1000 degrees; old format
99    #    grid = '{}/{}'.format(float(grid) / 1000.,
100    #                          float(grid) / 1000.)
101    # elif float(grid) / 100. < 0.5:
102    #    # grid is defined in normal degree; new format
103    #    grid = '{}/{}'.format(float(grid), float(grid))
104
105
106    # determine grid format
107    # assumes that nobody wants grid spacings of 20 deg or more
108    if float(grid) >= 20.:
109        # grid is defined in 1/1000 degree; old format
110        grid = '{}/{}'.format(float(grid) / 1000., float(grid) / 1000.)
111    else:
112        # grid is defined in degree; new format
113        grid = '{}/{}'.format(float(grid), float(grid))
114
115
116    return grid
117
118def check_area(grid, area, upper, lower, left, right):
119    '''Defines the correct area string.
120
121    Checks on the format of the four area components. Wether it is of
122    the order of 1000 or 1. Also checks wether area was already set by command
123    line, then the four components are overwritten.
124    Convert to correct format of the order of magnitude "1" and sets the
125    area parameter (North/West/South/East).
126    E.g.: -5./20./10./10.
127
128    Parameters
129    ----------
130    grid : str
131        Contains grid information.
132
133    area : str
134        Contains area informtion.
135
136    upper : str
137        The northern most latitude.
138
139    lower : str
140        The souther most latitude.
141
142    left : str
143        The western most longitude.
144
145    right : str
146        The eastern most longiude.
147
148    Return
149    ------
150    grid : str
151        Contains grid in format Lat/lon. E.g. 0.1/0.1
152    '''
153    if 'N' in grid:  # Gaussian output grid
154        area = 'G'
155        return area
156
157    # if area was provided decompose area into its 4 components
158    if area:
159        components = area.split('/')
160        upper, left, lower, right = components
161
162    # determine area format
163    if all([(abs(float(upper) / 10000.) >= 0.01 or float(upper) / 1000. == 0.),
164            (abs(float(lower) / 10000.) >= 0.01 or float(lower) / 1000. == 0.),
165            (abs(float(left) / 10000.) >= 0.01 or float(left) / 1000. == 0.),
166            (abs(float(right) / 10000.) >= 0.01 or float(right) / 1000. == 0.)]):
167        # area is defined in 1/1000 degrees; old format
168        area = '{}/{}/{}/{}'.format(float(upper) / 1000.,
169                                    float(left) / 1000.,
170                                    float(lower) / 1000.,
171                                    float(right) / 1000.)
172    elif all([abs(float(upper) / 10000.) < 0.05,
173              abs(float(lower) / 10000.) < 0.05,
174              abs(float(left) / 10000.) < 0.05,
175              abs(float(right) / 10000.) < 0.05]):
176        # area is already in new format
177        area = '{}/{}/{}/{}'.format(float(upper),
178                                    float(left),
179                                    float(lower),
180                                    float(right))
181    else:
182        raise ValueError('The area components have different '
183                         'formats (upper, lower, left, right): '
184                         '{}/{}/{}/{}'.format(str(upper), str(lower),
185                                              str(left), str(right)))
186
187    return area
188
189def check_levels(levelist, level):
190    '''Defines correct level list and guarantees that the maximum level is
191    one of the available maximum levels.
192
193    Parameters
194    ----------
195    levelist : str
196        Specifies the level list.
197        Examples: model level: 1/to/137, pressure levels: 500/to/1000
198
199    level : str
200        Specifies the maximum level.
201
202    Return
203    ------
204    levelist : str
205        Specifies the required levels. It has to have a valid
206        correspondence to the selected levtype.
207        Examples: model level: 1/to/137, pressure levels: 500/to/1000
208
209    level : str
210        Specifies the maximum level. It has to be one of the
211        available maximum level number as contained in the variable
212        MAX_LEVEL_LIST in "_config". E.g. [16, 19, 31, 40, 50, 60, 62, 91, 137]
213
214    '''
215    # assure consistency of levelist and level
216    if not levelist and not level:
217        raise ValueError('ERROR: neither levelist nor level '
218                         'specified in CONTROL file')
219    elif not levelist and level:
220        levelist = '1/to/' + level
221    elif (levelist and not level) or \
222         (levelist[-1] != level[-1]):
223        level = levelist.split('/')[-1]
224    else:
225        pass
226
227    # check if max level is a valid level
228    if int(level) not in _config.MAX_LEVEL_LIST:
229        raise ValueError('ERROR: \n'
230                         'LEVEL must be the maximum level of a specified '
231                         'level list from ECMWF, e.g. {} \n'
232                         'Check parameter "LEVEL" or the max level of '
233                         '"LEVELIST"!'.format(str(_config.MAX_LEVEL_LIST)))
234
235    return levelist, level
236
237
238def check_ppid(c, ppid):
239    '''Sets the current PPID.
240
241    Parameters
242    ----------
243    c : ControlFile
244            Contains all the parameters of CONTROL file and
245            command line.
246
247    ppid : int or None
248        Contains the ppid number provided by the command line parameter
249        of is None otherwise.
250
251    Return
252    ------
253
254    '''
255
256    if not ppid:
257        c.ppid = str(os.getppid())
258    else:
259        c.ppid = ppid
260
261    return
262
263
264def check_purefc(ftype):
265    '''Check for a pure forecast mode.
266
267    Parameters
268    ----------
269    ftype : list of str
270        List of field types.
271
272    Return
273    ------
274    True or False:
275        True if pure forecasts are to be retrieved. False if there are
276        analysis fields in between.
277    '''
278
279    if 'AN' not in ftype and '4V' not in ftype:
280        # pure forecast
281        return 1
282
283    return 0
284
285
286def check_step(step):
287    '''Checks on step format and convert into a list of steps.
288
289    If the steps were defined with "to" and "by" they are converted into
290    a list of steps. If the steps were set in a string, it is
291    converted into a list.
292
293    Parameters
294    ----------
295    step : list of str or str
296        Specifies the forecast time step from forecast base time.
297        Valid values are hours (HH) from forecast base time.
298
299    Return
300    ------
301    step : list of str
302        List of forecast steps in format e.g. [001, 002, ...]
303    '''
304    import numpy as np
305
306    if '/' in step:
307        steps = step.split('/')
308        if 'to' in step.lower() and 'by' in step.lower():
309            ilist = np.arange(int(steps[0]),
310                              int(steps[2]) + 1,
311                              int(steps[4]))
312            step = ['{:0>3}'.format(i) for i in ilist]
313        elif 'to' in step.lower() and 'by' not in step.lower():
314            my_error(step + ':\n' +
315                     'if "to" is used in steps parameter, '
316                     'please use "by" as well')
317        else:
318            step = steps
319
320    if not isinstance(step, list):
321        step = [step]
322
323    return step
324
325def check_type(ftype, steps):
326    '''Check if type variable is of type list and if analysis field has
327    forecast step 0.
328
329    Parameters
330    ----------
331    ftype : list of str or str
332        List of field types.
333
334    steps : str
335        Specifies the forecast time step from forecast base time.
336        Valid values are hours (HH) from forecast base time.
337
338    Return
339    ------
340    ftype : list of str
341        List of field types.
342    '''
343    if not isinstance(ftype, list):
344        ftype = [ftype]
345
346    for i, val in enumerate(ftype):
347        if ftype[i] == 'AN' and int(steps[i]) != 0:
348            print('Analysis retrievals must have STEP = 0 (now set to 0)')
349            ftype[i] = 0
350
351    return ftype
352
353def check_time(ftime):
354    '''Check if time variable is of type list. Otherwise convert to list.
355
356    Parameters
357    ----------
358    ftime : list of str or str
359        The time in hours of the field.
360
361    Return
362    ------
363    ftime : list of str
364        The time in hours of the field.
365    '''
366    if not isinstance(ftime, list):
367        ftime = [ftime]
368
369    return ftime
370
371def check_len_type_time_step(ftype, ftime, steps, maxstep, purefc):
372    '''Check if
373
374    Parameters
375    ----------
376    ftype : list of str
377        List of field types.
378
379    ftime : list of str or str
380        The time in hours of the field.
381
382    steps : str
383        Specifies the forecast time step from forecast base time.
384        Valid values are hours (HH) from forecast base time.
385
386    maxstep : int
387        The maximum forecast time step in hours from the forecast base time.
388        This is the maximum step for non flux (accumulated) forecast data.
389
390    purefc : int
391        Switch for definition of pure forecast mode or not.
392
393    Return
394    ------
395    ftype : list of str
396        List of field types.
397
398    ftime : list of str
399        The time in hours of the field.
400
401    steps : str
402        Specifies the forecast time step from forecast base time.
403        Valid values are hours (HH) from forecast base time.
404    '''
405    if not len(ftype) == len(ftime) == len(steps):
406        raise ValueError('ERROR: The number of field types, times and steps '
407                         'are not the same! Please check the setting in the '
408                         'CONTROL file!')
409
410    # if pure forecast is selected and only one field type/time is set
411    # prepare a complete list of type/time/step combination upto maxstep
412    if len(ftype) == 1 and purefc:
413        nftype = []
414        nsteps = []
415        nftime = []
416        for i in range(0, maxstep + 1):
417            nftype.append(ftype[0])
418            nsteps.append('{:0>3}'.format(i))
419            nftime.append(ftime[0])
420        return nftype, nftime, nsteps
421
422    return ftype, ftime, steps
423
424def check_mail(mail):
425    '''Check the string of mail addresses, seperate them and convert to a list.
426
427    Parameters
428    ----------
429    mail : list of str or str
430        Contains email addresses for notifications.
431        It might also contain just the ecmwf user name which will trigger
432        mailing to the associated email address for this user.
433
434    Return
435    ------
436    mail : list of str
437        Contains email addresses for notifications.
438        It might also contain just the ecmwf user name which will trigger
439        mailing to the associated email address for this user.
440
441    '''
442    if not isinstance(mail, list):
443        if ',' in mail:
444            mail = mail.split(',')
445        elif ' ' in mail:
446            mail = mail.split()
447        else:
448            mail = [mail]
449
450    return mail
451
452def check_queue(queue, gateway, destination, ecuid, ecgid):
453    '''Check if the necessary ECMWF parameters are set if the queue is
454    one of the QUEUES_LIST (in _config).
455
456    Parameters
457    ----------
458    queue : str
459        Name of the queue if submitted to the ECMWF servers.
460        Used to check if ecuid, ecgid, gateway and destination
461        are set correctly and are not empty.
462
463    gateway : str
464        The address of the gateway server.
465
466    destination : str
467        The name of the destination of the gateway server for data
468        transfer through ectrans. E.g. name@genericSftp
469
470    ecuid : str
471        ECMWF user id.
472
473    ecgid : str
474        ECMWF group id.
475
476    Return
477    ------
478
479    '''
480    if queue in _config.QUEUES_LIST and \
481            (not gateway or not destination or
482             not ecuid or not ecgid):
483        raise ValueError('\nEnvironment variables GATEWAY, DESTINATION, ECUID '
484                         'and ECGID were not set properly! \n '
485                         'Please check for existence of file "ECMWF_ENV" '
486                         'in the run directory!')
487    return
488
489def check_pathes(idir, odir, fpdir, fedir):
490    '''Check if output and flexpart pathes are set.
491
492    Parameters
493    ----------
494    idir : str
495        Path to the temporary directory for MARS retrieval data.
496
497    odir : str
498        Path to the final output directory where the FLEXPART input files
499        will be stored.
500
501    fpdir : str
502        Path to FLEXPART root directory.
503
504    fedir : str
505        Path to flex_extract root directory.
506
507    Return
508    ------
509    odir : str
510        Path to the final output directory where the FLEXPART input files
511        will be stored.
512
513    fpdir : str
514        Path to FLEXPART root directory.
515
516    '''
517    if not fpdir:
518        fpdir = fedir
519
520    if not odir:
521        odir = idir
522
523    return odir, fpdir
524
525def check_dates(start, end):
526    '''Checks if there is at least a start date for a one day retrieval.
527
528    Checks if end date lies after start date and end date is set.
529
530    Parameters
531    ----------
532    start : str
533        The start date of the retrieval job.
534
535    end : str
536        The end date of the retrieval job.
537
538    Return
539    ------
540    start : str
541        The start date of the retrieval job.
542
543    end : str
544        The end date of the retrieval job.
545
546    '''
547    # check for having at least a starting date
548    # otherwise program is not allowed to run
549    if not start:
550        raise ValueError('start_date was neither specified in command line nor '
551                         'in CONTROL file.\n'
552                         'Try "{} -h" to print usage information'
553                         .format(sys.argv[0].split('/')[-1]))
554
555    # retrieve just one day if end_date isn't set
556    if not end:
557        end = start
558
559    dstart = datetime.strptime(start, '%Y%m%d')
560    dend = datetime.strptime(end, '%Y%m%d')
561    if dstart > dend:
562        raise ValueError('ERROR: Start date is after end date! \n'
563                         'Please adapt the dates in CONTROL file or '
564                         'command line! (start={}; end={})'.format(start, end))
565
566    return start, end
567
568def check_maxstep(maxstep, steps):
569    '''Convert maxstep into integer if it is already given. Otherwise, select
570    maxstep by going through the steps list.
571
572    Parameters
573    ----------
574    maxstep : str
575        The maximum forecast time step in hours from the forecast base time.
576        This is the maximum step for non flux (accumulated) forecast data.
577
578    steps : str
579        Specifies the forecast time step from forecast base time.
580        Valid values are hours (HH) from forecast base time.
581
582    Return
583    ------
584    maxstep : int
585        The maximum forecast time step in hours from the forecast base time.
586        This is the maximum step for non flux (accumulated) forecast data.
587
588    '''
589    # if maxstep wasn't provided
590    # search for it in the "step" parameter
591    if not maxstep:
592        maxstep = 0
593        for s in steps:
594            if int(s) > maxstep:
595                maxstep = int(s)
596    else:
597        maxstep = int(maxstep)
598
599    return maxstep
600
601def check_basetime(basetime):
602    '''Check if basetime is set and contains one of the two
603    possible values (0, 12).
604
605    Parameters
606    ----------
607    basetime : int or str or None
608        The time for a half day retrieval. The 12 hours upfront are to be
609        retrieved.
610
611    Return
612    ------
613    basetime : int or None
614        The time for a half day retrieval. The 12 hours upfront are to be
615        retrieved.
616    '''
617    if basetime is not None:
618        basetime = int(basetime)
619        if basetime != 0 and basetime != 12:
620            raise ValueError('ERROR: Basetime has an invalid value '
621                             '-> {}'.format(str(basetime)))
622    return basetime
623
624def check_request(request, marsfile):
625    '''Check if there is an old mars request file and remove it.
626
627    Parameters
628    ----------
629    request : int
630        Selects the mode of retrieval.
631        0: Retrieves the data from ECMWF.
632        1: Prints the mars requests to an output file.
633        2: Retrieves the data and prints the mars request.
634
635    marsfile : str
636        Path to the mars request file.
637
638    Return
639    ------
640
641    '''
642    if request != 0:
643        if os.path.isfile(marsfile):
644            silent_remove(marsfile)
645    return
646
647def check_public(public, dataset):
648    '''Check wether the dataset parameter is set for a
649    public data set retrieval.
650
651    Parameters
652    ----------
653    public : int
654        Specifies if public data are to be retrieved or not.
655
656    dataset : str
657        Specific name which identifies the public dataset.
658
659    Return
660    ------
661
662    '''
663    if public and not dataset:
664        raise ValueError('ERROR: If public mars data wants to be retrieved, '
665                         'the "dataset"-parameter has to be set too!')
666    return
667
668def check_acctype(acctype, ftype):
669    '''Guarantees that the accumulation field type is set.
670
671    If not set, it is derivated as in the old method (TYPE[1]).
672
673    Parameters
674    ----------
675    acctype : str
676        The field type for the accumulated forecast fields.
677
678    ftype : list of str
679        List of field types.
680
681    Return
682    ------
683    acctype : str
684        The field type for the accumulated forecast fields.
685    '''
686    if not acctype:
687        print('... Control parameter ACCTYPE was not defined.')
688        try:
689            if len(ftype) == 1 and ftype[0] != 'AN':
690                print('... Use same field type as for the non-flux fields.')
691                acctype = ftype[0]
692            elif len(ftype) > 1 and ftype[1] != 'AN':
693                print('... Use old setting by using TYPE[1] for flux forecast!')
694                acctype = ftype[1]
695        except:
696            raise ValueError('ERROR: Accumulation field type could not be set!')
697    else:
698        if acctype.upper() == 'AN':
699            raise ValueError('ERROR: Accumulation forecast fields can not be '
700                             'of type "analysis"!')
701    return acctype
702
703
704def check_acctime(acctime, marsclass, purefc, time):
705    '''Guarantees that the accumulation forecast times were set.
706
707    If it is not set, it tries to set the value for some of the
708    most commonly used data sets. Otherwise it raises an error.
709
710    Parameters
711    ----------
712    acctime : str
713        The starting time from the accumulated forecasts.
714
715    marsclass : str
716        ECMWF data classification identifier.
717
718    purefc : int
719        Switch for definition of pure forecast mode or not.
720
721    Return
722    ------
723    acctime : str
724        The starting time from the accumulated forecasts.
725    '''
726
727    if not acctime:
728        print('... Control parameter ACCTIME was not defined.')
729        print('... Value will be set depending on field type:\n '
730              '\t\t EA=06/18\n\t\t EI/OD=00/12\n\t\t EP=18')
731        if marsclass.upper() == 'EA': # Era 5
732            acctime = '06/18'
733        elif marsclass.upper() == 'EI': # Era-Interim
734            acctime = '00/12'
735        elif marsclass.upper() == 'EP': # CERA
736            acctime = '18'
737        elif marsclass.upper() == 'OD' and not purefc: # On-demand
738            acctime = '00/12'
739        elif marsclass.upper() == 'OD' and purefc: # On-demand
740            acctime = time[0]
741        else:
742            raise ValueError('ERROR: Accumulation forecast time can not '
743                             'automatically be derived!')
744    return acctime
745
746def check_accmaxstep(accmaxstep, marsclass, purefc, maxstep):
747    '''Guarantees that the accumulation forecast step were set.
748
749    Parameters
750    ----------
751    accmaxstep : str
752        The maximum forecast step for the accumulated forecast fields.
753
754    marsclass : str
755        ECMWF data classification identifier.
756
757    purefc : int
758        Switch for definition of pure forecast mode or not.
759
760    maxstep : str
761        The maximum forecast time step in hours from the forecast base time.
762        This is the maximum step for non flux (accumulated) forecast data.
763
764    Return
765    ------
766    accmaxstep : str
767        The maximum forecast step for the accumulated forecast fields.
768    '''
769    if not accmaxstep:
770        print('... Control parameter ACCMAXSTEP was not defined.')
771        print('... Value will be set depending on field type/time: '
772              '\n\t\t EA/EI/OD=12\n\t\t EP=24')
773        if marsclass.upper() in ['EA', 'EI', 'OD'] and not purefc:
774            # Era 5, Era-Interim, On-demand operational
775            accmaxstep = '12'
776        elif marsclass.upper() == 'EP': # CERA
777            accmaxstep = '24'
778        elif purefc and accmaxstep != maxstep:
779            accmaxstep = maxstep
780            print('... For pure forecast mode, the accumulated forecast must '
781                  'have the same maxstep as the normal forecast fields!\n'
782                  '\t\t Accmaxstep was set to maxstep!')
783        else:
784            raise ValueError('ERROR: Accumulation forecast step can not '
785                             'automatically be derived!')
786    else:
787        if purefc and int(accmaxstep) != int(maxstep):
788            accmaxstep = maxstep
789            print('... For pure forecast mode, the accumulated forecast must '
790                  'have the same maxstep as the normal forecast fields!\n'
791                  '\t\t Accmaxstep was set to maxstep!')
792    return accmaxstep
793
794def check_addpar(addpar):
795    '''Check that addpar has correct format of additional parameters in
796    a single string, so that it can be easily appended to the hard coded
797    parameters that are retrieved in any case.
798
799    Parameters
800    ----------
801    addpar : str or list of str
802        List of additional parameters to be retrieved.
803
804    Return
805    ------
806    addpar : str
807        List of additional parameters to be retrieved.
808    '''
809
810    if addpar and isinstance(addpar, str):
811        if '/' in addpar:
812            parlist = addpar.split('/')
813            parlist = [p for p in parlist if p is not '']
814        else:
815            parlist = [addpar]
816
817        addpar = '/' + '/'.join(parlist)
818
819    return addpar
820
821
822def check_job_chunk(job_chunk):
823    '''Checks that if job chunk is set, the number is positive and non zero.
824
825    Parameters
826    ----------
827    job_chunk : int
828        The number of days for a single job script.
829
830    Return
831    ------
832    job_chunk : int
833        The number of days for a single job script.
834    '''
835    if not job_chunk:
836        return job_chunk
837    else:
838        job_chunk = int(job_chunk)
839
840    if job_chunk < 0:
841        raise ValueError('ERROR: The number of job chunk is negative!\n'
842                         'It has to be a positive number!')
843    elif job_chunk == 0:
844        job_chunk = None
845    else:
846        pass
847
848    return job_chunk
849
850
851def check_number(number):
852    '''Check for correct string format of ensemble member numbers.
853
854    Parameters
855    ----------
856    number : str
857        List of ensemble member forecast runs.
858
859    Return
860    ------
861    number : str
862        String with list of ensemble member forecast runs. E.g. '01/02/03/04'
863    '''
864
865    if '/' in number:
866        numbers = number.split('/')
867        if 'to' in number.lower() and 'by' in number.lower():
868            number = '{:0>3}'.format(int(numbers[0])) + '/TO/' + \
869                     '{:0>3}'.format(int(numbers[2])) + '/BY/' + \
870                     '{:0>3}'.format(int(numbers[4]))
871        elif 'to' in number.lower() and 'by' not in number.lower():
872            number = '{:0>3}'.format(int(numbers[0])) + '/TO/' + \
873                     '{:0>3}'.format(int(numbers[2]))
874        else:
875            numbers = ['{:0>3}'.format(i) for i in numbers]
876            number = '{:0>3}/'.join(numbers)
877    elif number.isdigit():
878        number = '{:0>3}'.format(int(number))
879    else:
880        pass
881
882    return number
Note: See TracBrowser for help on using the repository browser.
hosted by ZAMG