[Checkins] SVN: z3c.jsonrpc/trunk/ Implemented JSON-RPC 2.0 specification. Use JSON-RPC 2.0 version as default.

Roger Ineichen roger at projekt01.ch
Sat Aug 2 08:51:33 EDT 2008


Log message for revision 89216:
  Implemented JSON-RPC 2.0 specification. Use JSON-RPC 2.0 version as default.

Changed:
  U   z3c.jsonrpc/trunk/CHANGES.txt
  U   z3c.jsonrpc/trunk/buildout.cfg
  U   z3c.jsonrpc/trunk/src/z3c/jsonrpc/README.txt
  A   z3c.jsonrpc/trunk/src/z3c/jsonrpc/exception.py
  U   z3c.jsonrpc/trunk/src/z3c/jsonrpc/publication.py
  U   z3c.jsonrpc/trunk/src/z3c/jsonrpc/publisher.py
  U   z3c.jsonrpc/trunk/src/z3c/jsonrpc/testing.py
  U   z3c.jsonrpc/trunk/src/z3c/jsonrpc/tests/test_request.py

-=-
Modified: z3c.jsonrpc/trunk/CHANGES.txt
===================================================================
--- z3c.jsonrpc/trunk/CHANGES.txt	2008-08-02 12:50:14 UTC (rev 89215)
+++ z3c.jsonrpc/trunk/CHANGES.txt	2008-08-02 12:51:33 UTC (rev 89216)
@@ -2,16 +2,23 @@
 CHANGES
 =======
 
-0.5.2 (unreleased)
-------------------
+Version 0.5.2dev (unreleased)
+-----------------------------
 
-- Make progress with JSON-RPC 2.0 sepcification implementation
+- Implemented JSON-RPC 2.0 specification. Use JSON-RPC 2.0 version as default.
+  Optional the version 1.0 and 1.1 can be set. See JSON-RPC 2.0 specification
+  for more information.
 
+- Added initial version of JSON-RPC exceptions.
+
+- Added explicit test cleanup since some zope testing change left over a global 
+  adapter registry from old conections
+
 - Removed unused dependency to z3c.layer in test setup
 
 
-0.5.1 (2008-01-24)
-------------------
+Version 0.5.1 (2008-01-24)
+--------------------------
 
 - Improve meta-data.
 
@@ -19,7 +26,7 @@
   reverted.
 
 
-0.5.0 (2008-01-21)
-------------------
+Version 0.5.0 (2008-01-21)
+--------------------------
 
 - Initial Release

Modified: z3c.jsonrpc/trunk/buildout.cfg
===================================================================
--- z3c.jsonrpc/trunk/buildout.cfg	2008-08-02 12:50:14 UTC (rev 89215)
+++ z3c.jsonrpc/trunk/buildout.cfg	2008-08-02 12:51:33 UTC (rev 89216)
@@ -1,5 +1,6 @@
 [buildout]
 develop = .
+          ../z3c.json
 parts = test checker coverage
 find-links = http://pypi.python.org/simple/z3c.json/
 

Modified: z3c.jsonrpc/trunk/src/z3c/jsonrpc/README.txt
===================================================================
--- z3c.jsonrpc/trunk/src/z3c/jsonrpc/README.txt	2008-08-02 12:50:14 UTC (rev 89215)
+++ z3c.jsonrpc/trunk/src/z3c/jsonrpc/README.txt	2008-08-02 12:51:33 UTC (rev 89216)
@@ -95,10 +95,17 @@
   ...     def greeting(self, name):
   ...         return u"Hello %s" % name
   ...
-  ...     def kwarguments(self, prefix, foo=None, bar=None):
+  ...     def mixedparams(self, prefix, bar=None, foo=None):
   ...         # Note; keyword arguments can be found in request.form
-  ...         return u"%s %s %s" % (prefix, foo, bar)
+  ...         return u"%s %s %s" % (prefix, bar, foo)
   ...
+  ...     def kws(self, adam=None, foo=None, bar=None):
+  ...         # Note; keyword arguments can be found in request.form
+  ...         a = self.request.get('adam')
+  ...         b = self.request.form.get('foo')
+  ...         c = self.request.form.get('bar')
+  ...         return u"%s %s %s" % (a, b, c)
+  ...
   ...     def showId(self):
   ...         return u"The json id is: %s" % self.request.jsonId
   ...
