forked from mbr/tinyrpc
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial import containing partial README.rst, somewhat limited setup.…
…py and a lot of shabby WIP code.
- Loading branch information
0 parents
commit 5b7ff2e
Showing
3 changed files
with
197 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |