source: cows_wps/trunk/cows_wps/process_handler/validate_arguments.py @ 7001

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/cows_wps/trunk/cows_wps/process_handler/validate_arguments.py@7001
Revision 7001, 9.4 KB checked in by astephen, 10 years ago (diff)

bbox made to match openlayers in order of directions

Line 
1"""
2validate_arguments.py
3=====================
4
5Takes an argument dictionary being sent to a WPS process (or optionally to any callable
6object) and checks contents against the argument signature defined in the parsed
7configuration file.
8
9Holds ValidateArguments class to do this.
10
11Requires third party typecheck package to be installed.
12
13"""
14# Import standard library modules
15import os
16import sys
17import re
18import dateutil.parser
19
20import logging
21log = logging.getLogger(__name__)
22
23# Import local modules
24import cows_wps.utils.parse_capabilities_config as parse_capabilities_config
25from cows_wps.utils.parse_capabilities_config import caps_config_dict
26
27
28def convertType(dtype, item):
29    "Returns item converted to dtype data type or raises error."
30   
31    if dtype == "bool":
32       
33        if item.__class__ == bool:
34            return item
35       
36        item = parse_capabilities_config.fixTrueFalse(item)
37        if item not in (True, False):
38            raise Exception("Data type of '" + str(item) + "' must be 'True' or 'False'.")
39        else:
40            return item
41
42    if dtype in ("xml", "filepath"): 
43        dtype = "string"
44    elif dtype == "datetime":
45        return parseDateTime(item)
46    elif dtype in ("bbox",):
47        return parseBBox(item)
48
49    transformer = dtype
50    if transformer == "string": transformer = "str"
51   
52    try:
53        item = eval("%s('%s')" % (transformer, item))
54    except:
55        raise Exception("Could not convert item '" + str(item) + "' to data type '" + `dtype` + ".")
56   
57    return item
58
59
60def checkArgInList(item, enumeration):
61    "Returns True if item is in enumeration and raises error if not."
62   
63    if item not in enumeration:
64       
65        #check that the enumerations are strings
66        if type(enumeration[0]) == str:
67            enumerationString = ",".join(enumeration)
68        else:
69            enumerationString = ",".join([str(x) for x in enumeration])
70
71        raise Exception("Item %s not allowed in enumerated list: %s" % (item, enumerationString))
72   
73    return True
74
75
76def parseSimpleItem(item, dtype, enumeration):
77    """
78    Returns a parsed and correctly typed item checking it is of type dtype
79    and it is in enumeration if provided.
80    """
81    item = convertType(dtype, item)
82
83    # Check if enumeration specified and if given values are allowed
84    if enumeration:
85        enum_typed = [convertType(dtype, x) for x in enumeration] 
86        checkArgInList(item, enum_typed)   
87   
88    return item
89
90
91def parseSimpleArray(item, dtype, allowed_length, enumeration):
92    """
93    Returns a list of items following pattern '<x>|<y>|<z>',
94    checking against the valid lengths in allowed_length.
95    """
96#    log.debug('parseSimpleArray: allowed_length = %s' % allowed_length)
97    if type(item) in (type("string"), type(u"hi")):
98        items = item.split("|")
99    else:
100        items = item
101   
102#    log.debug('parseSimpleArray: %s' % ((item, dtype, allowed_length, enumeration),))
103
104    # Generate array
105    array = [convertType(dtype, x) for x in items]
106
107    # Check length of array is allowed
108    # Simple list of lengths first
109    l = len(array)
110    if allowed_length != None:
111   
112        if allowed_length.find("-") < 0:
113            allowed_lengths = [int(x) for x in allowed_length.split(",")]
114            if l not in allowed_lengths:
115                raise Exception("Array length of %s is not in list of allowed lengths: %s" \
116                                % (l, allowed_lengths))
117
118        elif allowed_length.find("-") > -1:     
119            low_high_match = re.match("(\d+)*-(\d+)*$", allowed_length.strip())
120            (low, high) = low_high_match.groups()
121            if low != None:
122                if l < int(low):
123                    raise Exception("Array length of '" + str(l) + "' is less than minimum allowed: " + str(low))
124
125            if high != None:
126                    if l > int(high):
127                        raise Exception("Array length of '" + str(l) + "' is greater than maximum allowed: " + str(high))
128
129    # Check if enumeration specified and if given values are allowed
130    if enumeration:
131        enum_typed = [convertType(dtype, x) for x in enumeration] 
132        for i in array:
133            checkArgInList(i, enum_typed)   
134
135    return array
136
137
138def parseDateTime(item):
139    """
140    Checks that item is a valid date time string: 'YYYY-MM-DDThh:mm:ss'.
141    """
142    pattn = re.compile("^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$")
143    if not pattn.match(item):
144        raise Exception("Item does not match date/time pattern.")
145
146    date_time_value = dateutil.parser.parse(item)
147    return date_time_value.isoformat()
148
149
150def parseBBox(item):
151    """
152    Checks bbox is valid floats.
153    We use (west, south, east, north) because that is used in OpenLayers
154    and we should use only one ordering in the bounds.
155    """
156    err_msg = "Invalid bounding box supplied: '%s'." % item
157
158    if not len(item) == 4:
159        raise Exception(err_msg)
160
161    try:
162        # can we convert all to floats
163        bbox_floats = [float(i) for i in item]
164    except:
165        raise Exception(err_msg)
166
167    # Must be physically valid
168    phys_err = "Value of '%s' (%s) is out of range in bounding box supplied: '%s'." 
169    directions = ("west", "south", "east", "north")
170    ranges = [(-360, 360), (-90, 90), (-360, 360), (-90, 90)]
171   
172    for i in range(4):
173        low, high = ranges[i]
174        value = bbox_floats[i]
175        direction = directions[i]
176
177        if value > high or value < low:
178            raise Exception(phys_err % (direction, value, bbox_floats))
179         
180    return bbox_floats
181
182
183class ValidateArguments:
184
185    def __init__(self, process_name, arg_dict, config_file="capabilities.ini"):
186        "Initialises dictionaries of input and validation rules."
187        log.info("Validation Arguments object created")
188        self.process_name = process_name
189        self.args = arg_dict
190#        log.debug('Config file: %s' % config_file)
191 
192        try:
193            self.valids = caps_config_dict[self.process_name]
194        except:
195            raise Exception("Cannot find entry for process '" + self.process_name + "' in config file.")
196
197    def validate(self):
198        """
199        Performs validation.
200        Raises exception if bad arguments given.
201        Returns a new dictionary of valid arguments of the correct type if valid.
202        """
203        new_dict = {}
204
205        # Step through each arg testing for validity
206        arg_names = self.valids["DataInputs"].keys()
207        arg_names.sort()
208               
209        log.debug("Validating list of arguments:(%s)" % (self.args,))
210       
211        # NOTE:policy of this code is to ignore any unknown arguments but not to raise an exception
212        for name in arg_names:
213
214            try:
215                # define d as the entry for this particular argument
216                d = self.valids["DataInputs"][name]
217               
218                # If did not get this arg test whether allowed and insert default value if given
219                if name not in self.args.keys():
220                    log.debug ("DataInput %s not present in args" % (name,))
221
222                    # Check if it is optional or has a default value
223                    if d.has_key("default"):
224                        value = d["default"]
225                        new_dict[name] = parseValue(value, d)
226                        log.debug("Using default value of %s for %s" % (new_dict[name], name))
227                       
228                    elif d.has_key('optional'):
229                        if not d['optional']:
230                            raise Exception("key %s does not exist in args (%s)" % (name, self.args.keys()))
231                    else:
232                        raise Exception("key %s does not exist in args (%s)" % (name, self.args.keys()))
233                   
234                    continue
235   
236                # Test the value provided
237                value_given = self.args[name]
238                new_dict[name] = parseValue(value_given, d)
239               
240            except:
241                log.warning("Error occurred while parsing the %s parameter." \
242                            % (name,))
243                raise
244       
245        #log a warning if there are parameters that arn't needed
246        for recievedParam in self.args.keys():
247            if recievedParam not in arg_names:
248                log.warning("Parameter %s was recieved but not needed for a %s process." \
249                            % (recievedParam, self.process_name))
250       
251        return new_dict
252
253def parseValue(value, d):
254    parsedValue = None
255   
256    if d["item_type"] == "list":
257        parsedValue = parseSimpleArray(value, d["type"], d["allowed_length"], d["possible_values"])
258    elif d["item_type"] == "item":
259        parsedValue = parseSimpleItem(value, d["type"], d["possible_values"])
260    else:
261        raise ValueError(d["item_type"])   
262
263    return parsedValue
264
265def makeDict(qs):
266        dct = {}
267        items = qs.split(",")
268        for c in range(0, len(items)-1, 2):
269            dct[items[c]] = items[(c + 1)]
270        return dct
271
272if __name__ == "__main__":
273
274
275    qs = ("Username,astephens,RequestID,session32432,Dataset,probdata,Variables,precip,EmissionsScenarios,a1b,",
276         "TimeSlices,2020-2049|2030-2059,TemporalAveragingPeriod,sep,LocationType,region,Location,south_west,",
277         "ProbabilityDataType,cdf,ImageOutputFormat,ps,ImageWidth,900,ImageHeight,600,",
278         "LegendPosition,bc,YLimits,0|100")
279
280    qs = "".join(qs)
281   
282    dct = makeDict(qs)
283    x = ValidateArguments("AsyncTest", dct, ["/home/ashaon/wps/wps-ng_new/wps-ng/configs/AsyncTest.ini"])
284    print x.validate()
Note: See TracBrowser for help on using the repository browser.