@@ -130,10 +137,17 @@
   ...     def greeting(self, name):
   ...         return u"Hello %s" % name
   ...
-  ...     def kwarguments(self, prefix, foo=None, bar=None):
+  ...     def mixedparams(self, prefix, foo=None, bar=None):
   ...         # Note; keyword arguments can be found in request.form
   ...         return u"%s %s %s" % (prefix, foo, bar)
   ...
+  ...     def kws(self, adam=None, foo=None, bar=None):
+  ...         # Note; keyword arguments can be found in request.form
+  ...         a = self.request.get('adam')
+  ...         b = self.request.form.get('foo')
+  ...         c = self.request.form.get('bar')
+  ...         return u"%s %s %s" % (a, b, c)
+  ...
   ...     def showId(self):
   ...         return u"The json id is: %s" % self.request.jsonId
   ...
@@ -164,7 +178,7 @@
   ...       for="custom.IDemoContent"
   ...       class="custom.DemoView"
   ...       permission="zope.Public"
-  ...       methods="hello greeting kwarguments showId forceValueError"
+  ...       methods="hello greeting mixedparams kws showId forceValueError"
   ...       layer="z3c.jsonrpc.testing.IJSONRPCTestSkin"
   ...       />
   ... </configure>
@@ -188,7 +202,7 @@
   ...       for="custom.IDemoContainer"
   ...       class="custom.DemoContainerView"
   ...       permission="zope.Public"
-  ...       methods="available greeting kwarguments showId forceValueError"
+  ...       methods="available greeting mixedparams kws showId forceValueError"
   ...       layer="z3c.jsonrpc.testing.IJSONRPCTestSkin"
   ...       />
   ... </configure>
@@ -267,14 +281,21 @@
   >>> proxy.greeting(u'Jessy')
   u'Hello Jessy'
 
-Now let's make a remote procedure call with a kw arguments. Note that this
-key word arguments are stored in the request.form. But this doesn't change
-anything because this varaibles are also accessible like form variables e.g.
-self.request['foo'].Also note that we don't support the **kw signature:
+Note that this key word arguments are stored in the request.form. Important
+to know is that JSON-RPC does not support positional and named arguments in
+one method call. We are working on a solution for solve that restriction which
+hurts us as python developer.
 
-  >>> proxy.kwarguments('Hello', foo=u'FOO', bar=u'BAR')
-  u'Hello FOO BAR'
+  >>> proxy.mixedparams('Hello', foo=u'FOO', bar=u'BAR')
+  Traceback (most recent call last):
+  ...
+  ValueError: Mixing positional and named parameters in one call is not possible
 
+Let's call named arguments:
+
+  >>> proxy.kws(bar=u'BAR', foo=u'FOO')
+  u'None FOO BAR'
+
 There is also an ``id`` in the json response. Let's use such a json request id
 in our JSONRPCProxy:
 
@@ -297,7 +318,7 @@
 and the error message is:
 
   >>> proxy.error
-  u'ValueError: Something was wrong in server method.'
+  {u'message': u'Invalid JSON-RPC', u'code': -32603, u'data': u'ValueError: Something was wrong in server method.'}
 
 The error property gets reset on the next successfull call:
 

Added: z3c.jsonrpc/trunk/src/z3c/jsonrpc/exception.py
===================================================================
--- z3c.jsonrpc/trunk/src/z3c/jsonrpc/exception.py	                        (rev 0)
+++ z3c.jsonrpc/trunk/src/z3c/jsonrpc/exception.py	2008-08-02 12:51:33 UTC (rev 89216)
@@ -0,0 +1,53 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id:$
+"""
+
+
+# The error codes used since JSON-RPC 2.0:
+#
+# code   message           Meaning 
+# --------------------------------
+# -32700 Parse error.          Invalid JSON. An error occurred on the server 
+#                              while parsing the JSON text.
+# -32600 Invalid Request.      The received JSON not a valid JSON-RPC Request.
+# -32601 Method not found.     The requested remote-procedure does not exist, 
+#                              is not available.
+# -32602 Invalid params.       Invalid method parameters.
+# -32603 Internal error.       Internal JSON-RPC error.
+# -32099..-32000 Server error. Reserved for implementation-defined server-errors.
+
+class JSONRPCException(Exception):
+    """Base class for JSON-RPC exception."""
+
+
+class ParseError(JSONRPCException):
+    """Invalid JSON. An error occurred on the server while parsing the JSON text."""
+
+
+class InvalidRequest(JSONRPCException):
+    """The received JSON not a valid JSON-RPC Request. """
+
+
+class MethodNotFound(JSONRPCException):
+    """The requested remote-procedure does not exist, is not available."""
+
+
+class InvalidParams(JSONRPCException):
+    """Invalid method parameters."""
+
+
+class InternalError(JSONRPCException):
+    """Internal JSON-RPC error."""


Property changes on: z3c.jsonrpc/trunk/src/z3c/jsonrpc/exception.py
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: z3c.jsonrpc/trunk/src/z3c/jsonrpc/publication.py
===================================================================
--- z3c.jsonrpc/trunk/src/z3c/jsonrpc/publication.py	2008-08-02 12:50:14 UTC (rev 89215)
+++ z3c.jsonrpc/trunk/src/z3c/jsonrpc/publication.py	2008-08-02 12:51:33 UTC (rev 89216)
@@ -18,7 +18,6 @@
 
 import zope.interface
 import zope.component
-
 from zope.app.publication.http import BaseHTTPPublication
 from zope.app.publication.interfaces import IRequestPublicationFactory
 
@@ -32,7 +31,6 @@
     zope.interface.implements(interfaces.IJSONRPCPublication)
 
 
-
 class JSONRPCFactory(object):
     zope.interface.implements(IRequestPublicationFactory)
     

Modified: z3c.jsonrpc/trunk/src/z3c/jsonrpc/publisher.py
===================================================================
--- z3c.jsonrpc/trunk/src/z3c/jsonrpc/publisher.py	2008-08-02 12:50:14 UTC (rev 89215)
+++ z3c.jsonrpc/trunk/src/z3c/jsonrpc/publisher.py	2008-08-02 12:51:33 UTC (rev 89216)
@@ -35,6 +35,8 @@
 from z3c.jsonrpc import interfaces
 from z3c.jsonrpc.interfaces import JSON_CHARSETS
 
+JSON_RPC_VERSION = '2.0'
+
 DEBUG = logging.DEBUG
 logger = logging.getLogger()
 
@@ -75,9 +77,33 @@
 
 
 class JSONRPCRequest(HTTPRequest):
-    """JSON-RPC request implementation based on IHTTPRequest."""
+    """JSON-RPC request implementation based on IHTTPRequest.
+    
+    This implementation supports the following JSON-RPC Specification versions:
+    
+    - 1.0
+    - 1.1
+    - 2.0
+    
+    Version 1.0 and 1.1 offers params as a list. This params get converted to
+    positional arguments if calling the JSON-RPC function.
+    
+    The version 2.0 offers support for named key/value params. The important 
+    thing to know is that this implementation will convert named params kwargs 
+    to form paramters. This means the method doesn't get any key word argument. 
+    The reason why I was choosing is the existing publisher implementation and 
+    it's debugger integration. If someone likes to integrate **kwargs support, 
+    take a look at the publisher.publish method and it's mapply function which 
+    get wrapped by the Debugger class. I hope that's fine for now and I 
+    recommend to avoid kwargs for JSON-RPC methods ;-)
 
+    The z3c.jsonrpcclient JavaScript method JSONRPCProxy converts a 
+    typeof object as arguments[0] to named key/value pair arguments.
+
+    """
+
     _jsonId = 'jsonrpc'
+    jsonVersion = JSON_RPC_VERSION
     jsonId = None
 
     zope.interface.implements(interfaces.IJSONRPCRequest,
@@ -121,17 +147,27 @@
         # ensure unicode
         if not isinstance(input, unicode):
             input = self._decode(input)
-        data = json.read(input)
+        try:
+            data = json.read(input)
+        except:
+            # catch any error since we don't know which library is used as 
+            # parser
+            raise ParseError
+        # get the params
+        params = data['params']
         if self.jsonId is None:
             self.jsonId = data.get('id', self._jsonId)
-        params = data['params']
 
-        if isinstance(params, list):
-            # json-rpc 1.0
+        # get the json version. The version 1.0 offers no version argument.
+        # The version 1.1 offers a version key and since version 2.0 the 
+        # version is given with the ``jsonrpc`` key. Let's try to find the 
+        # version for our request.
+        self.jsonVersion = data.get('version', self.jsonVersion)
+        self.jsonVersion = data.get('jsonrpc', self.jsonVersion)
+        if self.jsonVersion in ['1.0', '1.1']:
+            # json-rpc 1.0 and 1.1
             args = params
-            # now, look for keyword parameters, the old way
-            kwargs = None
-            notPositional = []
+            # version 1.0 and 1.1 uses a list of arguments
             for arg in args:
                 if isinstance(arg, dict):
                     # set every dict key value as form items and support at 
@@ -144,39 +180,75 @@
                             match = self._typeFormat.match(key, pos + 1)
                             if match is not None:
                                 key, type_name = key[:pos], key[pos + 1:]
-                                if type_name == 'list':
+                                if type_name == 'list' and not isinstance(d, list):
                                     d = [d]
-                                if type_name == 'tuple':
+                                if type_name == 'tuple' and not isinstance(d, tuple):
                                     d = tuple(d)
                         self.form[key] = d
-        elif isinstance(params, dict):
-            # json-rpc 1.2
-            # Note: the JSONRPCProxy uses allways a dict for params. This means
-            # we only use this part for extract the data.
+            
+        elif self.jsonVersion == '2.0':
+            # version 2.0 uses a list or a dict as params. Process the list
+            # params here. This params get used as positional arguments in the
+            # method call.
+            if isinstance(params, list):
+                args = params
+                # now, look for keyword parameters, the old way
+                for arg in args:
+                    if isinstance(arg, dict):
+                        # set every dict key value as form items and support at 
+                        # least ``:list`` and ``:tuple`` input field name postifx
+                        # conversion.
+                        for key, d in arg.items():
+                            key = str(key)
+                            pos = key.rfind(":")
+                            if pos > 0:
+                                match = self._typeFormat.match(key, pos + 1)
+                                if match is not None:
+                                    key, type_name = key[:pos], key[pos + 1:]
+                                    if type_name == 'list' and not isinstance(d, list):
+                                        d = [d]
+                                    if type_name == 'tuple' and not isinstance(d, tuple):
+                                        d = tuple(d)
+                            self.form[key] = d
 
-            # Get the numeric params for positional params
-            # This was proposed for json-rpc 1.1 but seems not get accepted.
-            # The new 2.0 proposal only defines named paramters if they get
-            # applied as key/value pair.
-            
-            # review and check this implementation after JSON-RPC 2.0 final
-            temp_positional = []
-            for key in params:
-                if str(key).isdigit():
-                    temp_positional.append((key, params[key]))
-            temp_positional.sort(key=intsort)
-            args = []
-            # make args from positional args and remove them from params
-            for item in temp_positional:
-                args.append(item[1])
-                del params[item[0]]
-            # drop remaining named params into request.form
-            for named_param in params:
-                # named_param is unicode; python needs string for param names
-                self.form[str(named_param)] = params[named_param]
+            elif isinstance(params, dict):
+                # process the key/value pair params. This arguments get stored
+                # in the request.form argument and we skip it from method calls.
+                # This means this library will not support key word arguments
+                # for method calls. It will instead store them in the form.
+                # This has two reasons.
+                # 1. Zope doesn't support kwargs in the publication 
+                #    implementation. It only supports positional arguments
+                # 2. The JSON-RPC specification doesn't allow to use positional
+                #     and keyword arguments on one method call
+                # 3. Python doesn't allow to convert kwargs to positional 
+                #    arguments because a dict doesn't provide an order
+                # This means you should avoid to call a method with kwargs.
+                # just use positional arguments if possible. Or get them from
+                # directly from the request or request.form argument in your
+                # code. Let me know if this is a real problem for you and you
+                # like to implement a different kwarg handling. We have some 
+                # ideas for add support for this.
+                args = params
+                # set every dict key value as form items and support at 
+                # least ``:list`` and ``:tuple`` input field name postifx
+                # conversion.
+                for key, d in args.items():
+                    key = str(key)
+                    pos = key.rfind(":")
+                    if pos > 0:
+                        match = self._typeFormat.match(key, pos + 1)
+                        if match is not None:
+                            key, type_name = key[:pos], key[pos + 1:]
+                            if type_name == 'list' and not isinstance(d, list):
+                                d = [d]
+                            if type_name == 'tuple' and not isinstance(d, tuple):
+                                d = tuple(d)
+                    self.form[key] = d
+                args = []
         else:
-            raise TypeError, 'Unsupported type for JSON-RPC "params" (%s)' \
-                % type(params)
+            raise TypeError, 'Unsupported JSON-RPC version (%s)' % \
+                self.jsonVersion
         self._args = tuple(args)
         # make environment, cookies, etc., available to request.get()
         super(JSONRPCRequest,self).processInputs()
@@ -208,7 +280,7 @@
             return result
         return super(JSONRPCRequest, self).get(key, default)
 
-    def __getitem__(self,key):
+    def __getitem__(self, key):
         return self.get(key)
 
 
@@ -219,26 +291,38 @@
     def setResult(self, result):
         """The result dict contains the following key value pairs
 
