source: TI05-delivery/ows_framework/trunk/ows_server/ows_server/lib/decorators.py @ 2578

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI05-delivery/ows_framework/trunk/ows_server/ows_server/lib/decorators.py@2578
Revision 2578, 8.4 KB checked in by spascoe, 12 years ago (diff)

Validators provide a mechanism for type checking operation parameters.
This is slightly at odds with the Domain.isValidValue() way of doing
things: something that will need resolving eventually.

Line 
1# Copyright (C) 2007 STFC & NERC (Science and Technology Facilities Council).
2# This software may be distributed under the terms of the
3# Q Public License, version 1.0 or later.
4# http://ndg.nerc.ac.uk/public_docs/QPublic_license.txt
5"""
6Decorators for annotating OWS server controllers with information used
7to populate the ows_common model.
8
9@author: Stephen Pascoe
10"""
11
12from ows_common.util import make_domain
13from ows_common.exceptions import * 
14import inspect
15
16def operation(method):
17    """
18    A decorator which defines a method as a OWS operation.
19
20    The decorator assumes it is used in the context of an OwsController object with the
21    attribute self.ows_params available.
22
23
24    The method is wrapped in function that performs various OWS compatible checks:
25     1. Parameters from the request are checked against the methods _ows_parameters attribute
26        to ensure they conform to possibleValues.  Valid parameters are passed to the method
27        as arguments, invalid parameters trigger an exception.
28
29    @note: It would seem natural to implement this as a decorator class
30        but I can't make it work.
31
32    """
33
34    argspec = inspect.getargspec(method)
35
36    def wrapper(self, **kw):
37        """
38        This wrapper assumes invocation via GET with KVP encoding.  Future implementations
39        could detect POST and support XML encoding.
40
41        @param mself: The object of the method being called.
42        @param kw: All other keyword arguments are passed to the method.
43       
44        """
45
46        # Remove unwanted arguments from kw
47        if not argspec[2]:
48            kw2 = {}
49            for k, v in kw.iteritems():
50                if k in argspec[0]:
51                    kw2[k] = v
52            kw = kw2   
53
54        # Add arguments to kw according to self._ows_parameters
55        for param, domain in wrapper._ows_parameters.iteritems():
56            try:
57                param_l = param.lower()
58                value = self.ows_params[param_l]
59            except KeyError:
60                if param in wrapper._ows_required_parameters:
61                    raise MissingParameterValue('%s parameter is not specified' % param,
62                                                param)
63            else:
64                # If the argument is present check it's a valid value
65                if not domain.isValidValue(value):
66                    raise InvalidParameterValue('%s is not a valid value for %s' % (value, param), param)
67                # Use validator function if present
68                if param in wrapper._ows_validators:
69                    value = wrapper._ows_validators[param](value)
70
71                # Set value in kw dict
72                kw[param_l] = value
73               
74        return method(self, **kw)
75
76    # Propergate the OWS protocol attributes up to the wrapper
77    wrapper._ows_name = method.__name__
78    wrapper._ows_parameters = getattr(method, '_ows_parameters', {})
79    wrapper._ows_constraints = getattr(method, '_ows_constraints', {})
80    wrapper._ows_required_parameters = getattr(method, '_ows_required_parameters', [])       
81    wrapper._ows_validators = getattr(method, '_ows_validators', {})
82
83    return wrapper
84
85
86def parameter(name, value=None, possibleValues=None,
87              meaning=None, dataType=None, valuesUnit=None, required=False,
88              validator=None):
89    """
90    A decorator to add a parameter to an operation.
91
92    @param name: the parameter name.  This should be appropriately CamelCased.
93    @param value: the ows:Domain.defaultValue attribute or None
94    @param possibleValues: None or a list of allowed values or a
95        PossibleValues instance.
96    @param required: A boolean that indicates whether to raise an exception if the parameter
97        is missing.
98    @param validator: If not None this is a function which is called to validate operation
99        parameters.  It takes the parameter value string as it's only argument and returns
100        a parsed version of the value or raises an OWS exception.
101   
102    """
103    def d(method):
104        method._ows_parameters = getattr(method, '_ows_parameters', {})
105        method._ows_validators = getattr(method, '_ows_validators', {})
106        method._ows_parameters[name] = make_domain(value=value,
107                                                 possibleValues=possibleValues,
108                                                 meaning=meaning,
109                                                 dataType=dataType,
110                                                 valuesUnit=valuesUnit)
111        method._ows_required_parameters = getattr(method, '_ows_required_parameters', [])
112        if required:
113            method._ows_required_parameters.append(name)
114        if validator:
115            method._ows_validators[name] = validator
116           
117        return method
118
119    return d
120
121
122def constraint(name, value=None, possibleValues=None,
123               meaning=None, dataType=None, valuesUnit=None):
124    """
125    A decorator to add a constraint to an operation.
126
127    @param name: the parameter name.  This should be appropriately CamelCased.
128    @param value: the ows:Domain.defaultValue attribute or None
129    @param possibleValues: None or a list of allowed values or a
130        PossibleValues instance.
131   
132    """
133    def d(method):
134        method._ows_constraints = getattr(method, '_ows_constraints', {})
135        method._ows_constraints[name] = make_domain(value=value,
136                                                 possibleValues=possibleValues,
137                                                 meaning=meaning,
138                                                 dataType=dataType,
139                                                 valuesUnit=valuesUnit)
140        return method
141
142    return d
143
144#-----------------------------------------------------------------------------
145
146from unittest import TestCase
147
148class TestDecorators(TestCase):
149    def testParameter(self):
150        @parameter('foo', 'bar')
151        def f(x):
152            return x+1
153        domain = f._ows_parameters['foo']
154        assert domain.defaultValue == 'bar'
155        assert domain.possibleValues.type == domain.possibleValues.ANY_VALUE
156        assert f(2) == 3
157
158    def testConstraint(self):
159        @constraint('foo', 'bar')
160        def f(x):
161            return x+1
162        domain = f._ows_constraints['foo']
163        assert domain.defaultValue == 'bar'
164        assert domain.possibleValues.type == domain.possibleValues.ANY_VALUE
165        assert f(2) == 3
166   
167    def testMultiParameter(self):
168        @parameter('foo', 'bar')
169        @parameter('baz', 12)
170        def f(x):
171            return x+1
172        assert 'foo' in f._ows_parameters.keys()
173        assert 'baz' in f._ows_parameters.keys()
174
175    def testMultiConstraint(self):
176        @constraint('foo', 'bar')
177        @constraint('baz', 12)
178        def f(x):
179            return x+1
180        assert 'foo' in f._ows_constraints.keys()
181        assert 'baz' in f._ows_constraints.keys()
182        assert f(2) == 3
183
184    def testComplexParameter(self):
185        @parameter('foo', 'bar', possibleValues=[1,2,3])
186        def f(x):
187            return x+1
188        domain = f._ows_parameters['foo']
189        assert domain.defaultValue == 'bar'
190        print domain.possibleValues.type
191        assert domain.possibleValues.type == \
192               domain.possibleValues.ALLOWED_VALUES
193        assert domain.possibleValues.allowedValues == [1,2,3]
194
195
196class TestOperationDecorator(TestCase):
197    def setUp(self):
198        class Foo(object):
199            @operation
200            @parameter('Bar', required=True)
201            @parameter('Baz', possibleValues=['a', 'b'])
202            def MyOp(self, bar, baz=None):
203                return bar+1
204
205        self.foo = Foo()
206
207    def testOwsProtocol(self):
208        # Check OWS protocol is adhered to
209        assert self.foo.MyOp._ows_name == 'MyOp'
210        assert self.foo.MyOp._ows_parameters.keys() == ['Bar', 'Baz']
211        assert self.foo.MyOp._ows_constraints == {}
212        assert self.foo.MyOp._ows_required_parameters == ['Bar']
213
214    def testCall(self):
215        self.foo.ows_params = {'bar': 2}
216        assert self.foo.MyOp() == 3
217
218    def testFailMissingParameter(self):
219        self.foo.ows_params = {'bip': 2}
220        self.assertRaises(MissingParameterValue, self.foo.MyOp)
221
222    def testFailIncorrectParameterValue(self):
223        self.foo.ows_params = {'bar': 1, 'baz': 2}
224        self.assertRaises(InvalidParameterValue, self.foo.MyOp)
225
226    def testCallWithPossibleValue(self):
227        self.foo.ows_params = {'bar': 2, 'baz': 'b'}
228        assert self.foo.MyOp() == 3
Note: See TracBrowser for help on using the repository browser.