Skip to content

Commit 59dbc9c

Browse files
author
Kyle Laplante
committed
first commit
0 parents  commit 59dbc9c

File tree

7 files changed

+400
-0
lines changed

7 files changed

+400
-0
lines changed

README.md

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
A tornado RPC library.
2+
3+
This RPC framework uses tornado
4+
so its very quick and asynchronous.
5+
6+
This framework supports regular and
7+
asynchronous methods to be registered
8+
with the TornRPCServer. The below example
9+
shows both. The only difference in the
10+
framework is how you register it.
11+
Normal functions are registered using
12+
"server.register()". Async functions
13+
are registered using "server.register_async()".
14+
15+
For more detailed info see the docstring
16+
for TornRPCClient and TornRPCServer.
17+
18+
Example:
19+
### example server code ###
20+
21+
from tornado import gen
22+
from tornrpc.server import TornRPCServer
23+
24+
def test(arg):
25+
return "You said %s" % arg
26+
27+
@gen.coroutine
28+
def testasync(arg):
29+
raise gen.Return("You said async %s" % arg)
30+
31+
server = TornRPCServer()
32+
server.register(test)
33+
server.register_async(testasync)
34+
server.start(8080)
35+
36+
### example client code ###
37+
38+
from tornrpc.client import TornRPCClient
39+
40+
client = TornRPCClient('localhost:8080')
41+
client.test('hi')
42+
client.testasync('hi')

setup.py

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from setuptools import setup
2+
3+
description = """
4+
A tornado RPC library.
5+
6+
This RPC framework uses tornado
7+
so its very quick and asynchronous.
8+
9+
This framework supports regular and
10+
asynchronous methods to be registered
11+
with the TornRPCServer. The below example
12+
shows both. The only difference in the
13+
framework is how you register it.
14+
Normal functions are registered using
15+
"server.register()". Async functions
16+
are registered using "server.register_async()".
17+
18+
For more detailed info see the docstring
19+
for TornRPCClient and TornRPCServer.
20+
21+
Example:
22+
### example server code ###
23+
24+
from tornado import gen
25+
from tornrpc.server import TornRPCServer
26+
27+
def test(arg):
28+
return "You said %s" % arg
29+
30+
@gen.coroutine
31+
def testasync(arg):
32+
raise gen.Return("You said async %s" % arg)
33+
34+
server = TornRPCServer()
35+
server.register(test)
36+
server.register_async(testasync)
37+
server.start(8080)
38+
39+
### example client code ###
40+
41+
from tornrpc.client import TornRPCClient
42+
43+
client = TornRPCClient('localhost:8080')
44+
client.test('hi')
45+
client.testasync('hi')
46+
"""
47+
48+
setup(
49+
name='TornRPC',
50+
version='1.0.4',
51+
description='A tornado RPC framework',
52+
long_description=description,
53+
author='Kyle Laplante',
54+
author_email='[email protected]',
55+
keywords='rpc tornado asynchronous web',
56+
packages=['tornrpc', 'tornrpc/client', 'tornrpc/server'],
57+
install_requires=['tornado'],
58+
)

tornrpc/README

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
A tornado RPC library.
2+
3+
This RPC framework uses tornado
4+
so its very quick and asynchronous.
5+
6+
This framework supports regular and
7+
asynchronous methods to be registered
8+
with the TornRPCServer. The below example
9+
shows both. The only difference in the
10+
framework is how you register it.
11+
Normal functions are registered using
12+
"server.register()". Async functions
13+
are registered using "server.register_async()".
14+
15+
For more detailed info see the docstring
16+
for TornRPCClient and TornRPCServer.
17+
18+
Example:
19+
### example server code ###
20+
21+
from tornado import gen
22+
from tornrpc.server import TornRPCServer
23+
24+
def test(arg):
25+
return "You said %s" % arg
26+
27+
@gen.coroutine
28+
def testasync(arg):
29+
raise gen.Return("You said async %s" % arg)
30+
31+
server = TornRPCServer()
32+
server.register(test)
33+
server.register_async(testasync)
34+
server.start(8080)
35+
36+
### example client code ###
37+
38+
from tornrpc.client import TornRPCClient
39+
40+
client = TornRPCClient('localhost:8080')
41+
client.test('hi')
42+
client.testasync('hi')

tornrpc/__init__.py

Whitespace-only changes.