+        The version 1.0 and 1.1 provides a response dict with the following 
+        arguments:
+
         id -- json request id
         result -- result or null on error
         error -- error or null if result is Ok
 
+        The version 2.0 provides a response dict with the following named 
+        paramters:
+
+        jsonrpc -- jsonrpc version 2.0 or higher in future versions
+        id -- json request id
+        result -- result if no error is raised
+        error -- error if any given
+
         """
-        id = self._request.jsonId
-        if id is not None:
-            result = premarshal(result)
-            wrapper = {'id': id}
-            wrapper['result'] = result
-            wrapper['error'] = None
-            json = zope.component.getUtility(IJSONWriter)
-            encoding = getCharsetUsingRequest(self._request)
-            result = json.write(wrapper)
-            body = self._prepareResult(result)
-            super(JSONRPCResponse,self).setResult(body)
-            logger.log(DEBUG, "%s" % result)
+        jsonId = self._request.jsonId
+        jsonVersion = self._request.jsonVersion
+        result = premarshal(result)
+        if jsonVersion == 1.0:
+            wrapper = {'result': result, 'error': None, 'id': jsonId}
+        elif jsonVersion == 1.1:
+            wrapper = {'version': jsonVersion, 'result': result, 'error': None,
+                       'id': jsonId}
         else:
-            self.setStatus(204)
-            super(JSONRPCResponse,self).setResult('')
+            wrapper = {'jsonrpc': jsonVersion, 'result': result, 'id': jsonId}
+        json = zope.component.getUtility(IJSONWriter)
+        encoding = getCharsetUsingRequest(self._request)
+        result = json.write(wrapper)
+        body = self._prepareResult(result)
+        super(JSONRPCResponse,self).setResult(body)
+        logger.log(DEBUG, "%s" % result)
 
     def _prepareResult(self, result):
         # we've asked json to return unicode; result should be unicode
@@ -269,9 +353,25 @@
             exc_data.append( "** %s: %s" % exc_info[:2])
         logger.log(logging.ERROR, "\n".join(exc_data))
         s = '%s: %s' % (getattr(t, '__name__', t), value)
-        wrapper = {'id': self._request.jsonId}
-        wrapper['result'] = None
-        wrapper['error'] = s
+        if self._request.jsonVersion == 1.0:
+            wrapper = {'result': None,
+                       'error': s,
+                       'id': self._request.jsonId,}
+        elif self._request.jsonVersion == 1.1:
+            wrapper = {'version': self._request.jsonVersion,
+                       'result': None,
+                       'error': s,
+                       'id': self._request.jsonId,}
+        else:
+            # TODO: implement better error handling, use the right error codes.
+            # see:
+            # http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal#error-object
+            wrapper = {'jsonrpc': self._request.jsonVersion,
+                       'error': {'code': -32603,
+                                 'message': 'Invalid JSON-RPC',
+                                 'data': s},
+                       'id': self._request.jsonId}
+
         json = zope.component.getUtility(IJSONWriter)
         result = json.write(wrapper)
         body = self._prepareResult(result)

Modified: z3c.jsonrpc/trunk/src/z3c/jsonrpc/testing.py
===================================================================
--- z3c.jsonrpc/trunk/src/z3c/jsonrpc/testing.py	2008-08-02 12:50:14 UTC (rev 89215)
+++ z3c.jsonrpc/trunk/src/z3c/jsonrpc/testing.py	2008-08-02 12:51:33 UTC (rev 89216)
@@ -20,9 +20,9 @@
 
 import persistent
 import zope.interface
+import zope.testing.cleanup
 from zope.testing import doctest
 from zope.app.testing import functional
-from zope.app.testing.functional import HTTPCaller
 
 from z3c.json.interfaces import IJSONReader
 from z3c.json.converter import JSONReader
@@ -206,19 +206,15 @@
 # Doctest setup
 #
 ###############################################################################
-
-def FunctionalDocFileSuite(*paths, **kw):
+def _prepare_doctest_keywords(kw):
     globs = kw.setdefault('globs', {})
     globs['http'] = HTTPCaller()
     globs['getRootFolder'] = functional.getRootFolder
     globs['sync'] = functional.sync
 
-    kw['package'] = doctest._normalize_module(kw.get('package'))
-
     kwsetUp = kw.get('setUp')
     def setUp(test):
         functional.FunctionalTestSetup().setUp()
-
         if kwsetUp is not None:
             kwsetUp(test)
     kw['setUp'] = setUp
@@ -231,11 +227,19 @@
     kw['tearDown'] = tearDown
 
     if 'optionflags' not in kw:
-        kw['optionflags'] = (doctest.ELLIPSIS
+        old = doctest.set_unittest_reportflags(0)
+        doctest.set_unittest_reportflags(old)
+        kw['optionflags'] = (old
+                             | doctest.ELLIPSIS
                              | doctest.REPORT_NDIFF
                              | doctest.NORMALIZE_WHITESPACE)
 
-    suite = functional.FunctionalDocFileSuite(*paths, **kw)
+
+def FunctionalDocFileSuite(*paths, **kw):
+    # use our custom HTTPCaller and layer
+    kw['package'] = doctest._normalize_module(kw.get('package'))
+    _prepare_doctest_keywords(kw)
+    suite = doctest.DocFileSuite(*paths, **kw)
     suite.layer = JSONRPCTestingLayer
     return suite
 
@@ -290,11 +294,11 @@
 #
 ###############################################################################
 
-
-
 def setUp(test):
     setUpTestAsModule(test, name='README')
 
 
 def tearDown(test):
+    # ensure that we cleanup everything
+    zope.testing.cleanup.cleanUp()
     tearDownTestAsModule(test)

Modified: z3c.jsonrpc/trunk/src/z3c/jsonrpc/tests/test_request.py
===================================================================
--- z3c.jsonrpc/trunk/src/z3c/jsonrpc/tests/test_request.py	2008-08-02 12:50:14 UTC (rev 89215)
+++ z3c.jsonrpc/trunk/src/z3c/jsonrpc/tests/test_request.py	2008-08-02 12:51:33 UTC (rev 89216)
@@ -32,12 +32,7 @@
 
     require_docstrings = 0
 
-    def getDefaultTraversal(self, request, ob):
-        if hasattr(ob, 'browserDefault'):
-            return ob.browserDefault(request)
-        return ob, ()
 
-
 class TestJSONRPCRequest(JSONRPCRequest, HTTPCharsets):
     """Make sure that our request also implements IHTTPCharsets, so that we do
     not need to register any adapters."""
@@ -47,33 +42,18 @@
         JSONRPCRequest.__init__(self, *args, **kw)
 
 
-class TestCall:
+class PositionalTestCall:
     def __init__(self):
-        self.body = '{"id":"httpReq","method":"action","params":[1]}'
+        self.body = '{"jsonrpc":"2.0","id":"httpReq","method":"positional","params":["aaa","bbb"]}'
         self.headers = []
 
 
-jsonrpc_call = TestCall()
-
-
-class ParamTestCall:
+class NamedTestCall:
     def __init__(self):
-        self.body = '{"id":"httpReq","method":"keyworded","params":[{"kw1":"aaa"}]}'
+        self.body = '{"jsonrpc":"2.0","id":"httpReq","method":"named","params":{"kw1":"aaa","kw2":"bbb"}}'
         self.headers = []
 
 
-class Param1_1SpecTestCall1:
-    def __init__(self):
-        self.body = '{"id":"httpReq","method":"keyworded","params":{"1":1,"kw1":"aaa"}}'
-        self.headers = []
-
-
-class Param1_1SpecTestCall2:
-    def __init__(self):
-        self.body = '{"id":"httpReq","method":"action1_1","params":{"1":1,"kw1":"aaa"}}'
-        self.headers = []
-
-
 class JSONRPCTests(unittest.TestCase):
     """The only thing different to HTTP is the input processing; so there
        is no need to redo all the HTTP tests again.
