p01.jsonrpc


Namep01.jsonrpc JSON
Version 0.8.1 PyPI version JSON
download
home_pagehttp://pypi.python.org/pypi/p01.jsonrpc
SummaryJSON RPC server and client implementation for Zope3
upload_time2025-08-22 13:54:48
maintainerNone
docs_urlNone
authorRoger Ineichen and the Zope Community
requires_pythonNone
licenseZPL 2.1
keywords zope3 z3c p01 json rpc json-rpc server client
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            .. caution:: 

    This repository has been archived. If you want to work on it please open a ticket in https://github.com/zopefoundation/meta/issues requesting its unarchival.

This package provides an JSON-RPC server implementation for Zope3.


Detailed Documentation
**********************

=======
JSONRPC
=======

JSON is javascript object notation. JSON-RPC performs the same service
as XML-RPC, except the transport is JSON instead of XML.

Many thanks to Jim Washington for the work on zif.jsonserver. This project uses
many code writen by Jim. I implemented an additional python JSONRPC proxy which
can communicate with the server. This means we can use this library to call
JSON from python to python. The JSON-RPC proxy uses similar patterns like the
XML-RPC implementation.

There is also an additional xmlhttp and json javascript implementation which
offers a JSON-RPC proxy implementation for JavaScript.

This project provides the proposed request type "application/json". The request
type "application/json-rpc" is supported as long it is not officialy deprecated.

The goal of this project is to provide a JSON-RPC implementation. Simple
Browser views which handle JSON calls with a BrowserRequest are not supported
by this package. I'm still not sure if this is good or bad and in which
direction I will go with this package.

Some of my goals are right now, but can change in the future if I'll understand
all the concepts around JSON, e.g. JSPON, JSONP, CrossSite etc:

- provide a secure way to handle JSON calls from client to server.
  I hope we can implement JSONRequest some days. CrossSite seems to use a
  intereting concept

- Simple pythonic implementation

