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