tornrpc/client/__init__.py

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import json
2+
import requests
3+
from urlparse import urljoin
4+
5+
class _RPC(object):
6+
__HEADERS__ = {'User-Agent': 'TornRPCClient'}
7+
8+
def __init__(self, server, name):
9+
self._name = name
10+
self._url = urljoin(server, name)
11+
12+
def __call__(self, *args, **kwargs):
13+
if args:
14+
# the server will turn this list into
15+
# positional args
16+
kwargs['__args'] = args
17+
18+
try:
19+
resp = requests.get(self._url, data=kwargs, headers=self.__HEADERS__)
20+
except Exception as e:
21+
raise TornRPCClient.FailedCall(e)
22+
23+
if resp.status_code == 404:
24+
raise TornRPCClient.MissingMethod(
25+
'No remote method found for {0}'.format(self._name))
26+
27+
try:
28+
ret = json.loads(resp.content)
29+
except Exception as e:
30+
raise TornRPCClient.InvalidSerializationError(e)
31+
32+
if 'error' in ret:
33+
raise TornRPCClient.FailedCall(ret['error'])
34+
35+
return ret['response']
36+
37+
class TornRPCClient(object):
38+
"""
39+
Client for talking to TornRPCServer servers.
40+
41+
Example:
42+
from tornrpc.client import TornRPCClient
43+
client = TornRPCClient('http://myserver.com:9091')
44+
45+
# If you want to include some methods that are not allowed
46+
# to be called include them in the unallowed_methods argument
47+
# to the constructor. If you do not want auto-completion or dir
48+
# fulfillment use load_remotes=False in the constructor.
49+
50+
# then just call any remote method on your client
51+
client.remote_method_name(args)
52+
"""
53+
54+
class FailedCall(Exception): pass
55+
class InvalidSerializationError(Exception): pass
56+
class MissingMethod(Exception): pass
57+
58+
__UNALLOWED__ = [
59+
# iPython calls these when using tab-complete.
60+
# dont allow these methods to be sent to the
61+
# server since _loadremotemethods() takes care
62+
# of tab-completion.
63+
'trait_names',
64+
'_getAttributeNames',
65+
]
66+
67+
def __init__(self, server, unallowed_calls=[], load_remotes=True):
68+
if server.startswith('http'):
69+
self._server = server
70+
else:
71+
self._server = 'http://{0}'.format(server)
72+
self._unallowed = unallowed_calls + self.__UNALLOWED__
73+
if load_remotes:
74+
self.__loadremoteroutes()
75+
76+
def __send(self, name):
77+
return _RPC(self._server, name)
78+
79+
def __remoteroutes(self):
80+
return self._getroutes()
81+
82+
def __loadremoteroutes(self):
83+
for route in self.__remoteroutes():
84+
setattr(self, route, self.__send(route))
85+
86+
def __getattr__(self, name):
87+
return None if name in self._unallowed else self.__send(name)
88+
89+
90+
__all__ = ('TornRPCClient')

tornrpc/server/__init__.py

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
from tornado import (
2+
gen,
3+
ioloop,
4+
log,
5+
web
6+
)
7+
8+
from .handlers import _AsyncBase, _Base
9+
10+
class TornRPCServer(object):
11+
"""
12+
Server for TornRPC.
13+
14+
Example:
15+
from tornrpc.server import TornRPCServer
16+
server = TornRPCServer()
17+
18+
# create your functions and register them
19+
# to the server.
20+
# you dont need to use async funcs
21+
# but it is recommended.
22+
# Here is an example that uses both asycn
23+
# and non-async
24+
25+
def not_async(arg=None):
26+
return arg
27+
28+
@gen.coroutine
29+
def is_async(arg=None):
30+
raise gen.Return(arg)
31+
32+
server.register(not_async)
33+
server.register_async(is_async)
34+
35+
# start the server on the desired port
36+
port = 8080
37+
server.start(port)
38+
39+
Use the TornRPCClient to talk to this server
40+
"""
41+
42+
def __init__(self):
43+
self._routes = []
44+
self.log = log.logging.getLogger()
45+
self.log.setLevel(log.logging.INFO)
46+
log.enable_pretty_logging(logger=self.log)
47+
self.register_async(self._getroutes)
48+
49+
@gen.coroutine
50+
def _getroutes(self):
51+
# this is support for the client to have a list
52+
# of all the remote methods
53+
raise gen.Return([v.__name__ for _, v in self._routes])
54+
55+
def _make(self, func, base):
56+
name = func.__name__
57+
# put func into a list as the only item because if
58+
# we assign it as a method it will be required to have
59+
# self as the first arg and we dont want that
60+
handler = type(name, (base,), {'func': [func]})
61+
self._routes.append((r'/{0}'.format(name), handler))
62+
self.log.info('Registered {0} command {1}'.format(base.TYPE, name))
63+
64+
def register(self, func):
65+
"""
66+
Register normal synchronous functions.
67+
Use this to register functions
68+
that do NOT return Futures.
69+
70+
Just pass the function name:
71+
def test():
72+
return True
73+
register(test)
74+
"""
75+
76+
self._make(func, _Base)
77+
78+
def register_async(self, func):
79+
"""
80+
Register an asynchronous function.
81+
Use this to register functions that
82+
DO return Futures. Generally these
83+
functions will be wrapped in
84+
@gen.coroutine.
85+
86+
Just pass the function name:
87+
@gen.coroutine
88+
def test():
89+
raise gen.Return(True)
90+
"""
91+
92+
self._make(func, _AsyncBase)
93+
94+
def start(self, port):
95+
"""
96+
Start a TornRPCServer on the defined port
97+
"""
98+
99+
self.log.info('Starting server on port {0}'.format(port))
100+
app = web.Application(self._routes, debug=True)
101+
app.listen(int(port))
102+
ioloop.IOLoop.current().start()
103+
104+
105+
__all__ = ('TornRPCServer')

0 commit comments

Comments
 (0)