@@ -104,28 +84,36 @@
         class Item(object):
 
             def __call__(self, a, b):
-                return "%s, %s" % (`a`, `b`)
+                return "%s, %s" % (a, b)
 
             def doit(self, a, b):
                 return 'do something %s %s' % (a, b)
 
         class View(object):
 
-            def action(self, a, kw1=None):
-                return "Parameter[type: %s; value: %s" %(
-                    type(a).__name__, `a`)
+            def __init__(self, context, request):
+                self.context = context
+                self.request = request
 
-            def keyworded(self, a, kw1="spam"):
-                return "kw1: [type: %s; value: %s]" %(
-                    type(kw1).__name__, `kw1`)
+            def positional(self, a, b):
+                return "Parameter: a; type: %s; value: %s" %(
+                    type(a).__name__, a)
 
-            def action1_1(self, a, kw1=None):
-                return "Parameter[type: %s; value: %s" %(
-                    type(a).__name__, `a`)
+            def named(self, kw1=None, kw2=None):
+                kw1 = self.request.get('kw1', kw1)
+                kw2 = self.request.get('kw2', kw2)
+                return "Keyword: kw1; type: %s; value: %s] " \
+                       "Keyword: kw2; type: %s; value: %s]" %(
+                    type(kw1).__name__, kw1, type(kw2).__name__, kw2)
 
         class Item2(object):
