Skip to content

Commit

Permalink
Initial import containing partial README.rst, somewhat limited setup.…
Browse files Browse the repository at this point in the history
…py and a lot of shabby WIP code.
  • Loading branch information
mbr committed Jan 23, 2013
0 parents commit 5b7ff2e
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 0 deletions.
56 changes: 56 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
tinyrpc: A small and modular way of handling web-related RPC
============================================================

Motivation
----------

As of this writing (in Jan 2013) there are a few jsonrpc_ libraries already out
there on PyPI_, most of them handling one specific use case (i.e. json via
WSGI, using Twisted, or TCP-sockets).

None of the libraries, however, made it easy to reuse the jsonrpc_-parsing bits
and substitute a different transport (i.e. going from json_ via TCP_ to an
implementation using WebSockets_ or 0mq_).

In the end, all these libraries have their own dispatching interfaces and a
custom implementation of handling jsonrpc_.

``tinyrpc`` aims to do better by dividing the problem into cleanly
interchangeable parts that allow easy addition of new transport methods, RPC
protocols or dispatchers.

Structure of tinyrpc
--------------------

``tinyrpc`` architectually considers three layers: Transport, Protocol and
Dispatch.

The Transport-layer is responsible for receiving and sending messages. No
assumptions are made about messages, except that they are of a fixed size.
Messages are received and possibly passed on a Python strings.

On the Protocol-layer messages are decoded into a format that is protocol
independent, these can be passed on to a dispatcher.

The Dispatch-layer performs the actual method calling and serializes the return
value. These can be routed back through the Protocol- and Transport-layer to
return the answer to the calling client.

Each layer is useful "on its own" and can be used seperately. If you simply
need to decode a jsonrpc_ message, without passing it on or sending it through
a transport, the ``JSONRPCProtocol``-class is completely usable on its own.

Protocols
---------

Currently, only jsonrpc_ is supported[#]_.

.. [#]: tinyrpc started out as a jsonrpc_ library because that was the
immediate need when it was written. Its structure should make it very
straight-forward to implement other RPC schemes though.
.. _jsonrpc: http://www.jsonrpc.org/
.. _PyPI: http://pypi.python.org
.. _json: http://www.json.org/
.. _TCP: http://en.wikipedia.org/wiki/Transmission_Control_Protocol
.. _WebSockets: http://en.wikipedia.org/wiki/WebSocket
.. _0mq: http://www.zeromq.org/
19 changes: 19 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from setuptools import setup, find_packages


def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()


setup(
name='tinyrpc',
version='0.1dev',
description='A small, modular, transport and protocol neutral RPC '\
'library that, among other things, supports JSON-RPC and zmq.',
long_description=read('README.rst'),
keywords='json rpc json-rpc jsonrpc 0mq zmq zeromq',
author='Marc Brinkmann',
author_email='[email protected]',
url='http://github.com/mbr/tinyrpc',
license='MIT',
)
122 changes: 122 additions & 0 deletions tinyrpc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

class RPCProtocol(object):
def handle_request(request):
# do decoding
pass

class RPCDispatcher(object):
def get_method(method_name, args=None, kwargs=None):
pass


from collections import namedtuple


def run_server(transport, protocol, dispatcher):
data_in, transport_ctx = transport.get_request()
function_call, protocol_ctx = protocol.parse_request(data_in)
return_value = dispatcher.call(function_call)
data_out = protocol.serialize_response(return_value, protocol_ctx)
transport.send_reply(data_out, transport_ctx)


class RPCError(Exception):
pass


class InvalidRequestError(RPCError):
pass


class MethodNotFoundError(RPCError):
pass


# IDEA: nest dispatchers, allowing trees - decouples classes from
# instance dispatchers?
class Dispatcher(object):
def __init__(self, methods=None):
self.methods = methods or {}

def call(spec):
if not spec.method in self.methods:
return ReturnValue(None, MethodNotFoundError(spec.method))

try:
val = self.methods[spec.method](*spec.args, **spec.kwargs)
return ReturnValue(val, None)
except Exception as e:
return ReturnValue(None, e)

def expose(f):
self.methods[f.__name__] = f
return f

def register_method(self, name, method):
self.methods[name] = method


CallSpec = namedtuple('CallSpec', ['method', 'args', 'kwargs'])
ReturnValue = namedtuple('ReturnValue', ['value', 'error'])


import json


class JSONRPCProtocol(RPCProtocol):
JSON_RPC_VERSION = "2.0"
_ALLOWED_REQUEST_KEYS = sorted(['id', 'jsonrpc', 'method', 'params'])

def parse_request(data):
req = json.loads(data)

for k in req.iterkeys():
if not k in _ALLOWED_REQUEST_KEYS:
raise InvalidRequestError(
'%s not a valid request key.' % k
)

if req['jsonrpc'] != JSON_RPC_VERSION:
raise InvalidRequestError(
'Only supports jsonrpc %s' % JSON_RPC_VERSION
)

if not isinstance(req['method'], basestring):
raise InvalidRequestError(
'method name must be a string: %s' % req['method']
)

if isinstance(req['params'], list):
return CallSpec(
str(req['method']),
req['params'],
None,
), req['id']

if isinstance(req['params'], dict):
return CallSpec(
str(req['method']),
None,
req['params'],
), req['id']

raise InvalidRequestError(
'params must be either array or object.'
)

def serialize_response(return_value):
resp = {
'jsonrpc': JSON_RPC_VERSION,
'id': return_value.context
}

if return_value.status == 'ok':
resp['result'] = return_value.value
elif return_value.status == 'error':
resp['error'] = return_value.value
else:
resp['error'] = str(return_value.value)

return json.dumps(resp)

0 comments on commit 5b7ff2e

Please sign in to comment.