- Use together with JQuery (see http://www.jquery.org).

- No other dependency then JQuery and basic zope packages.

- well tested (this is not the case for JavaScript right now)


About JSON
----------

See www.json.org for more information about JSON.

See http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html for more information
about the JSON 1.1 specification.


What this package can't do
--------------------------

JSON and this package have different limitations. This package can right now
not handle the following tasks:

- Handle fileupload

- Handle GET request

Note that the JSONRPCRequest implementation is based on the IHTTPRequest, this
means that there is no other browser page available if you call them in
python, e.g. getMultiAdapter((context, request), name='myViewName'). This is
explicitly done this way. If you'd like to use content form such browser pages
in a JSON request/call, you can inherit your skin form IJSONRPCLayer and
IBrowserRequest and register your JSON-RPC views for this custom layer.


Setup
-----

  >>> import json
  >>> import p01.publisher.testing
  >>> import p01.jsonrpc.testing
  >>> from p01.jsonrpc.testing import getJSONRPCTestProxy


JSON-RPC server
---------------

The JSON server looks for content-type "application/json", and handles those
requests as JSON-RPC. The official mime-type for JSON is "application/json"
The old content type ``application/json-rpc`` is supported too.

Let's define a content object:

  >>> import zope.interface
  >>> class IDemoContent(zope.interface.Interface):
  ...     """Demo content interface."""
  ...     __module__ = 'p01.jsonrpc.tests'

  >>> import persistent
  >>> @zope.interface.implementer(IDemoContent)
  ... class DemoContent(persistent.Persistent):
  ...     """Demo content."""

And define a JSONRPC method view:

  >>> from p01.jsonrpc import publisher
  >>> class DemoView(publisher.MethodPublisher):
  ...     """Sample JSON view."""
  ...
  ...     def hello(self):
  ...         return u"Hello World"
  ...
  ...     def greeting(self, name):
  ...         return u"Hello %s" % name
  ...
  ...     def mixedparams(self, prefix, bar=None, foo=None):
  ...         # Note; keyword arguments can be found in request.form
  ...         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
  ...
  ...     def forceValueError(self):
  ...         raise ValueError('Something was wrong in server method.')

Let's define a content object that is a container:

  >>> import zope.interface
  >>> class IDemoContainer(zope.container.interfaces.IReadContainer):
  ...     """Demo container interface."""
  ...     __module__ = 'p01.jsonrpc.tests'

  >>> import persistent
  >>> from zope.container import btree

  >>> @zope.interface.implementer(IDemoContainer)
  ... class DemoContainer(btree.BTreeContainer):
  ...     """Demo container."""

And define a JSONRPC method view:

  >>> from p01.jsonrpc import publisher
  >>> class DemoContainerView(publisher.MethodPublisher):
  ...     """Sample JSON view."""
  ...
  ...     def available(self):
  ...         return u"Hello World"
  ...
  ...     def greeting(self, name):
  ...         return u"Hello %s" % name
  ...
  ...     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
  ...
  ...     def forceValueError(self):
  ...         raise ValueError('Something was wrong in server method.')


Make them available under the fake package ``jsonsamples``:

  >>> import sys
  >>> sys.modules['custom'] = type('Module', (), {})()
  >>> sys.modules['custom'].IDemoContent = IDemoContent
  >>> sys.modules['custom'].DemoContent = DemoContent
  >>> sys.modules['custom'].DemoView = DemoView
  >>> sys.modules['custom'].IDemoContainer = IDemoContainer
  >>> sys.modules['custom'].DemoContainer = DemoContainer
  >>> sys.modules['custom'].DemoContainerView = DemoContainerView

Let's show how we can register a jsonrpc view:

  >>> from zope.configuration import xmlconfig
  >>> import p01.jsonrpc
  >>> context = xmlconfig.file('meta.zcml', p01.jsonrpc)
  >>> context = xmlconfig.string("""
  ... <configure
  ...     xmlns:p01="http://namespaces.zope.org/p01">
  ...   <p01:jsonrpc
  ...       for="custom.IDemoContent"
  ...       class="custom.DemoView"
  ...       permission="zope.Public"
  ...       methods="hello greeting mixedparams kws showId forceValueError"
  ...       layer="p01.jsonrpc.testing.IJSONRPCTestSkin"
  ...       />
  ... </configure>
  ... """, context)

Let's show how we can register a jsonrpc view for the container:
(The container class needs permission configuration too)

  >>> context = xmlconfig.file('meta.zcml', p01.jsonrpc)
  >>> context = xmlconfig.file('meta.zcml', zope.security, context)
  >>> context = xmlconfig.string("""
  ... <configure
  ...     xmlns:p01="http://namespaces.zope.org/p01"
  ...     xmlns="http://namespaces.zope.org/zope">
  ...     <class class="custom.DemoContainer">
  ...       <allow
  ...           interface="custom.IDemoContainer"
  ...           />
  ...     </class>
  ...   <p01:jsonrpc
  ...       for="custom.IDemoContainer"
  ...       class="custom.DemoContainerView"
  ...       permission="zope.Public"
  ...       methods="available greeting mixedparams kws showId forceValueError"
  ...       layer="p01.jsonrpc.testing.IJSONRPCTestSkin"
  ...       />
  ... </configure>
  ... """, context)


Now we will setup a content object in our site:

  >>> import p01.jsonrpc.testing
  >>> p01.jsonrpc.testing.setUpTestApplication()
  <JSONRPCTestApplication>

  >>> site  = getRootFolder()
  >>> site
  <JSONRPCTestApplication>

  >>> content = DemoContent()
  >>> site[u'content'] = content
  >>> container = DemoContainer()
  >>> site[u'container'] = container

Now we can call the method from our JSONRPC view:

  >>> request = p01.jsonrpc.testing.TestRequest()
  >>> demoView = DemoView(content, request)
  >>> print(demoView.hello())
  Hello World

But this is not intuitive. Let's see how we can traverse to the method ``hello``
with the traverser:

  >>> from p01.jsonrpc.publisher import MethodTraverser
  >>> methodTraverser = MethodTraverser(demoView, request)
  >>> print(methodTraverser.publishTraverse(request, 'hello')())
  Hello World

Now we try to access the JSON-RPC view method with a test browser. As you can
see, there is no view accessible. This is because the JSONRPC view is not a
browser view and is not traversable. The error shows that the request factory
falls back to the browser request factory:

  >>> handleErrors = False
  >>> useBasicAuth = True
  >>> browser = p01.publisher.testing.getTestBrowser(handleErrors=handleErrors,
  ...     useBasicAuth=useBasicAuth)
  >>> browser.addHeader('Accept-Language', 'en')
  >>> browser.addHeader('Content-Type', 'application/json')
  >>> siteURL = 'http://localhost/++skin++JSONRPCTestSkin'
  >>> browser.open(siteURL + '/content/hello')
  Traceback (most recent call last):
  ...
  zope.publisher.interfaces.NotFound: Object: <JSONRPCTestApplication>, name: ...'++skin++JSONRPCTestSkin'

Testing
-------

If you need to test a JSONRPC view you can use the test proxy like shown
below in the ``JSON-RPC proxy`` section.


JSON-RPC proxy
--------------

The jsonrpc package provides also a JSON-RPC proxy implementation. This
implementation is similar to the one known from xmlrpclib except that it can
handle JSON instead of XML.

Let's try to call our method called ``hello`` we defined before:

  >>> proxy = getJSONRPCTestProxy(siteURL + '/content', handleErrors=False)
  >>> print(proxy.hello())
  Hello World

  >>> proxy2 = getJSONRPCTestProxy(siteURL + '/container')
  >>> print(proxy2.available())
  Hello World

Now let's make a remote procedure call with a argument:

  >>> print(proxy.greeting(u'Jessy'))
  Hello Jessy

Let's call named arguments:

  >>> print(proxy.kws(bar=u'BAR', foo=u'FOO'))
  None FOO BAR

There is also an ``id`` in the json response. Let's use such a json request id
in our JSONRPCProxy:

  >>> proxy = getJSONRPCTestProxy(siteURL + '/content', jsonId = u'my id')
  >>> print(proxy.showId())
  The json id is: my id

The proxy also knows this id as jsonId:

  >>> print(proxy.jsonId)
  my id


JSON-RPC Versions
-----------------

Let's test the different JSON-RPC versions starting with version 1.0:

  >>> v1 = getJSONRPCTestProxy(siteURL + '/container', jsonVersion='1.0')
  >>> print(v1.available())
  Hello World

  >>> print(v1.greeting(u'Jessy'))
  Hello Jessy

  >>> print(v1.kws(bar=u'BAR', foo=u'FOO'))
  None FOO BAR

  >>> v1 = getJSONRPCTestProxy(siteURL + '/content', jsonId = u'my id',
  ...     jsonVersion='1.0')
  >>> print(v1.showId())
  The json id is: my id

  >>> print(v1.jsonId)
  my id

Now test with JSON-RPC version 1.1:

  >>> v11 = getJSONRPCTestProxy(siteURL + '/container', jsonVersion='1.1')
  >>> print(v11.available())
  Hello World

  >>> print(v11.greeting(u'Jessy'))
  Hello Jessy

  >>> print(v11.kws(bar=u'BAR', foo=u'FOO'))
  None FOO BAR

  >>> v11 = getJSONRPCTestProxy(siteURL + '/content', jsonId = u'my id',
  ...     jsonVersion='1.1')
  >>> print(v11.showId())
  The json id is: my id

  >>> print(v11.jsonId)
  my id

Now test with JSON-RPC version 2.0:

  >>> v2 = getJSONRPCTestProxy(siteURL + '/container', jsonVersion='2.0')
  >>> print(v2.available())
  Hello World

  >>> print(v2.greeting(u'Jessy'))
  Hello Jessy

  >>> print(v2.kws(bar=u'BAR', foo=u'FOO'))
  None FOO BAR

  >>> v2 = getJSONRPCTestProxy(siteURL + '/content', jsonId = u'my id',
  ...     jsonVersion='2.0')
  >>> print(v2.showId())
  The json id is: my id

  >>> print(v2.jsonId)
  my id


Mixed parameters
----------------

Note the keyword arguments will get stored in the request.form. Important
to know is that JSON-RPC does not support positional and named arguments in
one method call.

  >>> v1.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

  >>> v11.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

  >>> v2.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


Error handling
--------------

See what happens if the server raises an Exception. We will get a response
error with additional error content:

  >>> proxy.forceValueError()
  Traceback (most recent call last):
  ...
  p01.json.exceptions.ResponseError: Received error from server: {'code': -32603, 'message': 'Internal error', 'data': {'i18nMessage': 'Internal error'}}

and the error content looks like:

  >>> print(json.dumps(proxy.error, sort_keys=True))
  {"code": -32603, "data": {"i18nMessage": "Internal error"}, "message": "Internal error"}

The error property gets reset on the next successfull call:

  >>> x = proxy.showId()
  >>> proxy.error is None
  True

And now we force a ResponseError with a fake json reader. This will now raise a
ResponseError:

  >>> def forceResponseErrorJSONReader(data, *args, **kwargs):
  ...     aBadString = u'{"id":"jsonrpc", "method":"hello", "no-params"}'
  ...     return json.loads(aBadString, *args, **kwargs)

  >>> proxy = getJSONRPCTestProxy(siteURL + '/content',
  ...     jsonReader=forceResponseErrorJSONReader)
  >>> proxy.hello()
  Traceback (most recent call last):
  ...
  p01.json.exceptions.ResponseError: Expecting ':' delimiter: line 1 column 47 (char 46)

the error message is stored in the proxy too:

  >>> proxy.error
  "Expecting ':' delimiter: line 1 column 47 (char 46)"


Transport
~~~~~~~~~

We used the JSONRPCTestProxy here for testing. This JSON-RPC proxy is a wrapper
for the original JSONRPCProxy and adds handleErrors support and a special
Transport layer which uses a testing caller. You can use one of the different
Transport layers defined in the z3c.json.transport module in real usecases
together with the default JSONRPCProxy implementation.


cleanup
-------

Now we need to clean up the custom module.

  >>> del sys.modules['custom']


==========
Directives
==========

JSONRPC directive
-----------------

Show how we can use the jsonrpc directive. Register the meta configuration for 
the directive.

  >>> from zope.configuration import xmlconfig
  >>> import p01.jsonrpc
  >>> context = xmlconfig.file('meta.zcml', p01.jsonrpc)

Now register the view defined in the testing module within the ``p01:jsonrpc``
directive:

  >>> context = xmlconfig.string("""
  ... <configure
  ...     xmlns:p01="http://namespaces.zope.org/p01">
  ...   <p01:jsonrpc
  ...       for="p01.jsonrpc.testing.IA"
  ...       class="p01.jsonrpc.testing.MethodsA"
  ...       permission="zope.Public"
  ...       methods="hello"
  ...       />
  ... </configure>
  ... """, context)

Let's check if the view is registered as adapter:

  >>> import zope.component
  >>> from p01.jsonrpc.testing import A
  >>> from p01.jsonrpc.testing import TestRequest
  >>> a = A()
  >>> request = TestRequest()
  >>> zope.component.queryMultiAdapter((a, request), name='hello')
  <p01.jsonrpc.zcml.MethodsA object at ...>

We can also use a layer interface wich will restrict our view registration to
a specific request type. Provide such a request type layer:

  >>> from p01.jsonrpc.testing import IJSONRPCTestLayer
  >>> demoRequest = TestRequest()
  >>> zope.interface.directlyProvides(demoRequest, IJSONRPCTestLayer)

And register a new JSON-RPC view:

  >>> context = xmlconfig.string("""
  ... <configure
  ...     xmlns:p01="http://namespaces.zope.org/p01">
  ...   <p01:jsonrpc
  ...       for="p01.jsonrpc.testing.IB"
  ...       class="p01.jsonrpc.testing.MethodsB"
  ...       permission="zope.Public"
  ...       methods="hello"
  ...       layer="p01.jsonrpc.testing.IJSONRPCTestLayer"
  ...       />
  ... </configure>
  ... """, context)

Setup a new content stub:

  >>> from p01.jsonrpc.testing import B
  >>> b = B()

And test the view within our new layer:

  >>> zope.component.queryMultiAdapter((b, demoRequest), name='hello')
  <p01.jsonrpc.zcml.MethodsB object at ...>

Note the object b does not know the view within the default request layer:

  >>> zope.component.queryMultiAdapter((b, request), name='hello') is None
  True


setDefaultJSONRPCSkin
---------------------

  >>> from p01.jsonrpc import interfaces
  >>> import p01.jsonrpc.zcml

  >>> class IMySkin(zope.interface.Interface):
  ...     __module__ = 'p01.jsonrpc.tests'
  >>> zope.interface.directlyProvides(IMySkin, interfaces.IJSONRPCSkinType)

Before we setup a default request, we try to set a default request for our
request:

  >>> from zope.publisher.skinnable import setDefaultSkin
  >>> setDefaultSkin(request)

Our request should not provide any default kins since we didn't register any:

  >>> IMySkin.providedBy(request)
  False

Now let's register a default skin:

  >>> zope.component.provideUtility(IMySkin, interfaces.IJSONRPCSkinType,
  ...     name='JSONRPC')
  >>> p01.jsonrpc.zcml.setDefaultJSONRPCSkin('JSONRPC')

We can lookup a default skin from the adapter registry: 

  >>> from zope.publisher.interfaces import IDefaultSkin
  >>> adapters = zope.component.getSiteManager().adapters
  >>> default = adapters.lookup((interfaces.IJSONRPCRequest,), IDefaultSkin, '')
  >>> default is IMySkin
  True

Since we have a default skin utility registered as a skin type for our 
request, a new request instance should provide the default skin:

  >>> request = TestRequest()
  >>> setDefaultSkin(request)
  >>> IMySkin.providedBy(request)
  True

We can get the applied default skin by look for our skin type:

  >>> for iface in zope.interface.providedBy(request):
  ...     if interfaces.IJSONRPCSkinType.providedBy(iface):
  ...         print("<InterfaceClass %s>" % iface)
  <InterfaceClass p01.jsonrpc.tests.IMySkin>


=======
CHANGES
=======

0.8.1 (2025-08-19)
------------------

- python 3 migration


0.8.0 (2025-06-04)
------------------

- migrated from z3c.jsonrpc to p01.jsonrpc

- fix tests, use p01.testbrowser and p01.publisher as dependency, remove
  zope.app.testing dependency


0.7.2 (2013-10-11)
------------------

- ``handleException``: provide human readable traceback


0.7.1 (2012-11-27)
------------------

- Fix ``JSONRPCTestTransport`` to include the request full host.
  Until now it ate the port.


0.7.0 (2012-03-25)
------------------

- Fix: added missing exception import for ParseError in publisher.processInputs

- import doctest from python


0.6.0 (2010-01-27)
------------------

- cleanup setup dependencies, adjust ftesting.zcml

- adjust coverage report setup

- implemented error view concept which will work with ZopePublication

- implemented default error view for known zope and JSON-RPC errors

- use DirectResult in response

- removed unauthenticated error view. This was not working and requires a
  custom concept supported by the used java script library used at client
  side


Version 0.5.4 (2009-04-07)
--------------------------

- handle empty and none-existing params in jsonrpc requests


Version 0.5.3 (2009-03-10)
--------------------------

- Fix: reflect skin lookup changes in zope.publisher. Use the new skinnable
  concept.

- Fix: The default skin didn't get applied based on the inherited concept give
  from the zope.publisher.browser implementation because our JSON-RPC request
  doesn't provide IBrowserRequest. Added a workaround which will apply a given
  IDefaultSkin during request instance creation.


Version 0.5.2 (2009-02-24)
--------------------------

- added tests for all JSON-RPC versions

- Feature: implemented defaultJSONRPCSkin directive

- Feature: support non positional arguments for all jsonrpc versions. There is
  now no distinction in handling method parameters for all supported versions.

- Fix: for jsonrpc version 1.1 :
   - must not provide "error" property in case of success
   - must not provide "result" property in case of error

- Fix: removed develop path for z3c.json from buildout.cfg

- Fix: publisher checks for version id as a string not a float

- Feature: 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.

- Feature: Added initial version of JSON-RPC exceptions.

- Added explicit test cleanup since some zope testing change left over a
  global adapter registry from old connections

- Removed unused dependency to z3c.layer in test setup

- Removed unused dependency on z3c.i18n.


Version 0.5.1 (2008-01-24)
--------------------------

- Improve meta-data.

- Bug: The skin code relied on un-released API that was actually later
  reverted.


Version 0.5.0 (2008-01-21)
--------------------------

- Initial Release
            

Raw data

            {
    "_id": null,
    "home_page": "http://pypi.python.org/pypi/p01.jsonrpc",
    "name": "p01.jsonrpc",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": null,
    "keywords": "zope3 z3c p01 json rpc json-rpc server client",
    "author": "Roger Ineichen and the Zope Community",
    "author_email": "zope-dev@zope.org",
    "download_url": "https://files.pythonhosted.org/packages/93/7f/526295944580cfdbe3e5bc43d1719aafcfe2417549ef6446a59c3ee13067/p01.jsonrpc-0.8.1.tar.gz",
    "platform": null,
    "description": ".. caution:: \n\n    This repository has been archived. If you want to work on it please open a ticket in https://github.com/zopefoundation/meta/issues requesting its unarchival.\n\nThis package provides an JSON-RPC server implementation for Zope3.\n\n\nDetailed Documentation\n**********************\n\n=======\nJSONRPC\n=======\n\nJSON is javascript object notation. JSON-RPC performs the same service\nas XML-RPC, except the transport is JSON instead of XML.\n\nMany thanks to Jim Washington for the work on zif.jsonserver. This project uses\nmany code writen by Jim. I implemented an additional python JSONRPC proxy which\ncan communicate with the server. This means we can use this library to call\nJSON from python to python. The JSON-RPC proxy uses similar patterns like the\nXML-RPC implementation.\n\nThere is also an additional xmlhttp and json javascript implementation which\noffers a JSON-RPC proxy implementation for JavaScript.\n\nThis project provides the proposed request type \"application/json\". The request\ntype \"application/json-rpc\" is supported as long it is not officialy deprecated.\n\nThe goal of this project is to provide a JSON-RPC implementation. Simple\nBrowser views which handle JSON calls with a BrowserRequest are not supported\nby this package. I'm still not sure if this is good or bad and in which\ndirection I will go with this package.\n\nSome of my goals are right now, but can change in the future if I'll understand\nall the concepts around JSON, e.g. JSPON, JSONP, CrossSite etc:\n\n- provide a secure way to handle JSON calls from client to server.\n  I hope we can implement JSONRequest some days. CrossSite seems to use a\n  intereting concept\n\n- Simple pythonic implementation\n\n- Use together with JQuery (see http://www.jquery.org).\n\n- No other dependency then JQuery and basic zope packages.\n\n- well tested (this is not the case for JavaScript right now)\n\n\nAbout JSON\n----------\n\nSee www.json.org for more information about JSON.\n\nSee http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html for more information\nabout the JSON 1.1 specification.\n\n\nWhat this package can't do\n--------------------------\n\nJSON and this package have different limitations. This package can right now\nnot handle the following tasks:\n\n- Handle fileupload\n\n- Handle GET request\n\nNote that the JSONRPCRequest implementation is based on the IHTTPRequest, this\nmeans that there is no other browser page available if you call them in\npython, e.g. getMultiAdapter((context, request), name='myViewName'). This is\nexplicitly done this way. If you'd like to use content form such browser pages\nin a JSON request/call, you can inherit your skin form IJSONRPCLayer and\nIBrowserRequest and register your JSON-RPC views for this custom layer.\n\n\nSetup\n-----\n\n  >>> import json\n  >>> import p01.publisher.testing\n  >>> import p01.jsonrpc.testing\n  >>> from p01.jsonrpc.testing import getJSONRPCTestProxy\n\n\nJSON-RPC server\n---------------\n\nThe JSON server looks for content-type \"application/json\", and handles those\nrequests as JSON-RPC. The official mime-type for JSON is \"application/json\"\nThe old content type ``application/json-rpc`` is supported too.\n\nLet's define a content object:\n\n  >>> import zope.interface\n  >>> class IDemoContent(zope.interface.Interface):\n  ...     \"\"\"Demo content interface.\"\"\"\n  ...     __module__ = 'p01.jsonrpc.tests'\n\n  >>> import persistent\n  >>> @zope.interface.implementer(IDemoContent)\n  ... class DemoContent(persistent.Persistent):\n  ...     \"\"\"Demo content.\"\"\"\n\nAnd define a JSONRPC method view:\n\n  >>> from p01.jsonrpc import publisher\n  >>> class DemoView(publisher.MethodPublisher):\n  ...     \"\"\"Sample JSON view.\"\"\"\n  ...\n  ...     def hello(self):\n  ...         return u\"Hello World\"\n  ...\n  ...     def greeting(self, name):\n  ...         return u\"Hello %s\" % name\n  ...\n  ...     def mixedparams(self, prefix, bar=None, foo=None):\n  ...         # Note; keyword arguments can be found in request.form\n  ...         return u\"%s %s %s\" % (prefix, bar, foo)\n  ...\n  ...     def kws(self, adam=None, foo=None, bar=None):\n  ...         # Note; keyword arguments can be found in request.form\n  ...         a = self.request.get('adam')\n  ...         b = self.request.form.get('foo')\n  ...         c = self.request.form.get('bar')\n  ...         return u\"%s %s %s\" % (a, b, c)\n  ...\n  ...     def showId(self):\n  ...         return u\"The json id is: %s\" % self.request.jsonId\n  ...\n  ...     def forceValueError(self):\n  ...         raise ValueError('Something was wrong in server method.')\n\nLet's define a content object that is a container:\n\n  >>> import zope.interface\n  >>> class IDemoContainer(zope.container.interfaces.IReadContainer):\n  ...     \"\"\"Demo container interface.\"\"\"\n  ...     __module__ = 'p01.jsonrpc.tests'\n\n  >>> import persistent\n  >>> from zope.container import btree\n\n  >>> @zope.interface.implementer(IDemoContainer)\n  ... class DemoContainer(btree.BTreeContainer):\n  ...     \"\"\"Demo container.\"\"\"\n\nAnd define a JSONRPC method view:\n\n  >>> from p01.jsonrpc import publisher\n  >>> class DemoContainerView(publisher.MethodPublisher):\n  ...     \"\"\"Sample JSON view.\"\"\"\n  ...\n  ...     def available(self):\n  ...         return u\"Hello World\"\n  ...\n  ...     def greeting(self, name):\n  ...         return u\"Hello %s\" % name\n  ...\n  ...     def mixedparams(self, prefix, foo=None, bar=None):\n  ...         # Note; keyword arguments can be found in request.form\n  ...         return u\"%s %s %s\" % (prefix, foo, bar)\n  ...\n  ...     def kws(self, adam=None, foo=None, bar=None):\n  ...         # Note; keyword arguments can be found in request.form\n  ...         a = self.request.get('adam')\n  ...         b = self.request.form.get('foo')\n  ...         c = self.request.form.get('bar')\n  ...         return u\"%s %s %s\" % (a, b, c)\n  ...\n  ...     def showId(self):\n  ...         return u\"The json id is: %s\" % self.request.jsonId\n  ...\n  ...     def forceValueError(self):\n  ...         raise ValueError('Something was wrong in server method.')\n\n\nMake them available under the fake package ``jsonsamples``:\n\n  >>> import sys\n  >>> sys.modules['custom'] = type('Module', (), {})()\n  >>> sys.modules['custom'].IDemoContent = IDemoContent\n  >>> sys.modules['custom'].DemoContent = DemoContent\n  >>> sys.modules['custom'].DemoView = DemoView\n  >>> sys.modules['custom'].IDemoContainer = IDemoContainer\n  >>> sys.modules['custom'].DemoContainer = DemoContainer\n  >>> sys.modules['custom'].DemoContainerView = DemoContainerView\n\nLet's show how we can register a jsonrpc view:\n\n  >>> from zope.configuration import xmlconfig\n  >>> import p01.jsonrpc\n  >>> context = xmlconfig.file('meta.zcml', p01.jsonrpc)\n  >>> context = xmlconfig.string(\"\"\"\n  ... <configure\n  ...     xmlns:p01=\"http://namespaces.zope.org/p01\">\n  ...   <p01:jsonrpc\n  ...       for=\"custom.IDemoContent\"\n  ...       class=\"custom.DemoView\"\n  ...       permission=\"zope.Public\"\n  ...       methods=\"hello greeting mixedparams kws showId forceValueError\"\n  ...       layer=\"p01.jsonrpc.testing.IJSONRPCTestSkin\"\n  ...       />\n  ... </configure>\n  ... \"\"\", context)\n\nLet's show how we can register a jsonrpc view for the container:\n(The container class needs permission configuration too)\n\n  >>> context = xmlconfig.file('meta.zcml', p01.jsonrpc)\n  >>> context = xmlconfig.file('meta.zcml', zope.security, context)\n  >>> context = xmlconfig.string(\"\"\"\n  ... <configure\n  ...     xmlns:p01=\"http://namespaces.zope.org/p01\"\n  ...     xmlns=\"http://namespaces.zope.org/zope\">\n  ...     <class class=\"custom.DemoContainer\">\n  ...       <allow\n  ...           interface=\"custom.IDemoContainer\"\n  ...           />\n  ...     </class>\n  ...   <p01:jsonrpc\n  ...       for=\"custom.IDemoContainer\"\n  ...       class=\"custom.DemoContainerView\"\n  ...       permission=\"zope.Public\"\n  ...       methods=\"available greeting mixedparams kws showId forceValueError\"\n  ...       layer=\"p01.jsonrpc.testing.IJSONRPCTestSkin\"\n  ...       />\n  ... </configure>\n  ... \"\"\", context)\n\n\nNow we will setup a content object in our site:\n\n  >>> import p01.jsonrpc.testing\n  >>> p01.jsonrpc.testing.setUpTestApplication()\n  <JSONRPCTestApplication>\n\n  >>> site  = getRootFolder()\n  >>> site\n  <JSONRPCTestApplication>\n\n  >>> content = DemoContent()\n  >>> site[u'content'] = content\n  >>> container = DemoContainer()\n  >>> site[u'container'] = container\n\nNow we can call the method from our JSONRPC view:\n\n  >>> request = p01.jsonrpc.testing.TestRequest()\n  >>> demoView = DemoView(content, request)\n  >>> print(demoView.hello())\n  Hello World\n\nBut this is not intuitive. Let's see how we can traverse to the method ``hello``\nwith the traverser:\n\n  >>> from p01.jsonrpc.publisher import MethodTraverser\n  >>> methodTraverser = MethodTraverser(demoView, request)\n  >>> print(methodTraverser.publishTraverse(request, 'hello')())\n  Hello World\n\nNow we try to access the JSON-RPC view method with a test browser. As you can\nsee, there is no view accessible. This is because the JSONRPC view is not a\nbrowser view and is not traversable. The error shows that the request factory\nfalls back to the browser request factory:\n\n  >>> handleErrors = False\n  >>> useBasicAuth = True\n  >>> browser = p01.publisher.testing.getTestBrowser(handleErrors=handleErrors,\n  ...     useBasicAuth=useBasicAuth)\n  >>> browser.addHeader('Accept-Language', 'en')\n  >>> browser.addHeader('Content-Type', 'application/json')\n  >>> siteURL = 'http://localhost/++skin++JSONRPCTestSkin'\n  >>> browser.open(siteURL + '/content/hello')\n  Traceback (most recent call last):\n  ...\n  zope.publisher.interfaces.NotFound: Object: <JSONRPCTestApplication>, name: ...'++skin++JSONRPCTestSkin'\n\nTesting\n-------\n\nIf you need to test a JSONRPC view you can use the test proxy like shown\nbelow in the ``JSON-RPC proxy`` section.\n\n\nJSON-RPC proxy\n--------------\n\nThe jsonrpc package provides also a JSON-RPC proxy implementation. This\nimplementation is similar to the one known from xmlrpclib except that it can\nhandle JSON instead of XML.\n\nLet's try to call our method called ``hello`` we defined before:\n\n  >>> proxy = getJSONRPCTestProxy(siteURL + '/content', handleErrors=False)\n  >>> print(proxy.hello())\n  Hello World\n\n  >>> proxy2 = getJSONRPCTestProxy(siteURL + '/container')\n  >>> print(proxy2.available())\n  Hello World\n\nNow let's make a remote procedure call with a argument:\n\n  >>> print(proxy.greeting(u'Jessy'))\n  Hello Jessy\n\nLet's call named arguments:\n\n  >>> print(proxy.kws(bar=u'BAR', foo=u'FOO'))\n  None FOO BAR\n\nThere is also an ``id`` in the json response. Let's use such a json request id\nin our JSONRPCProxy:\n\n  >>> proxy = getJSONRPCTestProxy(siteURL + '/content', jsonId = u'my id')\n  >>> print(proxy.showId())\n  The json id is: my id\n\nThe proxy also knows this id as jsonId:\n\n  >>> print(proxy.jsonId)\n  my id\n\n\nJSON-RPC Versions\n-----------------\n\nLet's test the different JSON-RPC versions starting with version 1.0:\n\n  >>> v1 = getJSONRPCTestProxy(siteURL + '/container', jsonVersion='1.0')\n  >>> print(v1.available())\n  Hello World\n\n  >>> print(v1.greeting(u'Jessy'))\n  Hello Jessy\n\n  >>> print(v1.kws(bar=u'BAR', foo=u'FOO'))\n  None FOO BAR\n\n  >>> v1 = getJSONRPCTestProxy(siteURL + '/content', jsonId = u'my id',\n  ...     jsonVersion='1.0')\n  >>> print(v1.showId())\n  The json id is: my id\n\n  >>> print(v1.jsonId)\n  my id\n\nNow test with JSON-RPC version 1.1:\n\n  >>> v11 = getJSONRPCTestProxy(siteURL + '/container', jsonVersion='1.1')\n  >>> print(v11.available())\n  Hello World\n\n  >>> print(v11.greeting(u'Jessy'))\n  Hello Jessy\n\n  >>> print(v11.kws(bar=u'BAR', foo=u'FOO'))\n  None FOO BAR\n\n  >>> v11 = getJSONRPCTestProxy(siteURL + '/content', jsonId = u'my id',\n  ...     jsonVersion='1.1')\n  >>> print(v11.showId())\n  The json id is: my id\n\n  >>> print(v11.jsonId)\n  my id\n\nNow test with JSON-RPC version 2.0:\n\n  >>> v2 = getJSONRPCTestProxy(siteURL + '/container', jsonVersion='2.0')\n  >>> print(v2.available())\n  Hello World\n\n  >>> print(v2.greeting(u'Jessy'))\n  Hello Jessy\n\n  >>> print(v2.kws(bar=u'BAR', foo=u'FOO'))\n  None FOO BAR\n\n  >>> v2 = getJSONRPCTestProxy(siteURL + '/content', jsonId = u'my id',\n  ...     jsonVersion='2.0')\n  >>> print(v2.showId())\n  The json id is: my id\n\n  >>> print(v2.jsonId)\n  my id\n\n\nMixed parameters\n----------------\n\nNote the keyword arguments will get stored in the request.form. Important\nto know is that JSON-RPC does not support positional and named arguments in\none method call.\n\n  >>> v1.mixedparams('Hello', foo=u'FOO', bar=u'BAR')\n  Traceback (most recent call last):\n  ...\n  ValueError: Mixing positional and named parameters in one call is not possible\n\n  >>> v11.mixedparams('Hello', foo=u'FOO', bar=u'BAR')\n  Traceback (most recent call last):\n  ...\n  ValueError: Mixing positional and named parameters in one call is not possible\n\n  >>> v2.mixedparams('Hello', foo=u'FOO', bar=u'BAR')\n  Traceback (most recent call last):\n  ...\n  ValueError: Mixing positional and named parameters in one call is not possible\n\n\nError handling\n--------------\n\nSee what happens if the server raises an Exception. We will get a response\nerror with additional error content:\n\n  >>> proxy.forceValueError()\n  Traceback (most recent call last):\n  ...\n  p01.json.exceptions.ResponseError: Received error from server: {'code': -32603, 'message': 'Internal error', 'data': {'i18nMessage': 'Internal error'}}\n\nand the error content looks like:\n\n  >>> print(json.dumps(proxy.error, sort_keys=True))\n  {\"code\": -32603, \"data\": {\"i18nMessage\": \"Internal error\"}, \"message\": \"Internal error\"}\n\nThe error property gets reset on the next successfull call:\n\n  >>> x = proxy.showId()\n  >>> proxy.error is None\n  True\n\nAnd now we force a ResponseError with a fake json reader. This will now raise a\nResponseError:\n\n  >>> def forceResponseErrorJSONReader(data, *args, **kwargs):\n  ...     aBadString = u'{\"id\":\"jsonrpc\", \"method\":\"hello\", \"no-params\"}'\n  ...     return json.loads(aBadString, *args, **kwargs)\n\n  >>> proxy = getJSONRPCTestProxy(siteURL + '/content',\n  ...     jsonReader=forceResponseErrorJSONReader)\n  >>> proxy.hello()\n  Traceback (most recent call last):\n  ...\n  p01.json.exceptions.ResponseError: Expecting ':' delimiter: line 1 column 47 (char 46)\n\nthe error message is stored in the proxy too:\n\n  >>> proxy.error\n  \"Expecting ':' delimiter: line 1 column 47 (char 46)\"\n\n\nTransport\n~~~~~~~~~\n\nWe used the JSONRPCTestProxy here for testing. This JSON-RPC proxy is a wrapper\nfor the original JSONRPCProxy and adds handleErrors support and a special\nTransport layer which uses a testing caller. You can use one of the different\nTransport layers defined in the z3c.json.transport module in real usecases\ntogether with the default JSONRPCProxy implementation.\n\n\ncleanup\n-------\n\nNow we need to clean up the custom module.\n\n  >>> del sys.modules['custom']\n\n\n==========\nDirectives\n==========\n\nJSONRPC directive\n-----------------\n\nShow how we can use the jsonrpc directive. Register the meta configuration for \nthe directive.\n\n  >>> from zope.configuration import xmlconfig\n  >>> import p01.jsonrpc\n  >>> context = xmlconfig.file('meta.zcml', p01.jsonrpc)\n\nNow register the view defined in the testing module within the ``p01:jsonrpc``\ndirective:\n\n  >>> context = xmlconfig.string(\"\"\"\n  ... <configure\n  ...     xmlns:p01=\"http://namespaces.zope.org/p01\">\n  ...   <p01:jsonrpc\n  ...       for=\"p01.jsonrpc.testing.IA\"\n  ...       class=\"p01.jsonrpc.testing.MethodsA\"\n  ...       permission=\"zope.Public\"\n  ...       methods=\"hello\"\n  ...       />\n  ... </configure>\n  ... \"\"\", context)\n\nLet's check if the view is registered as adapter:\n\n  >>> import zope.component\n  >>> from p01.jsonrpc.testing import A\n  >>> from p01.jsonrpc.testing import TestRequest\n  >>> a = A()\n  >>> request = TestRequest()\n  >>> zope.component.queryMultiAdapter((a, request), name='hello')\n  <p01.jsonrpc.zcml.MethodsA object at ...>\n\nWe can also use a layer interface wich will restrict our view registration to\na specific request type. Provide such a request type layer:\n\n  >>> from p01.jsonrpc.testing import IJSONRPCTestLayer\n  >>> demoRequest = TestRequest()\n  >>> zope.interface.directlyProvides(demoRequest, IJSONRPCTestLayer)\n\nAnd register a new JSON-RPC view:\n\n  >>> context = xmlconfig.string(\"\"\"\n  ... <configure\n  ...     xmlns:p01=\"http://namespaces.zope.org/p01\">\n  ...   <p01:jsonrpc\n  ...       for=\"p01.jsonrpc.testing.IB\"\n  ...       class=\"p01.jsonrpc.testing.MethodsB\"\n  ...       permission=\"zope.Public\"\n  ...       methods=\"hello\"\n  ...       layer=\"p01.jsonrpc.testing.IJSONRPCTestLayer\"\n  ...       />\n  ... </configure>\n  ... \"\"\", context)\n\nSetup a new content stub:\n\n  >>> from p01.jsonrpc.testing import B\n  >>> b = B()\n\nAnd test the view within our new layer:\n\n  >>> zope.component.queryMultiAdapter((b, demoRequest), name='hello')\n  <p01.jsonrpc.zcml.MethodsB object at ...>\n\nNote the object b does not know the view within the default request layer:\n\n  >>> zope.component.queryMultiAdapter((b, request), name='hello') is None\n  True\n\n\nsetDefaultJSONRPCSkin\n---------------------\n\n  >>> from p01.jsonrpc import interfaces\n  >>> import p01.jsonrpc.zcml\n\n  >>> class IMySkin(zope.interface.Interface):\n  ...     __module__ = 'p01.jsonrpc.tests'\n  >>> zope.interface.directlyProvides(IMySkin, interfaces.IJSONRPCSkinType)\n\nBefore we setup a default request, we try to set a default request for our\nrequest:\n\n  >>> from zope.publisher.skinnable import setDefaultSkin\n  >>> setDefaultSkin(request)\n\nOur request should not provide any default kins since we didn't register any:\n\n  >>> IMySkin.providedBy(request)\n  False\n\nNow let's register a default skin:\n\n  >>> zope.component.provideUtility(IMySkin, interfaces.IJSONRPCSkinType,\n  ...     name='JSONRPC')\n  >>> p01.jsonrpc.zcml.setDefaultJSONRPCSkin('JSONRPC')\n\nWe can lookup a default skin from the adapter registry: \n\n  >>> from zope.publisher.interfaces import IDefaultSkin\n  >>> adapters = zope.component.getSiteManager().adapters\n  >>> default = adapters.lookup((interfaces.IJSONRPCRequest,), IDefaultSkin, '')\n  >>> default is IMySkin\n  True\n\nSince we have a default skin utility registered as a skin type for our \nrequest, a new request instance should provide the default skin:\n\n  >>> request = TestRequest()\n  >>> setDefaultSkin(request)\n  >>> IMySkin.providedBy(request)\n  True\n\nWe can get the applied default skin by look for our skin type:\n\n  >>> for iface in zope.interface.providedBy(request):\n  ...     if interfaces.IJSONRPCSkinType.providedBy(iface):\n  ...         print(\"<InterfaceClass %s>\" % iface)\n  <InterfaceClass p01.jsonrpc.tests.IMySkin>\n\n\n=======\nCHANGES\n=======\n\n0.8.1 (2025-08-19)\n------------------\n\n- python 3 migration\n\n\n0.8.0 (2025-06-04)\n------------------\n\n- migrated from z3c.jsonrpc to p01.jsonrpc\n\n- fix tests, use p01.testbrowser and p01.publisher as dependency, remove\n  zope.app.testing dependency\n\n\n0.7.2 (2013-10-11)\n------------------\n\n- ``handleException``: provide human readable traceback\n\n\n0.7.1 (2012-11-27)\n------------------\n\n- Fix ``JSONRPCTestTransport`` to include the request full host.\n  Until now it ate the port.\n\n\n0.7.0 (2012-03-25)\n------------------\n\n- Fix: added missing exception import for ParseError in publisher.processInputs\n\n- import doctest from python\n\n\n0.6.0 (2010-01-27)\n------------------\n\n- cleanup setup dependencies, adjust ftesting.zcml\n\n- adjust coverage report setup\n\n- implemented error view concept which will work with ZopePublication\n\n- implemented default error view for known zope and JSON-RPC errors\n\n- use DirectResult in response\n\n- removed unauthenticated error view. This was not working and requires a\n  custom concept supported by the used java script library used at client\n  side\n\n\nVersion 0.5.4 (2009-04-07)\n--------------------------\n\n- handle empty and none-existing params in jsonrpc requests\n\n\nVersion 0.5.3 (2009-03-10)\n--------------------------\n\n- Fix: reflect skin lookup changes in zope.publisher. Use the new skinnable\n  concept.\n\n- Fix: The default skin didn't get applied based on the inherited concept give\n  from the zope.publisher.browser implementation because our JSON-RPC request\n  doesn't provide IBrowserRequest. Added a workaround which will apply a given\n  IDefaultSkin during request instance creation.\n\n\nVersion 0.5.2 (2009-02-24)\n--------------------------\n\n- added tests for all JSON-RPC versions\n\n- Feature: implemented defaultJSONRPCSkin directive\n\n- Feature: support non positional arguments for all jsonrpc versions. There is\n  now no distinction in handling method parameters for all supported versions.\n\n- Fix: for jsonrpc version 1.1 :\n   - must not provide \"error\" property in case of success\n   - must not provide \"result\" property in case of error\n\n- Fix: removed develop path for z3c.json from buildout.cfg\n\n- Fix: publisher checks for version id as a string not a float\n\n- Feature: Implemented JSON-RPC 2.0 specification. Use JSON-RPC 2.0 version as\n  default. Optional the version 1.0 and 1.1 can be set. See JSON-RPC 2.0\n  specification for more information.\n\n- Feature: Added initial version of JSON-RPC exceptions.\n\n- Added explicit test cleanup since some zope testing change left over a\n  global adapter registry from old connections\n\n- Removed unused dependency to z3c.layer in test setup\n\n- Removed unused dependency on z3c.i18n.\n\n\nVersion 0.5.1 (2008-01-24)\n--------------------------\n\n- Improve meta-data.\n\n- Bug: The skin code relied on un-released API that was actually later\n  reverted.\n\n\nVersion 0.5.0 (2008-01-21)\n--------------------------\n\n- Initial Release",
    "bugtrack_url": null,
    "license": "ZPL 2.1",
    "summary": "JSON RPC server and client implementation for Zope3",
    "version": "0.8.1",
    "project_urls": {
        "Homepage": "http://pypi.python.org/pypi/p01.jsonrpc"
    },
    "split_keywords": [
        "zope3",
        "z3c",
        "p01",
        "json",
        "rpc",
        "json-rpc",
        "server",
        "client"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "937f526295944580cfdbe3e5bc43d1719aafcfe2417549ef6446a59c3ee13067",
                "md5": "62ee2f4fa0cd560e1d05d74bbe9c5308",
                "sha256": "1af028475d60cb15f49008ffc1a0d2d597b902ab656527724ad015cccceaae69"
            },
            "downloads": -1,
            "filename": "p01.jsonrpc-0.8.1.tar.gz",
            "has_sig": false,
            "md5_digest": "62ee2f4fa0cd560e1d05d74bbe9c5308",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 42117,
            "upload_time": "2025-08-22T13:54:48",
            "upload_time_iso_8601": "2025-08-22T13:54:48.020508Z",
            "url": "https://files.pythonhosted.org/packages/93/7f/526295944580cfdbe3e5bc43d1719aafcfe2417549ef6446a59c3ee13067/p01.jsonrpc-0.8.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-08-22 13:54:48",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "p01.jsonrpc"
}
        
Elapsed time: 3.76089s