-            view = View()
 
+            request = None
+
+            @property
+            def view(self):
+                return View(self, self.request)
+
         self.app = AppRoot()
         self.app.folder = Folder()
         self.app.folder.item = Item()
@@ -137,7 +125,6 @@
         env.update(extra_env)
         if len(body.body):
             env['CONTENT_LENGTH'] = str(len(body.body))
-
         publication = Publication(self.app)
         instream = StringIO(body.body)
         request = TestJSONRPCRequest(instream, env)
@@ -146,42 +133,31 @@
 
 
     def testProcessInput(self):
-        req = self._createRequest({}, jsonrpc_call)
+        req = self._createRequest({}, PositionalTestCall())
+        self.app.folder.item2.request = req
         req.processInputs()
-        self.failUnlessEqual(req._args, (1,))
-        self.failUnlessEqual(tuple(req._path_suffix), ('action',))
+        self.failUnlessEqual(req._args, (u'aaa', u'bbb'))
+        self.failUnlessEqual(tuple(req._path_suffix), ('positional',))
 
-
-    def testTraversal(self):
-        req = self._createRequest({}, jsonrpc_call)
+    def testPositional(self):
+        req = self._createRequest({}, PositionalTestCall())
+        self.app.folder.item2.request = req
         req.processInputs()
         action = req.traverse(self.app)
         self.failUnlessEqual(action(*req._args),
-                             "Parameter[type: int; value: 1")
+                             'Parameter: a; type: unicode; value: aaa')
 
     def testKeyword(self):
-        req = self._createRequest({}, ParamTestCall())
+        req = self._createRequest({}, NamedTestCall())
+        self.app.folder.item2.request = req
         req.processInputs()
         action = req.traverse(self.app)
         self.failUnlessEqual(action(*req._args, **req.form),
-                             "kw1: [type: unicode; value: u'aaa']")
+            u'Keyword: kw1; type: unicode; value: aaa] Keyword: kw2; type: unicode; value: bbb]')
 
-    def test1_1spec_kw(self):
-        req = self._createRequest({}, Param1_1SpecTestCall1())
-        req.processInputs()
-        action = req.traverse(self.app)
-        self.failUnlessEqual(action(*req._args, **req.form),
-                             "kw1: [type: unicode; value: u'aaa']")
-
-    def test1_1spec2_p(self):
-        req = self._createRequest({}, Param1_1SpecTestCall2())
-        req.processInputs()
-        action = req.traverse(self.app)
-        self.failUnlessEqual(action(*req._args, **req.form),
-                             "Parameter[type: int; value: 1")
-
     def testJSONRPCMode(self):
-        req = self._createRequest({}, jsonrpc_call)
+        req = self._createRequest({}, PositionalTestCall())
+        self.app.folder.item2.request = req
         req.processInputs()
         self.failUnlessEqual(req['JSONRPC_MODE'],True)
 



More information about the Checkins mailing list