Changeset 808

Show
Ignore:
Timestamp:
06/03/06 03:58:41 (3 years ago)
Author:
athomas
Message:

XmlRpcPlugin:

  • Removed magic for guessing whether first argument to a remote call should be a req object. RPC methods now always receive a req object as their first argument.
  • Added ticket attachment add/remove/list/delete (closes #344).
  • Set newly created tickets status to new, unless specified (closes #401).
  • Made /xmlrpc listing a bit friendlier.
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • xmlrpcplugin/0.10/tracrpc/api.py

    r504 r808  
    1313             list: 'array', dict: 'struct', None : 'int'} 
    1414 
    15 class IXMLRPCHandler(Interface): 
    16     def xmlrpc_namespace(): 
    17         """ Provide the namespace in which a set of methods lives. 
    18             This can be overridden if the 'name' element is provided by 
    19             xmlrpc_methods(). """ 
    20  
    21     def xmlrpc_methods(): 
    22         """ Return an iterator of (permission, signatures, callable[, name]), 
    23             where callable is exposed via XML-RPC if the authenticated user has 
    24             the appropriate permission. 
    25              
    26             The callable itself can be a method or a normal method. The 
    27             XMLRPCSystem performs some extra magic to remove the "self" 
    28             argument when listing the available methods. 
    29  
    30             Signatures is a list of XML-RPC introspection signatures for this method. 
    31             """ 
    32  
    33 class AbstractRPCHandler(Component): 
    34     implements(IXMLRPCHandler) 
    35     abstract = True 
    36  
    37     def _init_methods(self): 
    38         import inspect 
    39         self._rpc_methods = [] 
    40         for name, val in inspect.getmembers(self): 
    41             if hasattr(val, '_xmlrpc_signatures'): 
    42                 self._rpc_methods.append((val._xml_rpc_permission, val._xmlrpc_signatures, val, name)) 
    43  
    44     def xmlrpc_methods(self): 
    45         if not hasattr(self, '_rpc_methods'): 
    46             self._init_methods() 
    47         return self._rpc_methods 
    4815 
    4916def expose_rpc(permission, return_type, *arg_types): 
    50     """ Expose a method as an RPC call with the given signature. """ 
     17    """ Decorator for exposing a method as an RPC call with the given 
     18    signature. """ 
    5119    def decorator(func): 
    5220        if not hasattr(func, '_xmlrpc_signatures'): 
     
    5624        return func 
    5725    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 
    5866 
    5967class Method(object): 
     
    7583    def __call__(self, req, args): 
    7684        req.perm.assert_permission(self.permission) 
    77         # Pass optional "req" arg if required 
    78         argspec = inspect.getargspec(self.callable)[0] 
    79         if (argspec and argspec[0] == 'req') or (len(argspec) > 1 and argspec[0:2] == ['self', 'req']): 
    80             result = self.callable(req, *args) 
    81         else: 
    82             result = self.callable(*args) 
     85        result = self.callable(req, *args) 
    8386        # If result is null, return a zero 
    8487        if result is None: 
    8588            result = 0 
    86         # We'll fully traverse the generator results and return it as a tuple 
    87         elif type(result) is types.GeneratorType: 
    88             result = tuple(result) 
    89         elif type(result) is set: 
    90             result = tuple(result) 
     89        elif not isinstance(result, basestring): 
     90            # Try and convert result to a list 
     91            try: 
     92                result = [i for i in result] 
     93            except TypeError: 
     94                pass 
    9195        return (result,) 
    9296 
     
    97101        fullargspec = inspect.getargspec(self.callable) 
    98102        argspec = fullargspec[0] 
     103        assert argspec[0:2] == ['self', 'req'] or argspec[0] == 'req', \ 
     104            'Invalid argspec %s for %s' % (argspec, self.name) 
    99105        while argspec and (argspec[0] in ('self', 'req')): 
    100106            argspec.pop(0) 
     
    130136        return self.rpc_signatures 
    131137 
     138 
    132139class XMLRPCSystem(Component): 
    133140    """ Core of the XML-RPC system. """ 
     
    171178    def multicall(self, req, signatures): 
    172179        """ Takes an array of XML-RPC calls encoded as structs of the form (in 
    173             a Pythonish notation here): 
    174  
    175             {'methodName': string, 'params': array} 
     180        a Pythonish notation here): 
     181 
     182        {'methodName': string, 'params': array} 
    176183        """ 
    177184        for signature in signatures: 
     
    200207    def methodSignature(self, req, method): 
    201208        """ This method takes one parameter, the name of a method implemented 
    202             by the XML-RPC server. 
    203  
    204             It returns an array of possible signatures for this method. A 
    205             signature is an array of types. The first of these types is the 
    206             return type of the method, the rest are parameters. """ 
     209        by the XML-RPC server. 
     210 
     211        It returns an array of possible signatures for this method. A signature 
     212        is an array of types. The first of these types is the return type of 
     213        the method, the rest are parameters. """ 
    207214        p = self.get_method(method) 
    208215        req.perm.assert_permission(p.permission) 
  • xmlrpcplugin/0.10/tracrpc/templates/xmlrpclist.cs

    r481 r808  
    22<?cs include "macros.cs"?> 
    33 
    4 <div id="content" class="report"> 
     4<div id="ctxtnav" class="nav"></div> 
     5 
     6<div id="content" class="wiki"> 
    57 
    68<h2>XML-RPC exported functions</h2> 
    79 
     10<div id="searchable"> 
    811<dl> 
    912<?cs each:namespace = xmlrpc.functions ?> 
    1013 
    11 <dt><h3 id=<?cs var:namespace.namespace ?>><?cs var:namespace.namespace ?> - <?cs var:namespace.description ?></h3></dt> 
     14<dt><h3 id=xmlrpc.<?cs var:namespace.namespace ?>><?cs var:namespace.namespace ?> - <?cs var:namespace.description ?></h3></dt> 
    1215<dd> 
    1316<table class="listing tickets"> 
     
    3336<?cs /each ?> 
    3437</dl> 
     38</div> 
     39 
     40<script type="text/javascript"> 
     41addHeadingLinks(document.getElementById("searchable")); 
     42</script 
    3543 
    3644</div> 
  • xmlrpcplugin/0.10/tracrpc/ticket.py

    r786 r808  
     1from trac.attachment import Attachment 
    12from trac.core import * 
    23from tracrpc.api import IXMLRPCHandler, expose_rpc 
    34import trac.ticket.model as model 
    45import trac.ticket.query as query 
     6 
    57import pydoc 
    68import xmlrpclib 
     
    2224        yield ('TICKET_ADMIN', ((None, int),), self.delete) 
    2325        yield ('TICKET_VIEW', ((dict, int), (dict, int, int)), self.changeLog) 
     26        yield ('TICKET_VIEW', ((list, int),), self.listAttachments) 
     27        yield ('TICKET_VIEW', ((xmlrpclib.Binary, int, str),), self.getAttachment) 
     28        yield ('TICKET_APPEND', ((bool, int, str, xmlrpclib.Binary),), self.putAttachment) 
    2429 
    2530    # Exported methods 
    26     def query(self, qstr = 'status!=closed'): 
     31    def query(self, req, qstr = 'status!=closed'): 
    2732        """ Perform a ticket query, returning a list of ticket ID's. """ 
    2833        q = query.Query.from_string(self.env, qstr) 
     
    3237        return out 
    3338 
    34     def get(self, id): 
     39    def get(self, req, id): 
    3540        """ Fetch a ticket. Returns [id, time_created, time_changed, attributes]. """ 
    3641        t = model.Ticket(self.env, id) 
    3742        return (t.id, t.time_created, t.time_changed, t.values) 
    3843 
    39     def create(self, summary, description, attributes = {}): 
     44    def create(self, req, summary, description, attributes = {}): 
    4045        """ Create a new ticket, returning the ticket ID. """ 
    4146        t = model.Ticket(self.env) 
     47        t['status'] = 'new' 
    4248        t['summary'] = summary 
    4349        t['description'] = description 
     
    5561        return self.get(t.id) 
    5662 
    57     def delete(self, id): 
     63    def delete(self, req, id): 
    5864        """ Delete ticket with the given id. """ 
    5965        t = model.Ticket(self.env, id) 
    6066        t.delete() 
    6167 
    62     def changeLog(self, id, when = 0): 
     68    def changeLog(self, req, id, when = 0): 
    6369        t = model.Ticket(self.env, id) 
    6470        return t.get_changelog() 
    65  
    6671    # Use existing documentation from Ticket model 
    6772    changeLog.__doc__ = pydoc.getdoc(model.Ticket.get_changelog) 
     73 
     74    def listAttachments(self, req, ticket): 
     75        """ Lists attachments for a given ticket. """ 
     76        return [a.filename for a in Attachment.select(self.env, 'ticket', ticket)] 
     77 
     78    def getAttachment(self, req, ticket, filename): 
     79        """ returns the content of an attachment. """ 
     80        attachment = Attachment(self.env, 'ticket', ticket, filename) 
     81        return xmlrpclib.Binary(attachment.open().read()) 
     82 
     83    def putAttachment(self, req, ticket, filename, data): 
     84        """ (over)writes an attachment. """ 
     85        if not Ticket(self.env, ticket).exists: 
     86            raise TracError, 'Ticket "%s" does not exist' % ticket 
     87        attachment = Attachment(self.env, 'ticket', ticket) 
     88        attachment.insert(filename, StringIO(data.data), len(data.data)) 
     89        return True 
    6890 
    6991 
     
    83105            yield ('TICKET_ADMIN', ((None, str, dict),), self.update) 
    84106 
    85         def getAll(self): 
     107        def getAll(self, req): 
    86108            for i in cls.select(self.env): 
    87109                yield i.name 
    88110        getAll.__doc__ = """ Get a list of all ticket %s names. """ % cls.__name__.lower() 
    89111 
    90         def get(self, name): 
     112        def get(self, req, name): 
    91113            i = cls(self.env, name) 
    92114            attributes= {} 
     
    96118        get.__doc__ = """ Get a ticket %s. """ % cls.__name__.lower() 
    97119 
    98         def delete(self, name): 
     120        def delete(self, req, name): 
    99121            cls(self.env, name).delete() 
    100122        delete.__doc__ = """ Delete a ticket %s """ % cls.__name__.lower() 
    101123 
    102         def create(self, name, attributes): 
     124        def create(self, req, name, attributes): 
    103125            self._updateHelper(name, attributes).insert() 
    104126        create.__doc__ = """ Create a new ticket %s with the given attributes. """ % cls.__name__.lower() 
    105127 
    106         def update(self, name, attributes): 
     128        def update(self, req, name, attributes): 
    107129            self._updateHelper(name, attributes).update() 
    108130        update.__doc__ = """ Update ticket %s with the given attributes. """ % cls.__name__.lower() 
     
    133155            yield ('TICKET_ADMIN', ((None, str, str),), self.update) 
    134156 
    135         def getAll(self): 
     157        def getAll(self, req): 
    136158            for i in cls.select(self.env): 
    137159                yield i.name 
    138160        getAll.__doc__ = """ Get a list of all ticket %s names. """ % cls.__name__.lower() 
    139161 
    140         def get(self, name): 
     162        def get(self, req, name): 
    141163            i = cls(self.env, name) 
    142164            return i.value 
    143165        get.__doc__ = """ Get a ticket %s. """ % cls.__name__.lower() 
    144166 
    145         def delete(self, name): 
     167        def delete(self, req, name): 
    146168            cls(self.env, name).delete() 
    147169        delete.__doc__ = """ Delete a ticket %s """ % cls.__name__.lower() 
    148170 
    149         def create(self, name, value): 
     171        def create(self, req, name, value): 
    150172            self._updateHelper(name, value).insert() 
    151173        create.__doc__ = """ Create a new ticket %s with the given value. """ % cls.__name__.lower() 
    152174 
    153         def update(self, name, value): 
     175        def update(self, req, name, value): 
    154176            self._updateHelper(name, value).update() 
    155177        update.__doc__ = """ Update ticket %s with the given value. """ % cls.__name__.lower() 
    156178 
    157         def _updateHelper(self, name, value): 
     179        def _updateHelper(self, req, name, value): 
    158180            i = cls(self.env) 
    159181            i.name = name 
  • xmlrpcplugin/0.10/tracrpc/web_ui.py

    r229 r808  
    11from trac.core import * 
    22from trac.web.main import IRequestHandler 
    3 from trac.web.chrome import ITemplateProvider 
     3from trac.web.chrome import ITemplateProvider, add_stylesheet 
    44from tracrpc.api import IXMLRPCHandler, XMLRPCSystem 
    55from trac.wiki.formatter import wiki_to_oneliner 
     
    4242                    namespaces[namespace]['methods'].append((method.signature, wiki_to_oneliner(method.description, self.env), method.permission)) 
    4343                except Exception, e: 
    44                     raise Exception('%s: %s' % (method.name, str(e))) 
     44                    from StringIO import StringIO 
     45                    import traceback 
     46                    out = StringIO() 
     47                    traceback.print_exc(file=out) 
     48                    raise Exception('%s: %s\n%s' % (method.name, str(e), out.getvalue())) 
     49            add_stylesheet(req, 'common/css/wiki.css') 
    4550            req.hdf['xmlrpc.functions'] = namespaces 
    4651            return 'xmlrpclist.cs', None 
  • xmlrpcplugin/0.10/tracrpc/wiki.py

    r478 r808  
    4949                    author=author, version=int(version)) 
    5050 
    51     def getRecentChanges(self, since): 
     51    def getRecentChanges(self, req, since): 
    5252        """ Get list of changed pages since timestamp """ 
    5353        since = self._to_timestamp(since) 
     
    6161        return result 
    6262 
    63     def getRPCVersionSupported(self): 
     63    def getRPCVersionSupported(self, req): 
    6464        """ Returns 2 with this version of the Trac API. """ 
    6565        return 2 
    6666 
    67     def getPage(self, pagename, version=None): 
     67    def getPage(self, req, pagename, version=None): 
    6868        """ Get the raw Wiki text of page, latest version. """ 
    6969        page = WikiPage(self.env, pagename, version) 
     
    8282        return '<html><body>%s</body></html>' % html 
    8383 
    84     def getAllPages(self): 
     84    def getAllPages(self, req): 
    8585        """ Returns a list of all pages. The result is an array of utf8 pagenames. """ 
    8686        return list(self.wiki.get_pages()) 
    8787 
    88     def getPageInfo(self, pagename, version=None): 
     88    def getPageInfo(self, req, pagename, version=None): 
    8989        """ Returns information about the given page. """ 
    9090        page = WikiPage(self.env, pagename, version) 
     
    113113        return True 
    114114 
    115     def listAttachments(self, pagename): 
     115    def listAttachments(self, req, pagename): 
    116116        """ Lists attachments on a given page. """ 
    117117        return [pagename + '/' + a.filename for a in Attachment.select(self.env, 'wiki', pagename)] 
    118118 
    119     def getAttachment(self, path): 
     119    def getAttachment(self, req, path): 
    120120        """ returns the content of an attachment. """ 
    121121        pagename, filename = posixpath.split(path) 
     
    123123        return xmlrpclib.Binary(attachment.open().read()) 
    124124 
    125     def putAttachment(self, path, data): 
     125    def putAttachment(self, req, path, data): 
    126126        """ (over)writes an attachment. """ 
    127127        pagename, filename = posixpath.split(path) 
     
    132132        return True 
    133133 
    134     def listLinks(self, pagename): 
     134    def listLinks(self, req, pagename): 
    135135        """ ''Not implemented'' """ 
    136136        return []