source: flex_extract.git/Source/Python/Mods/checks.py @ 50f9ca6

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

BUGFIX (ticket #263): Corrected installation check for parameters GATEWAY and DESTINATION

  • 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 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 ecuid or not ecgid):
482        raise ValueError('\nEnvironment variables ECUID '
483                         'and ECGID were not set properly! \n '
484                         'Please check for existence of file "ECMWF_ENV" '
485                         'in the run directory!')
486    elif queue in _config.QUEUES_LIST and \
487             (not gateway or not destination):
488        print('WARNING: Parameters GATEWAY and DESTINATION were '
489              'not properly set for working on ECMWF server. \n'
490              'There will be no transfer of output files to the '
491              'local gateway server possible!')
492    return
493
494def check_pathes(idir, odir, fpdir, fedir):
495    '''Check if output and flexpart pathes are set.
496
497    Parameters
498    ----------
499    idir : str
500        Path to the temporary directory for MARS retrieval data.
501
502    odir : str
503        Path to the final output directory where the FLEXPART input files
504        will be stored.
505
506    fpdir : str
507        Path to FLEXPART root directory.
508
509    fedir : str
510        Path to flex_extract root directory.
511
512    Return
513    ------
514    odir : str
515        Path to the final output directory where the FLEXPART input files
516        will be stored.
517
518    fpdir : str
519        Path to FLEXPART root directory.
520
521    '''
522    if not fpdir:
523        fpdir = fedir
524
525    if not odir:
526        odir = idir
527
528    return odir, fpdir
529
530def check_dates(start, end):
531    '''Checks if there is at least a start date for a one day retrieval.
532
533    Checks if end date lies after start date and end date is set.
534
535    Parameters
536    ----------
537    start : str
538        The start date of the retrieval job.
539
540    end : str
541        The end date of the retrieval job.
542
543    Return
544    ------
545    start : str
546        The start date of the retrieval job.
547
548    end : str
549        The end date of the retrieval job.
550
551    '''
552    # check for having at least a starting date
553    # otherwise program is not allowed to run
554    if not start:
555        raise ValueError('start_date was neither specified in command line nor '
556                         'in CONTROL file.\n'
557                         'Try "{} -h" to print usage information'
558                         .format(sys.argv[0].split('/')[-1]))
559
560    # retrieve just one day if end_date isn't set
561    if not end:
562        end = start
563
564    dstart = datetime.strptime(start, '%Y%m%d')
565    dend = datetime.strptime(end, '%Y%m%d')
566    if dstart > dend:
567        raise ValueError('ERROR: Start date is after end date! \n'
568                         'Please adapt the dates in CONTROL file or '
569                         'command line! (start={}; end={})'.format(start, end))
570
571    return start, end
572
573def check_maxstep(maxstep, steps):
574    '''Convert maxstep into integer if it is already given. Otherwise, select
575    maxstep by going through the steps list.
576
577    Parameters
578    ----------
579    maxstep : str
580        The maximum forecast time step in hours from the forecast base time.
581        This is the maximum step for non flux (accumulated) forecast data.
582
583    steps : str
584        Specifies the forecast time step from forecast base time.
585        Valid values are hours (HH) from forecast base time.
586
587    Return
588    ------
589    maxstep : int
590        The maximum forecast time step in hours from the forecast base time.
591        This is the maximum step for non flux (accumulated) forecast data.
592
593    '''
594    # if maxstep wasn't provided
595    # search for it in the "step" parameter
596    if not maxstep:
597        maxstep = 0
598        for s in steps:
599            if int(s) > maxstep:
600                maxstep = int(s)
601    else:
602        maxstep = int(maxstep)
603
604    return maxstep
605
606def check_basetime(basetime):
607    '''Check if basetime is set and contains one of the two
608    possible values (0, 12).
609
610    Parameters
611    ----------
612    basetime : int or str or None
613        The time for a half day retrieval. The 12 hours upfront are to be
614        retrieved.
615
616    Return
617    ------
618    basetime : int or None
619        The time for a half day retrieval. The 12 hours upfront are to be
620        retrieved.
621    '''
622    if basetime is not None:
623        basetime = int(basetime)
624        if basetime != 0 and basetime != 12:
625            raise ValueError('ERROR: Basetime has an invalid value '
626                             '-> {}'.format(str(basetime)))
627    return basetime
628
629def check_request(request, marsfile):
630    '''Check if there is an old mars request file and remove it.
631
632    Parameters
633    ----------
634    request : int
635        Selects the mode of retrieval.
636        0: Retrieves the data from ECMWF.
637        1: Prints the mars requests to an output file.
638        2: Retrieves the data and prints the mars request.
639
640    marsfile : str
641        Path to the mars request file.
642
643    Return
644    ------
645
646    '''
647    if request != 0:
648        if os.path.isfile(marsfile):
649            silent_remove(marsfile)
650    return
651
652def check_public(public, dataset):
653    '''Check wether the dataset parameter is set for a
654    public data set retrieval.
655
656    Parameters
657    ----------
658    public : int
659        Specifies if public data are to be retrieved or not.
660
661    dataset : str
662        Specific name which identifies the public dataset.
663
664    Return
665    ------
666
667    '''
668    if public and not dataset:
669        raise ValueError('ERROR: If public mars data wants to be retrieved, '
670                         'the "dataset"-parameter has to be set too!')
671    return
672
673def check_acctype(acctype, ftype):
674    '''Guarantees that the accumulation field type is set.
675
676    If not set, it is derivated as in the old method (TYPE[1]).
677
678    Parameters
679    ----------
680    acctype : str
681        The field type for the accumulated forecast fields.
682
683    ftype : list of str
684        List of field types.
685
686    Return
687    ------
688    acctype : str
689        The field type for the accumulated forecast fields.
690    '''
691    if not acctype:
692        print('... Control parameter ACCTYPE was not defined.')
693        try:
694            if len(ftype) == 1 and ftype[0] != 'AN':
695                print('... Use same field type as for the non-flux fields.')
696                acctype = ftype[0]
697            elif len(ftype) > 1 and ftype[1] != 'AN':
698                print('... Use old setting by using TYPE[1] for flux forecast!')
699                acctype = ftype[1]
700        except:
701            raise ValueError('ERROR: Accumulation field type could not be set!')
702    else:
703        if acctype.upper() == 'AN':
704            raise ValueError('ERROR: Accumulation forecast fields can not be '
705                             'of type "analysis"!')
706    return acctype
707
708
709def check_acctime(acctime, marsclass, purefc, time):
710    '''Guarantees that the accumulation forecast times were set.
711
712    If it is not set, it tries to set the value for some of the
713    most commonly used data sets. Otherwise it raises an error.
714
715    Parameters
716    ----------
717    acctime : str
718        The starting time from the accumulated forecasts.
719
720    marsclass : str
721        ECMWF data classification identifier.
722
723    purefc : int
724        Switch for definition of pure forecast mode or not.
725
726    Return
727    ------
728    acctime : str
729        The starting time from the accumulated forecasts.
730    '''
731
732    if not acctime:
733        print('... Control parameter ACCTIME was not defined.')
734        print('... Value will be set depending on field type:\n '
735              '\t\t EA=06/18\n\t\t EI/OD=00/12\n\t\t EP=18')
736        if marsclass.upper() == 'EA': # Era 5
737            acctime = '06/18'
738        elif marsclass.upper() == 'EI': # Era-Interim
739            acctime = '00/12'
740        elif marsclass.upper() == 'EP': # CERA
741            acctime = '18'
742        elif marsclass.upper() == 'OD' and not purefc: # On-demand
743            acctime = '00/12'
744        elif marsclass.upper() == 'OD' and purefc: # On-demand
745            acctime = time[0]
746        else:
747            raise ValueError('ERROR: Accumulation forecast time can not '
748                             'automatically be derived!')
749    return acctime
750
751def check_accmaxstep(accmaxstep, marsclass, purefc, maxstep):
752    '''Guarantees that the accumulation forecast step were set.
753
754    Parameters
755    ----------
756    accmaxstep : str
757        The maximum forecast step for the accumulated forecast fields.
758
759    marsclass : str
760        ECMWF data classification identifier.
761
762    purefc : int
763        Switch for definition of pure forecast mode or not.
764
765    maxstep : str
766        The maximum forecast time step in hours from the forecast base time.
767        This is the maximum step for non flux (accumulated) forecast data.
768
769    Return
770    ------
771    accmaxstep : str
772        The maximum forecast step for the accumulated forecast fields.
773    '''
774    if not accmaxstep:
775        print('... Control parameter ACCMAXSTEP was not defined.')
776        print('... Value will be set depending on field type/time: '
777              '\n\t\t EA/EI/OD=12\n\t\t EP=24')
778        if marsclass.upper() in ['EA', 'EI', 'OD'] and not purefc:
779            # Era 5, Era-Interim, On-demand operational
780            accmaxstep = '12'
781        elif marsclass.upper() == 'EP': # CERA
782            accmaxstep = '24'
783        elif purefc and accmaxstep != maxstep:
784            accmaxstep = maxstep
785            print('... For pure forecast mode, the accumulated forecast must '
786                  'have the same maxstep as the normal forecast fields!\n'
787                  '\t\t Accmaxstep was set to maxstep!')
788        else:
789            raise ValueError('ERROR: Accumulation forecast step can not '
790                             'automatically be derived!')
791    else:
792        if purefc and int(accmaxstep) != int(maxstep):
793            accmaxstep = maxstep
794            print('... For pure forecast mode, the accumulated forecast must '
795                  'have the same maxstep as the normal forecast fields!\n'
796                  '\t\t Accmaxstep was set to maxstep!')
797    return accmaxstep
798
799def check_addpar(addpar):
800    '''Check that addpar has correct format of additional parameters in
801    a single string, so that it can be easily appended to the hard coded
802    parameters that are retrieved in any case.
803
804    Parameters
805    ----------
806    addpar : str or list of str
807        List of additional parameters to be retrieved.
808
809    Return
810    ------
811    addpar : str
812        List of additional parameters to be retrieved.
813    '''
814
815    if addpar and isinstance(addpar, str):
816        if '/' in addpar:
817            parlist = addpar.split('/')
818            parlist = [p for p in parlist if p is not '']
819        else:
820            parlist = [addpar]
821
822        addpar = '/' + '/'.join(parlist)
823
824    return addpar
825
826
827def check_job_chunk(job_chunk):
828    '''Checks that if job chunk is set, the number is positive and non zero.
829
830    Parameters
831    ----------
832    job_chunk : int
833        The number of days for a single job script.
834
835    Return
836    ------
837    job_chunk : int
838        The number of days for a single job script.
839    '''
840    if not job_chunk:
841        return job_chunk
842    else:
843        job_chunk = int(job_chunk)
844
845    if job_chunk < 0:
846        raise ValueError('ERROR: The number of job chunk is negative!\n'
847                         'It has to be a positive number!')
848    elif job_chunk == 0:
849        job_chunk = None
850    else:
851        pass
852
853    return job_chunk
854
855
856def check_number(number):
857    '''Check for correct string format of ensemble member numbers.
858
859    Parameters
860    ----------
861    number : str
862        List of ensemble member forecast runs.
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