source: flex_extract.git/Source/Python/Mods/checks.py @ 8463d78

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

made python3 again, now working

  • Property mode set to 100644
File size: 25.7 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#    This work is licensed under the Creative Commons Attribution 4.0
15#    International License. To view a copy of this license, visit
16#    http://creativecommons.org/licenses/by/4.0/ or send a letter to
17#    Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
18#*******************************************************************************
19'''This module contains check methods for the CONTROL paramaters.
20'''
21
22# ------------------------------------------------------------------------------
23# MODULES
24# ------------------------------------------------------------------------------
25
26from __future__ import print_function
27import os
28import sys
29
30import _config
31try:
32    import exceptions
33except ImportError:
34    import builtins as exceptions
35from datetime import datetime
36from Mods.tools import my_error, silent_remove
37# ------------------------------------------------------------------------------
38# FUNCTIONS
39# ------------------------------------------------------------------------------
40
41def check_logicals_type(c, logicals):
42    '''Check that the logical variables have correct type integer.
43
44    Parameters
45    ----------
46    c : ControlFile
47        Contains all the parameters of CONTROL file and
48        command line.
49
50    logicals : list of (str or int)
51        Names of the switches that are used to control the flow of the
52        program.
53
54    Return
55    ------
56
57    '''
58
59    for var in logicals:
60        if not isinstance(getattr(c, var), int):
61            setattr(c, var, int(getattr(c, var)))
62
63    return
64
65def check_grid(grid):
66    '''Convert grid into correct Lat/Lon format. E.g. '0.5/0.5'
67
68    Checks on format of original grid. Wether it is in the order of 1000 or 1.
69    Convert to correct grid format and substitute into "Lat/Lon" format string.
70
71    Parameters
72    ----------
73    grid : str
74        Contains grid information
75
76    Return
77    ------
78    grid : str
79        Contains grid in format Lat/lon. E.g. 0.1/0.1
80    '''
81
82    if 'N' in grid:
83        return grid
84    if '/' in grid:
85        gridx, gridy = grid.split('/')
86        if gridx == gridy:
87            grid = gridx
88        else:
89            raise ValueError('GRID parameter contains two '
90                             'different values: %s' (grid))
91    # # determine grid format
92    # if float(grid) / 100. >= 0.5:
93    #    # grid is defined in 1/1000 degrees; old format
94    #    grid = '{}/{}'.format(float(grid) / 1000.,
95    #                          float(grid) / 1000.)
96    # elif float(grid) / 100. < 0.5:
97    #    # grid is defined in normal degree; new format
98    #    grid = '{}/{}'.format(float(grid), float(grid))
99
100
101    # determine grid format
102    # assumes that nobody wants grid spacings of 20 deg or more
103    if float(grid) >= 20.:
104        # grid is defined in 1/1000 degree; old format
105        grid = '{}/{}'.format(float(grid) / 1000., float(grid) / 1000.)
106    else:
107        # grid is defined in degree; new format
108        grid = '{}/{}'.format(float(grid), float(grid))
109
110
111    return grid
112
113def check_area(grid, area, upper, lower, left , right):
114    '''Defines the correct area string.
115
116    Checks on the format of the four area components. Wether it is of
117    the order of 1000 or 1. Also checks wether area was already set by command
118    line, then the four components are overwritten.
119    Convert to correct format of the order of magnitude "1" and sets the
120    area parameter (North/West/South/East).
121    E.g.: -5./20./10./10.
122
123    Parameters
124    ----------
125    grid : str
126        Contains grid information.
127
128    area : str
129        Contains area informtion.
130
131    upper : str
132        The northern most latitude.
133
134    lower : str
135        The souther most latitude.
136
137    left : str
138        The western most longitude.
139
140    right : str
141        The eastern most longiude.
142
143    Return
144    ------
145    grid : str
146        Contains grid in format Lat/lon. E.g. 0.1/0.1
147    '''
148    if 'N' in grid:  # Gaussian output grid
149        area = 'G'
150        return area
151
152    # if area was provided decompose area into its 4 components
153    if area:
154        components = area.split('/')
155        upper, left, lower, right = components
156
157    # determine area format
158    if ((abs(float(upper) / 10000.) >= 0.01 or float(upper) / 1000. == 0. ) and
159        (abs(float(lower) / 10000.) >= 0.01 or float(lower) / 1000. == 0. ) and
160        (abs(float(left) / 10000.) >= 0.01 or float(left) / 1000. == 0. ) and
161        (abs(float(right) / 10000.) >= 0.01 or float(right) / 1000. == 0.)):
162        # area is defined in 1/1000 degrees; old format
163        area = '{}/{}/{}/{}'.format(float(upper) / 1000.,
164                                    float(left) / 1000.,
165                                    float(lower) / 1000.,
166                                    float(right) / 1000.)
167    elif (abs(float(upper) / 10000.) < 0.05 and
168          abs(float(lower) / 10000.) < 0.05 and
169          abs(float(left) / 10000.) < 0.05 and
170          abs(float(right) / 10000.) < 0.05):
171        # area is already in new format
172        area = '{}/{}/{}/{}'.format(float(upper),
173                                    float(left),
174                                    float(lower),
175                                    float(right))
176    else:
177        raise ValueError('The area components have different '
178                         'formats (upper, lower, left, right): '
179                         '{}/{}/{}/{}'.format(str(upper), str(lower),
180                                              str(left) , str(right)))
181
182    return area
183
184def check_levels(levelist, level):
185    '''Defines correct level list and guarantees that the maximum level is
186    one of the available maximum levels.
187
188    Parameters
189    ----------
190    levelist : str
191        Specifies the level list.
192        Examples: model level: 1/to/137, pressure levels: 500/to/1000
193
194    level : str
195        Specifies the maximum level.
196
197    Return
198    ------
199    levelist : str
200        Specifies the required levels. It has to have a valid
201        correspondence to the selected levtype.
202        Examples: model level: 1/to/137, pressure levels: 500/to/1000
203
204    level : str
205        Specifies the maximum level. It has to be one of the
206        available maximum level number as contained in the variable
207        MAX_LEVEL_LIST in "_config". E.g. [16, 19, 31, 40, 50, 60, 62, 91, 137]
208
209    '''
210    # assure consistency of levelist and level
211    if not levelist and not level:
212        raise ValueError('ERROR: neither levelist nor level '
213                         'specified in CONTROL file')
214    elif not levelist and level:
215        levelist = '1/to/' + level
216    elif (levelist and not level) or \
217         (levelist[-1] != level[-1]):
218        level = levelist.split('/')[-1]
219    else:
220        pass
221
222    # check if max level is a valid level
223    if int(level) not in _config.MAX_LEVEL_LIST:
224        raise ValueError('ERROR: \n'
225                         'LEVEL must be the maximum level of a specified '
226                         'level list from ECMWF, e.g. {} \n'
227                         'Check parameter "LEVEL" or the max level of '
228                         '"LEVELIST"!'.format(str(_config.MAX_LEVEL_LIST)))
229
230    return levelist, level
231
232
233def check_ppid(c, ppid):
234    '''Sets the current PPID.
235
236    Parameters
237    ----------
238    c : ControlFile
239            Contains all the parameters of CONTROL file and
240            command line.
241
242    ppid : int or None
243        Contains the ppid number provided by the command line parameter
244        of is None otherwise.
245
246    Return
247    ------
248
249    '''
250
251    if not ppid:
252        c.ppid = str(os.getppid())
253    else:
254        c.ppid = ppid
255
256    return
257
258
259def check_purefc(ftype):
260    '''Check for a pure forecast mode.
261
262    Parameters
263    ----------
264    ftype : list of str
265        List of field types.
266
267    Return
268    ------
269    True or False:
270        True if pure forecasts are to be retrieved. False if there are
271        analysis fields in between.
272    '''
273
274    if 'AN' not in ftype and '4V' not in ftype:
275        # pure forecast
276        return 1
277
278    return 0
279
280
281def check_step(step, mailfail):
282    '''Checks on step format and convert into a list of steps.
283
284    If the steps were defined with "to" and "by" they are converted into
285    a list of steps. If the steps were set in a string, it is
286    converted into a list.
287
288    Parameters
289    ----------
290    step : list of str or str
291        Specifies the forecast time step from forecast base time.
292        Valid values are hours (HH) from forecast base time.
293
294    mailfail : list of str
295        Contains all email addresses which should be notified.
296        It might also contain just the ecmwf user name which will trigger
297        mailing to the associated email address for this user.
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, mailfail):
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    mailfail : list of str
860        Contains all email addresses which should be notified.
861        It might also contain just the ecmwf user name which will trigger
862        mailing to the associated email address for this user.
863
864    Return
865    ------
866    number : str
867        String with list of ensemble member forecast runs. E.g. '01/02/03/04'
868    '''
869
870    if '/' in number:
871        numbers = number.split('/')
872        if 'to' in number.lower() and 'by' in number.lower():
873            number = '{:0>3}'.format(int(numbers[0])) + '/TO/' + \
874                     '{:0>3}'.format(int(numbers[2])) + '/BY/' + \
875                     '{:0>3}'.format(int(numbers[4]))
876        elif 'to' in number.lower() and 'by' not in number.lower():
877            number = '{:0>3}'.format(int(numbers[0])) + '/TO/' + \
878                     '{:0>3}'.format(int(numbers[2]))
879        else:
880            numbers = ['{:0>3}'.format(i) for i in numbers]
881            number = '{:0>3}/'.join(numbers)
882    elif number.isdigit():
883        number = '{:0>3}'.format(int(number))
884    else:
885        pass
886
887    return number
Note: See TracBrowser for help on using the repository browser.
hosted by ZAMG