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

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

more fixes

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