source: xmlrpcplugin/0.10/tracrpc/api.py

Last change on this file was 1950, checked in by Alec Thomas, 17 years ago

XmlRpcPlugin:

Send ticket notifications when. Thanks to stp for the patch. Closes #1069.

File size: 8.5 KB
Line 
1from trac.core import *
2from trac.perm import IPermissionRequestor
3import inspect
4import types
5import xmlrpclib
6try:
7    set = set
8except:
9    from sets import Set as set
10
11RPC_TYPES = {int: 'int', bool: 'boolean', str: 'string', float: 'double',
12             xmlrpclib.DateTime: 'dateTime.iso8601', xmlrpclib.Binary: 'base64',
13             list: 'array', dict: 'struct', None : 'int'}
14
15
16def expose_rpc(permission, return_type, *arg_types):
17    """ Decorator for exposing a method as an RPC call with the given
18    signature. """
19    def decorator(func):
20        if not hasattr(func, '_xmlrpc_signatures'):
21            func._xmlrpc_signatures = []
22            func._xml_rpc_permission = permission
23        func._xmlrpc_signatures.append((return_type,) + tuple(arg_types))
24        return func
25    return decorator
26
27
28class IXMLRPCHandler(Interface):
29    def xmlrpc_namespace():
30        """ Provide the namespace in which a set of methods lives.
31            This can be overridden if the 'name' element is provided by
32            xmlrpc_methods(). """
33
34    def xmlrpc_methods():
35        """ Return an iterator of (permission, signatures, callable[, name]),
36        where callable is exposed via XML-RPC if the authenticated user has the
37        appropriate permission.
38           
39        The callable itself can be a method or a normal method. The first
40        argument passed will always be a request object. The XMLRPCSystem
41        performs some extra magic to remove the "self" and "req" arguments when
42        listing the available methods.
43
44        Signatures is a list of XML-RPC introspection signatures for this
45        method. Each signature is a tuple consisting of the return type
46        followed by argument types.
47        """
48
49
50class AbstractRPCHandler(Component):
51    implements(IXMLRPCHandler)
52    abstract = True
53
54    def _init_methods(self):
55        import inspect
56        self._rpc_methods = []
57        for name, val in inspect.getmembers(self):
58            if hasattr(val, '_xmlrpc_signatures'):
59                self._rpc_methods.append((val._xml_rpc_permission, val._xmlrpc_signatures, val, name))
60
61    def xmlrpc_methods(self):
62        if not hasattr(self, '_rpc_methods'):
63            self._init_methods()
64        return self._rpc_methods
65
66
67class Method(object):
68    """ Represents an XML-RPC exposed method. """
69    def __init__(self, provider, permission, signatures, callable, name = None):
70        """ Accept a signature in the form returned by xmlrpc_methods. """
71        import pydoc
72        self.permission = permission
73        self.callable = callable
74        self.rpc_signatures = signatures
75        self.description = pydoc.getdoc(callable)
76        if name is None:
77            self.name = provider.xmlrpc_namespace() + '.' + callable.__name__
78        else:
79            self.name = provider.xmlrpc_namespace() + '.' + name
80        self.namespace = provider.xmlrpc_namespace()
81        self.namespace_description = pydoc.getdoc(provider)
82
83    def __call__(self, req, args):
84        req.perm.assert_permission(self.permission)
85        result = self.callable(req, *args)
86        # If result is null, return a zero
87        if result is None:
88            result = 0
89        elif isinstance(result, dict):
90            pass
91        elif not isinstance(result, basestring):
92            # Try and convert result to a list
93            try:
94                result = [i for i in result]
95            except TypeError:
96                pass
97        return (result,)
98
99    def _get_signature(self):
100        """ Return the signature of this method. """
101        if hasattr(self, '_signature'):
102            return self._signature
103        fullargspec = inspect.getargspec(self.callable)
104        argspec = fullargspec[0]
105        assert argspec[0:2] == ['self', 'req'] or argspec[0] == 'req', \
106            'Invalid argspec %s for %s' % (argspec, self.name)
107        while argspec and (argspec[0] in ('self', 'req')):
108            argspec.pop(0)
109        argspec.reverse()
110        defaults = fullargspec[3]
111        if not defaults:
112            defaults = []
113        else:
114            defaults = list(defaults)
115        args = []
116        sig = []
117        for sigcand in self.xmlrpc_signatures():
118            if len(sig) < len(sigcand):
119                sig = sigcand
120        sig = list(sig)
121        for arg in argspec:
122            if defaults:
123                value = defaults.pop()
124                if type(value) is str:
125                    if '"' in value:
126                        value = "'%s'" % value
127                    else:
128                        value = '"%s"' % value
129                arg += '=%s' % value
130            args.insert(0, RPC_TYPES[sig.pop()] + ' ' + arg)
131        self._signature = '%s %s(%s)' % (RPC_TYPES[sig.pop()], self.name, ', '.join(args))
132        return self._signature
133
134    signature = property(_get_signature)
135
136    def xmlrpc_signatures(self):
137        """ Signature as an XML-RPC 'signature'. """
138        return self.rpc_signatures
139
140
141class XMLRPCSystem(Component):
142    """ Core of the XML-RPC system. """
143    implements(IPermissionRequestor, IXMLRPCHandler)
144
145    method_handlers = ExtensionPoint(IXMLRPCHandler)
146
147    # IPermissionRequestor methods
148    def get_permission_actions(self):
149        yield 'XML_RPC'
150
151    # IXMLRPCHandler methods
152    def xmlrpc_namespace(self):
153        return 'system'
154
155    def xmlrpc_methods(self):
156        yield ('XML_RPC', ((list, list),), self.multicall)
157        yield ('XML_RPC', ((list,),), self.listMethods)
158        yield ('XML_RPC', ((str, str),), self.methodHelp)
159        yield ('XML_RPC', ((list, str),), self.methodSignature)
160        yield ('XML_RPC', ((list,),), self.getAPIVersion)
161
162    def get_method(self, method):
163        """ Get an RPC signature by full name. """ 
164        for provider in self.method_handlers:
165            for candidate in provider.xmlrpc_methods():
166                #self.env.log.debug(candidate)
167                p = Method(provider, *candidate)
168                if p.name == method:
169                    return p
170        raise xmlrpclib.Fault(1, 'XML-RPC method "%s" not found' % method)
171       
172    # Exported methods
173    def all_methods(self, req):
174        """ List all methods exposed via XML-RPC. Returns a list of Method objects. """
175        for provider in self.method_handlers:
176            for candidate in provider.xmlrpc_methods():
177                # Expand all fields of method description
178                c = Method(provider, *candidate)
179                if req.perm.has_permission(c.permission):
180                    yield c
181
182    def multicall(self, req, signatures):
183        """ Takes an array of XML-RPC calls encoded as structs of the form (in
184        a Pythonish notation here):
185
186        {'methodName': string, 'params': array}
187        """
188        for signature in signatures:
189            try:
190                yield self.get_method(signature['methodName'])(req, signature['params'])
191            except xmlrpclib.Fault, e:
192                yield e
193            except Exception, e:
194                yield xmlrpclib.Fault(2, "'%s' while executing '%s()'" % (str(e), signature['methodName']))
195
196    def listMethods(self, req):
197        """ This method returns a list of strings, one for each (non-system)
198        method supported by the XML-RPC server. """
199        for method in self.all_methods(req):
200            yield method.name
201
202    def methodHelp(self, req, method):
203        """ This method takes one parameter, the name of a method implemented
204        by the XML-RPC server. It returns a documentation string describing the
205        use of that method. If no such string is available, an empty string is
206        returned. The documentation string may contain HTML markup. """
207        p = self.get_method(method)
208        req.perm.assert_permission(p.permission)
209        return '\n'.join((p.signature, '', p.description))
210
211    def methodSignature(self, req, method):
212        """ This method takes one parameter, the name of a method implemented
213        by the XML-RPC server.
214
215        It returns an array of possible signatures for this method. A signature
216        is an array of types. The first of these types is the return type of
217        the method, the rest are parameters. """
218        p = self.get_method(method)
219        req.perm.assert_permission(p.permission)
220        return [','.join([RPC_TYPES[x] for x in sig]) for sig in p.xmlrpc_signatures()]
221
222    def getAPIVersion(self, req):
223        """ Returns a list with two elements. First element is the major
224        version number, second is the minor. Changes to the major version
225        indicate API breaking changes, while minor version changes are simple
226        additions, bug fixes, etc. """
227        return [0, 2]
Note: See TracBrowser for help on using the repository browser.