Commits

Anonymous committed 3acee74

Further simplifications to docs. Back to a simple visual expression without any JS at the moment. Also changed the API slightly + some related cleaning.

  • Participants
  • Parent commits 6986966

Comments (0)

Files changed (2)

 # Make protocols pluggable (including default supported ones)
 # TODO: Types cleanup (Binary + datetime + all method docs)
 # TODO: Clean up method docs to not presume specific protocols
-# TODO: Make protocol documentation from plugin => HTML docs
-# TODO: Nicer display of enabled/disabled for known protocols
 # TODO: Abstract _send_error() using regular http status codes, and let each make the appropriate type of request
-# TODO: Really want to remove responding to other paths than /rpc and /login/rpc (change new API)
 t5437/t5437-protocol_api-r7194.diff
 

t5437/t5437-protocol_api-r7194.diff

+diff --git a/0.10/tracrpc/wiki.py b/0.10/tracrpc/wiki.py
+--- a/0.10/tracrpc/wiki.py
++++ b/0.10/tracrpc/wiki.py
+@@ -14,6 +14,10 @@
+ from tracrpc.api import IXMLRPCHandler, expose_rpc
+ from tracrpc.util import to_timestamp
+ 
++
++__all__ = ['WikiRPC']
++
++
+ class WikiRPC(Component):
+     """ Implementation of the [http://www.jspwiki.org/Wiki.jsp?page=WikiRPCInterface2 WikiRPC API]. """
+ 
 diff --git a/trunk/README.wiki b/trunk/README.wiki
 --- a/trunk/README.wiki
 +++ b/trunk/README.wiki
 diff --git a/trunk/setup.py b/trunk/setup.py
 --- a/trunk/setup.py
 +++ b/trunk/setup.py
