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

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

many 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
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    """
154    err_msg = "Invalid bounding box supplied: '%s'." % item
155
156    if not len(item) == 4:
157        raise Exception(err_msg)
158
159    try:
160        # can we convert all to floats
161        bbox_floats = [float(i) for i in item]
162    except:
163        raise Exception(err_msg)
164
165    # Must be physically valid
166    phys_err = "Value of '%s' (%s) is out of range in bounding box supplied: '%s'." 
167    directions = ("north", "west", "south", "east")
168    ranges = [(-90, 90), (-360, 360), (-90, 90), (-360, 360)]
169   
170    for i in range(4):
171        low, high = ranges[i]
172        value = bbox_floats[i]
173        direction = directions[i]
174
175        if value > high or value < low:
176            raise Exception(phys_err % (direction, value, bbox_floats))
177         
178    return bbox_floats
179
180
181class ValidateArguments:
182
183    def __init__(self, process_name, arg_dict, config_file="capabilities.ini"):
184        "Initialises dictionaries of input and validation rules."
185        log.info("Validation Arguments object created")
186        self.process_name = process_name
187        self.args = arg_dict
188#        log.debug('Config file: %s' % config_file)
189 
190        try:
191            self.valids = caps_config_dict[self.process_name]
192        except:
193            raise Exception("Cannot find entry for process '" + self.process_name + "' in config file.")
194
195    def validate(self):
196        """
197        Performs validation.
198        Raises exception if bad arguments given.
199        Returns a new dictionary of valid arguments of the correct type if valid.
200        """
201        new_dict = {}
202
203        # Step through each arg testing for validity
204        arg_names = self.valids["DataInputs"].keys()
205        arg_names.sort()
206               
207        log.debug("Validating list of arguments:(%s)" % (self.args,))
208       
209        # NOTE:policy of this code is to ignore any unknown arguments but not to raise an exception
210        for name in arg_names:
211
212            try:
213                # define d as the entry for this particular argument
214                d = self.valids["DataInputs"][name]
215               
216                # If did not get this arg test whether allowed and insert default value if given
217                if name not in self.args.keys():
218                    log.debug ("DataInput %s not present in args" % (name,))
219
220                    # Check if it is optional or has a default value
221                    if d.has_key("default"):
222                        value = d["default"]
223                        new_dict[name] = parseValue(value, d)
224                        log.debug("Using default value of %s for %s" % (new_dict[name], name))
225                       
226                    elif d.has_key('optional'):
227                        if not d['optional']:
228                            raise Exception("key %s does not exist in args (%s)" % (name, self.args.keys()))
229                    else:
230                        raise Exception("key %s does not exist in args (%s)" % (name, self.args.keys()))
231                   
232                    continue
233   
234                # Test the value provided
235                value_given = self.args[name]
236                new_dict[name] = parseValue(value_given, d)
237               
238            except:
239                log.warning("Error occurred while parsing the %s parameter." \
240                            % (name,))
241                raise
242       
243        #log a warning if there are parameters that arn't needed
244        for recievedParam in self.args.keys():
245            if recievedParam not in arg_names:
246                log.warning("Parameter %s was recieved but not needed for a %s process." \
247                            % (recievedParam, self.process_name))
248       
249        return new_dict
250
251def parseValue(value, d):
252    parsedValue = None
253   
254    if d["item_type"] == "list":
255        parsedValue = parseSimpleArray(value, d["type"], d["allowed_length"], d["possible_values"])
256    elif d["item_type"] == "item":
257        parsedValue = parseSimpleItem(value, d["type"], d["possible_values"])
258    else:
259        raise ValueError(d["item_type"])   
260
261    return parsedValue
262
263def makeDict(qs):
264        dct = {}
265        items = qs.split(",")
266        for c in range(0, len(items)-1, 2):
267            dct[items[c]] = items[(c + 1)]
268        return dct
269
270if __name__ == "__main__":
271
272
273    qs = ("Username,astephens,RequestID,session32432,Dataset,probdata,Variables,precip,EmissionsScenarios,a1b,",
274         "TimeSlices,2020-2049|2030-2059,TemporalAveragingPeriod,sep,LocationType,region,Location,south_west,",
275         "ProbabilityDataType,cdf,ImageOutputFormat,ps,ImageWidth,900,ImageHeight,600,",
276         "LegendPosition,bc,YLimits,0|100")
277
278    qs = "".join(qs)
279   
280    dct = makeDict(qs)
281    x = ValidateArguments("AsyncTest", dct, ["/home/ashaon/wps/wps-ng_new/wps-ng/configs/AsyncTest.ini"])
282    print x.validate()
Note: See TracBrowser for help on using the repository browser.