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

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

Fixed to async argument handler working.

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#    log.warn("III: %s, %s" % (item, str(enumeration)))   
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    # TOFIX: to avoid finding dynamic possible_values
85    if enumeration and enumeration[0].find("$") != 0:
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
116            if l not in allowed_lengths:
117                raise Exception("Array length of %s is not in list of allowed lengths: %s" \
118                                % (l, allowed_lengths))
119
120        elif allowed_length.find("-") > -1:     
121            low_high_match = re.match("(\d+)*-(\d+)*$", allowed_length.strip())
122            (low, high) = low_high_match.groups()
123            if low != None:
124                if l < int(low):
125                    raise Exception("Array length of '" + str(l) + "' is less than minimum allowed: " + str(low))
126
127            if high != None:
128                    if l > int(high):
129                        raise Exception("Array length of '" + str(l) + "' is greater than maximum allowed: " + str(high))
130
131    # Check if enumeration specified and if given values are allowed
132    # TOFIX: Need to improve how we miss out the URL template for possible_values
133    if enumeration and enumeration[0].find("$") != 0:
134        enum_typed = [convertType(dtype, x) for x in enumeration] 
135        for i in array:
136            checkArgInList(i, enum_typed)   
137
138    return array
139
140
141def parseDateTime(item):
142    """
143    Checks that item is a valid date time string: 'YYYY-MM-DDThh:mm:ss'.
144    """
145    pattn = re.compile("^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$")
146    if not pattn.match(item):
147        raise Exception("Item does not match date/time pattern.")
148
149    date_time_value = dateutil.parser.parse(item)
150    return date_time_value.isoformat()
151
152
153def parseBBox(item):
154    """
155    Checks bbox is valid floats.
156    We use (west, south, east, north) because that is used in OpenLayers
157    and we should use only one ordering in the bounds.
158    """
159    err_msg = "Invalid bounding box supplied: '%s'." % item
160
161    if not len(item) == 4:
162        raise Exception(err_msg)
163
164    try:
165        # can we convert all to floats
166        bbox_floats = [float(i) for i in item]
167    except:
168        raise Exception(err_msg)
169
170    # Must be physically valid
171    phys_err = "Value of '%s' (%s) is out of range in bounding box supplied: '%s'." 
172    directions = ("west", "south", "east", "north")
173    ranges = [(-360, 360), (-90, 90), (-360, 360), (-90, 90)]
174   
175    for i in range(4):
176        low, high = ranges[i]
177        value = bbox_floats[i]
178        direction = directions[i]
179
180        if value > high or value < low:
181            raise Exception(phys_err % (direction, value, bbox_floats))
182         
183    return bbox_floats
184
185
186class ValidateArguments:
187
188    def __init__(self, process_name, arg_dict, config_file="capabilities.ini"):
189        "Initialises dictionaries of input and validation rules."
190        log.info("Validation Arguments object created")
191        self.process_name = process_name
192        self.args = arg_dict
193#        log.debug('Config file: %s' % config_file)
194 
195        try:
196            self.valids = caps_config_dict[self.process_name]
197        except:
198            raise Exception("Cannot find entry for process '" + self.process_name + "' in config file.")
199
200    def validate(self):
201        """
202        Performs validation.
203        Raises exception if bad arguments given.
204        Returns a new dictionary of valid arguments of the correct type if valid.
205        """
206        new_dict = {}
207
208        # Step through each arg testing for validity
209        arg_names = self.valids["DataInputs"].keys()
210        arg_names.sort()
211               
212        log.debug("Validating list of arguments:(%s)" % (self.args,))
213       
214        # NOTE:policy of this code is to ignore any unknown arguments but not to raise an exception
215        for name in arg_names:
216
217            try:
218                # define d as the entry for this particular argument
219                d = self.valids["DataInputs"][name]
220               
221                # If did not get this arg test whether allowed and insert default value if given
222                if name not in self.args.keys():
223                    log.debug ("DataInput %s not present in args" % (name,))
224
225                    # Check if it is optional or has a default value
226                    if d.has_key("default"):
227                        value = d["default"]
228                        new_dict[name] = parseValue(value, d)
229                        log.debug("Using default value of %s for %s" % (new_dict[name], name))
230                       
231                    elif d.has_key('optional'):
232                        if not d['optional']:
233                            raise Exception("key %s does not exist in args (%s)" % (name, self.args.keys()))
234                    else:
235                        raise Exception("key %s does not exist in args (%s)" % (name, self.args.keys()))
236                   
237                    continue
238   
239                # Test the value provided
240                value_given = self.args[name]
241                new_dict[name] = parseValue(value_given, d)
242               
243            except:
244                log.warning("Error occurred while parsing the %s parameter." \
245                            % (name,))
246                raise
247       
248        #log a warning if there are parameters that arn't needed
249        for recievedParam in self.args.keys():
250            if recievedParam not in arg_names:
251                log.warning("Parameter %s was recieved but not needed for a %s process." \
252                            % (recievedParam, self.process_name))
253       
254        return new_dict
255
256
257def parseValue(value, d):
258    parsedValue = None
259#    log.warn("123: %s" % value)
260   
261    if d["item_type"] == "list":
262        parsedValue = parseSimpleArray(value, d["type"], d["allowed_length"], d["possible_values"])
263    elif d["item_type"] == "item":
264        parsedValue = parseSimpleItem(value, d["type"], d["possible_values"])
265    else:
266        raise ValueError(d["item_type"])   
267
268    return parsedValue
269
270
271def makeDict(qs):
272        dct = {}
273        items = qs.split(",")
274        for c in range(0, len(items)-1, 2):
275            dct[items[c]] = items[(c + 1)]
276        return dct
277
278
279if __name__ == "__main__":
280
281
282    qs = ("Username,astephens,RequestID,session32432,Dataset,probdata,Variables,precip,EmissionsScenarios,a1b,",
283         "TimeSlices,2020-2049|2030-2059,TemporalAveragingPeriod,sep,LocationType,region,Location,south_west,",
284         "ProbabilityDataType,cdf,ImageOutputFormat,ps,ImageWidth,900,ImageHeight,600,",
285         "LegendPosition,bc,YLimits,0|100")
286
287    qs = "".join(qs)
288   
289    dct = makeDict(qs)
290    x = ValidateArguments("AsyncTest", dct, ["/home/ashaon/wps/wps-ng_new/wps-ng/configs/AsyncTest.ini"])
291    print x.validate()
Note: See TracBrowser for help on using the repository browser.