-@@ -10,7 +10,7 @@
+@@ -10,19 +10,20 @@
  
  setup(
      name='TracXMLRPC',
      license='BSD',
      author='Alec Thomas',
      author_email='alec@swapoff.org',
-@@ -22,7 +22,8 @@
+     maintainer='Odd Simon Simonsen',
+     maintainer_email='simon-code@bvnetwork.no',
+     url='http://trac-hacks.org/wiki/XmlRpcPlugin',
+-    description='XML-RPC interface to Trac',
++    description='RPC interface to Trac',
+     zip_safe=True,
      test_suite = 'tracrpc.tests.suite',
      packages=find_packages(exclude=['*.tests']),
      package_data={
 diff --git a/trunk/tracrpc/api.py b/trunk/tracrpc/api.py
 --- a/trunk/tracrpc/api.py
 +++ b/trunk/tracrpc/api.py
-@@ -35,7 +35,29 @@
+@@ -13,6 +13,10 @@
+ import xmlrpclib
+ import datetime
+ 
++
++__all__ = ['expose_rpc', 'IRPCProtocol', 'IXMLRPCHandler', 'AbstractRPCHandler',
++            'Method', 'XMLRPCSystem']
++
+ try:
+     set = set
+ except:
+@@ -35,7 +39,28 @@
      return decorator
  
  
 +class IRPCProtocol(Interface):
 +    
-+    def rpc_info():
-+        """ Returns a tuple of (name, status, docs). Method provides
++    def rpc_info(req):
++        """ Returns a tuple of (name, docs). Method provides
 +        general information about the protocol used for the RPC HTML view.
 +        name: Shortname like 'XML-RPC'
-+        status: True if protocol is able to serve requests
 +        docs: HTML rendered documentation for the protocol
 +        """
 +
      def xmlrpc_namespace():
          """ Provide the namespace in which a set of methods lives.
              This can be overridden if the 'name' element is provided by
-@@ -56,7 +78,6 @@
+@@ -56,7 +81,6 @@
          followed by argument types.
          """
  
 new file mode 100644
 --- /dev/null
 +++ b/trunk/tracrpc/json_rpc.py
-@@ -0,0 +1,195 @@
+@@ -0,0 +1,209 @@
 +# -*- coding: utf-8 -*-
 +"""
 +License: BSD
 +import xmlrpclib
 +from types import GeneratorType
 +
++import genshi
++
 +from trac.core import *
 +from trac.perm import PermissionError
 +from trac.util.datefmt import utc
 +from trac.util.text import to_unicode
++from trac.wiki.formatter import wiki_to_html
 +
 +from tracrpc.api import IRPCProtocol, XMLRPCSystem
 +from tracrpc.util import exception_to_unicode, empty
 +
++
++__all__ = ['JsonRpcProtocol']
++
++
 +try:
 +    import json
 +    if not (hasattr(json, 'JSONEncoder') \
 +        return self._normalize(obj)
 +
 +class JsonRpcProtocol(Component):
-+    r"""
-+    === JSON-RPC example ===
-+    
-+    {{{
-+    user: ~ > cat body.json
-+    {"params": ["WikiStart"], "method": "wiki.getPage", "id": 123}
-+    user: ~ > curl -H "Content-Type: application/json" --data @body.json ${req.abs_href.rpc()}
-+    {"id": 123, "error": null, "result": "= Welcome to....
-+    }}}
-+    
-+    - JSON-RPC has no formalized type system, so a class-hint system is used
-+      for input and output of non-standard types:
-+      - `{"__jsonclass__": ["datetime", "YYYY-MM-DDTHH:MM:SS"]} => DateTime (UTC)`
-+      - `{"__jsonclass__": ["binary", "<base64-encoded>"]} => Binary`
-+    - `"id"` is optional, and any marker value received with a
-+      request is returned with the response.
++    """
++To access the API using the JSON-RPC protocol, use
++`{'Content-Type': 'application/json'}` with request to:
++ * [%s] for anonymous access
++ * [%s] for authenticated access
++
++Example and implementation details:
++{{{
++user: ~ > cat body.json
++{"params": ["WikiStart"], "method": "wiki.getPage", "id": 123}
++user: ~ > curl -H "Content-Type: application/json" --data @body.json %s
++{"id": 123, "error": null, "result": "= Welcome to....
++}}}
++
++ * JSON-RPC has no formalized type system, so a class-hint system is used
++   for input and output of non-standard types:
++  * `{"__jsonclass__": ["datetime", "YYYY-MM-DDTHH:MM:SS"]} => DateTime (UTC)`
++  * `{"__jsonclass__": ["binary", "<base64-encoded>"]} => Binary`
++ * `"id"` is optional, and any marker value received with a
++ request is returned with the response.
 +    """
 +
 +    implements(IRPCProtocol)
 +
 +    # IRPCProtocol methods
 +
-+    def rpc_info(self):
-+        return ('JSON-RPC', True, self.__doc__, list(self.rpc_match()))
++    def rpc_info(self, req):
++        doc_subs = (req.abs_href.rpc(), req.abs_href.login('rpc'),
++                        req.abs_href.rpc())
++        return ('JSON-RPC', genshi.core.Markup(wiki_to_html(
++                    self.__doc__ % doc_subs, self.env, req, absurls=1)))
 +
 +    def rpc_match(self):
 +        yield('rpc', 'application/json')
 +        return {'result': None, 'id': r_id, 'error': {
 +                'name': 'JSONRPCError', 'code': c, 'message': to_unicode(e)}}
 +
+diff --git a/trunk/tracrpc/search.py b/trunk/tracrpc/search.py
+--- a/trunk/tracrpc/search.py
++++ b/trunk/tracrpc/search.py
+@@ -16,6 +16,10 @@
+ except:
+     from sets import Set as set
+ 
++
++__all__ = ['SearchRPC']
++
++
+ class SearchRPC(Component):
+     """ Search Trac. """
+     implements(IXMLRPCHandler)
 diff --git a/trunk/tracrpc/templates/xmlrpclist.html b/trunk/tracrpc/templates/xmlrpclist.html
 --- a/trunk/tracrpc/templates/xmlrpclist.html
 +++ b/trunk/tracrpc/templates/xmlrpclist.html
-@@ -7,23 +7,95 @@
+@@ -7,23 +7,25 @@
    <xi:include href="layout.html" />
    <xi:include href="macros.html" />
    <head>
 -    <title>XML-RPC</title>
 +    <title>RPC</title>
-+    <script type="text/javascript">
-+      <py:choose test="rpc.proto_id">
-+        <py:when test="None"> <!--! unknown protocol -->
-+          var seltab = undefined;
-+        </py:when>
-+        <py:otherwise> <!--! selected protocol -->
-+          var seltab = ${[x[0] for x in rpc.protocols].index(rpc.proto_id)};
-+        </py:otherwise>
-+      </py:choose>
-+    </script>
    </head>
-+  
-+  <!--! Display tabs for protocol handlers -->
-+  <py:def function="tabs(divid)">
-+    <script type="text/javascript">
-+      tab_opts = {
-+                    select: function(event, tab) { 
-+                                if (tab.index != seltab) {
-+                                    seltab = tab.index;
-+  	                                $('.ui-tabs-nav').each(
-+  	                                  function () {
-+  	                                      $(this).tabs("select", tab.index);
-+                                        });
-+                                  }
-+                              }
-+                    };
-+      <py:if test="rpc.proto_id is not None"> <!--! selected protocol -->
-+		    tab_opts.selected = seltab;
-+		  </py:if>
-+		  $('#$divid').tabs(tab_opts);
-+    </script>
-+  </py:def>
 +
    <body>
 -
      <div id="content" class="wiki">
        <h1>Remote Procedure Call (RPC) Interface</h1>
-       <dl>
-         <dt>API Status:</dt>
+-      <dl>
+-        <dt>API Status:</dt>
 -          <dd>XML-RPC is <strong>available</strong></dd>
 -          <dd>JSON-RPC is <strong>
 -            ${xmlrpc.json and 'available' or 'not available'}</strong></dd>
-+        <div id="proto_desc">
-+          <ul>
-+    				<li py:for="protocol in rpc.protocols">
-+    				  <a href="#proto_desc_${protocol[0]}">${protocol[0]}</a>
-+    				</li>
-+          </ul>
-+          <div py:for="protocol in rpc.protocols" id="proto_desc_${protocol[0]}">
-+            <dd>
-+              <p>${protocol[0]} is <strong>
-+                ${protocol[1] and 'available' or 'not available'}</strong>
-+              </p>
-+              <ul>
-+                <li py:for="h, ct in protocol[3]">
-+                  Use <tt>${req.abs_href(h)}</tt> and specify 
-+                  <tt>Content-Type=$ct</tt> to perform anonymous calls. 
-+                  Use <tt>${req.abs_href.login(h)}</tt> with basic
-+                  authentication for user context.
-+                </li>
-+              </ul>
-+            </dd>
-+          </div>
-+        </div>
-       </dl>
-+      ${tabs('proto_desc')}
+-      </dl>
+ 
+-      <h2>RPC exported functions</h2>
++      <h2>RPC Protocols</h2>
 +
-+      <h2>Calling Methods</h2>
-+      <p>Libraries for remote procedure calls and parsing exists
-+        for most major languages and platforms - use a tested, standard library
-+        for consistent results.</p>
-+      <p>The following are examples for illustration only, and shows raw
-+        access to RPC using <tt>curl</tt> (with Content-Type and Body for POST request):</p>
-+      <div class="help">
-+        <div id="proto_ex">
-+          <ul>
-+    				<li py:for="protocol in rpc.protocols">
-+    				  <a href="#proto_ex_${protocol[0]}">${protocol[0]}</a>
-+    				</li>
-+          </ul>
-+          <div py:for="protocol in rpc.protocols" id="proto_ex_${protocol[0]}">
-+              ${wiki_to_html(context, protocol[2].replace(
-+                              '${req.abs_href.rpc()}', req.abs_href.rpc()))}
-+          </div>
-+        </div>
++      <div py:for="proto_name, proto_docs in rpc.protocols"
++            id="${proto_name}" class="protocol">
++        <h3>${proto_name}</h3>
++        ${proto_docs}
 +      </div>
-+      ${tabs('proto_ex')}
- 
-       <h2>RPC exported functions</h2>
++
++      <h2>RPC Funtions</h2>
  
        <div id="searchable">
 -        <dl py:for="key in sorted(xmlrpc.functions)" py:with="namespace = xmlrpc.functions[key]">
            <dt>
              <h3 id="${'xmlrpc.' + to_unicode(namespace.namespace)}">
                ${namespace.namespace} - ${namespace.description}
-@@ -50,60 +122,6 @@
+@@ -50,60 +52,6 @@
          </dl>
        </div>
        
 diff --git a/trunk/tracrpc/ticket.py b/trunk/tracrpc/ticket.py
 --- a/trunk/tracrpc/ticket.py
 +++ b/trunk/tracrpc/ticket.py
-@@ -22,7 +22,7 @@
+@@ -22,10 +22,14 @@
  
  import inspect
  import xmlrpclib
  
  from tracrpc.api import IXMLRPCHandler, expose_rpc
  
++
++__all__ = ['TicketRPC']
++
++
+ class TicketRPC(Component):
+     """ An interface to Trac's ticketing system. """
+ 
 diff --git a/trunk/tracrpc/util.py b/trunk/tracrpc/util.py
 --- a/trunk/tracrpc/util.py
 +++ b/trunk/tracrpc/util.py
  except ImportError:
      empty = None
 +
-+def accepts_mimetype(req, mimetype):
-+    if isinstance(mimetype, basestring):
-+      mimetype = (mimetype,)
-+    accept = (req.get_header('Accept') or '').split(',')
-+    return any(x.strip().startswith(y) for x in accept for y in mimetype)
++# def accepts_mimetype(req, mimetype):
++#     if isinstance(mimetype, basestring):
++#       mimetype = (mimetype,)
++#     accept = (req.get_header('Accept') or '').split(',')
++#     return any(x.strip().startswith(y) for x in accept for y in mimetype)
 diff --git a/trunk/tracrpc/web_ui.py b/trunk/tracrpc/web_ui.py
 --- a/trunk/tracrpc/web_ui.py
 +++ b/trunk/tracrpc/web_ui.py
-@@ -6,132 +6,67 @@
+@@ -6,132 +6,61 @@
  (c) 2009      ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
  """
  
 -from trac.perm import PermissionError
 -from trac.util.datefmt import utc
 -from trac.util.text import to_unicode
-+from trac.web.api import RequestDone
++from trac.web.api import RequestDone, HTTPUnsupportedMediaType
  from trac.web.main import IRequestHandler
 -from trac.web.chrome import ITemplateProvider, add_stylesheet
 +from trac.web.chrome import ITemplateProvider, add_stylesheet, add_script
 -from tracrpc.util import from_xmlrpc_datetime, to_xmlrpc_datetime
 -from tracrpc.util import exception_to_unicode, empty
 +from tracrpc.api import XMLRPCSystem, IRPCProtocol
-+from tracrpc.util import accepts_mimetype
  
 -try:
 -    try:
 -        1. datetime.datetime => {'__jsonclass__': ["datetime", "<rfc3339str>"]}
 -        2. xmlrpclib.Fault => unicode
 -        3. xmlrpclib.Binary => {'__jsonclass__': ["binary", "<base64str>"]} """
-+# Error codes
-+UNSUPPORTED_MEDIA_TYPE = 415
++__all__ = ['RPCWeb']
  
 -        def default(self, obj):
 -            if isinstance(obj, datetime.datetime):
 -                return to_unicode(obj)
 -            else:
 -                return json.JSONEncoder(self, obj)
--
+ 
 -    class TracRpcJSONDecoder(json.JSONDecoder):
 -        """ Extending the JSON decoder to support some additional types:
 -        1. {'__jsonclass__': ["datetime", "<rfc3339str>"]} => datetime.datetime
 +        content_type = req.get_header('Content-Type') or 'text/html'
 +        must_handle_request = False
 +        for protocol in self.protocols:
-+            if not protocol.rpc_info()[1]:
-+                continue # ...as protocol reports status disabled
 +            for p_path, p_type in protocol.rpc_match():
 +                if req.path_info in ['/%s' % p_path, '/login/%s' % p_path]:
 +                    must_handle_request = True
 +            raise RequestDone
 +        elif not content_type.startswith('text/html'):
 +            # Attempt at API call gone wrong. Raise a plain-text 415 error
-+            req.send_response(UNSUPPORTED_MEDIA_TYPE)
 +            body = "No protocol matching Content-Type '%s' at path '%s'." % (
 +                                                content_type, req.path_info)
-+            req.send_header('Content-Type', 'text/plain')
-+            req.send_header('Content-Length', len(body))
-+            req.write(body.encode('utf-8'))
-+            raise RequestDone
++            req.send_error(None, template='', content_type='text/plain',
++                    status=HTTPUnsupportedMediaType.code, env=None, data=body)
 +        else: 
 +            # Dump RPC documentation
              req.perm.require('XML_RPC') # Need at least XML_RPC
              namespaces = {}
              for method in XMLRPCSystem(self.env).all_methods(req):
-@@ -139,172 +74,37 @@
+@@ -139,172 +68,35 @@
                  if namespace not in namespaces:
                      namespaces[namespace] = {
                          'description' : wiki_to_oneliner(
 -            else:
 -                new_result.append(res)
 -        return new_result
-+            add_stylesheet(req, 'rpc/css/no-theme/ui.all.css')
-+            add_script(req, 'rpc/js/jquery-ui-1.6.custom.min.js')
 +            return ('xmlrpclist.html', 
 +                        {'rpc': {'functions': namespaces,
-+                        'protocols': [p.rpc_info() for p in self.protocols],
++                        'protocols': [p.rpc_info(req) for p in self.protocols],
 +                        'proto_id' : protocol \
 +                                        and protocol.rpc_info()[0] or None}},
 +                      None)
  import xmlrpclib
  import os
  
+@@ -23,6 +20,10 @@
+ 
+ from tracrpc.api import IXMLRPCHandler, expose_rpc
+ 
++
++__all__ = ['WikiRPC']
++
++
+ class WikiRPC(Component):
+     """Superset of the
+     [http://www.jspwiki.org/Wiki.jsp?page=WikiRPCInterface2 WikiRPC API]. """
 diff --git a/trunk/tracrpc/xml_rpc.py b/trunk/tracrpc/xml_rpc.py
 new file mode 100644
 --- /dev/null
 +++ b/trunk/tracrpc/xml_rpc.py
-@@ -0,0 +1,149 @@
+@@ -0,0 +1,164 @@
 +# -*- coding: utf-8 -*-
 +"""
 +License: BSD
 +from trac.core import *
 +from trac.perm import PermissionError
 +from trac.util.text import to_unicode
++from trac.wiki.formatter import wiki_to_html
 +
 +from tracrpc.api import XMLRPCSystem, IRPCProtocol
 +from tracrpc.util import empty
 +
 +
++__all__ = ['XmlRpcProtocol']
++
++
 +def to_xmlrpc_datetime(dt):
 +    """ Convert a datetime.datetime object to a xmlrpclib DateTime object """
 +    return xmlrpclib.DateTime(dt.utctimetuple())
 +    return apply(datetime.datetime, t, {'tzinfo': utc})
 +
 +class XmlRpcProtocol(Component):
-+    r"""
-+    === XML-RPC example ===
-+    {{{
-+    user: ~ > cat body.xml
-+    <?xml version="1.0"?>
-+    <methodCall>
-+    <methodName>wiki.getPage</methodName>
-+    <params>
-+    <param><string>WikiStart</string></param>
-+    </params>
-+    </methodCall>
-+    
-+    user: ~ > curl -H "Content-Type: application/xml" --data @body.xml ${req.abs_href.rpc()}
-+    <?xml version='1.0'?>
-+    <methodResponse>
-+    <params>
-+    <param>
-+    <value><string>= Welcome to....
-+    }}}
++    """
++There should be XML-RPC client implementations available for all
++popular programming languages.
++
++To access the XML-RPC protocol, use `{'Content-Type': 'application/xml'}` with
++request to:
++ * [%s] for anonymous access
++ * [%s] for authenticated access
++
++Example call:
++{{{
++user: ~ > cat body.xml
++<?xml version="1.0"?>
++<methodCall>
++<methodName>wiki.getPage</methodName>
++<params>
++<param><string>WikiStart</string></param>
++</params>
++</methodCall>
++
++user: ~ > curl -H "Content-Type: application/xml" --data @body.xml %s}
++<?xml version='1.0'?>
++<methodResponse>
++<params>
++<param>
++<value><string>= Welcome to....
++}}}
 +    """
 +    implements(IRPCProtocol)
 +
 +    # IRPCProtocol methods
 +
-+    def rpc_info(self):
-+        return ('XML-RPC', True, self.__doc__, list(self.rpc_match()))
++    def rpc_info(self, req):
++        doc_subs = (req.abs_href.rpc(), req.abs_href.login('rpc'),
++                        req.abs_href.rpc())
++        return ('XML-RPC', genshi.core.Markup(wiki_to_html(
++                    self.__doc__ % doc_subs, self.env, req, absurls=1)))
 +
 +    def rpc_match(self):
 +        yield ('rpc', 'application/xml')