root/xmlrpcplugin/0.10/tracrpc/api.py

Revision 1950, 8.5 kB (checked in by athomas, 2 years ago)

XmlRpcPlugin:

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

Line 
1 from trac.core import *
2 from trac.perm import IPermissionRequestor
3 import inspect
4 import types
5 import xmlrpclib
6 try:
7     set = set
8 except:
9     from sets import Set as set
10
11 RPC_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
16 def 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
28 class 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
50 class 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
67 class 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
141 class 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 browser.