Skip to content

Commit

Permalink
Merge branch 'spanezz-master'
Browse files Browse the repository at this point in the history
  • Loading branch information
joshmarshall committed Jan 25, 2014
2 parents da4a293 + 79918da commit 46eaec6
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 18 deletions.
25 changes: 16 additions & 9 deletions tests/helpers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import threading
import time
from tornado.httpclient import AsyncHTTPClient
from tornadorpc import start_server, private, async
from tornado.httpclient import AsyncHTTPClient


class Tree(object):
Expand All @@ -22,14 +22,6 @@ class TestHandler(object):
def add(self, x, y):
return x+y

@async
def async(self, url):
async_client = AsyncHTTPClient()
async_client.fetch(url, self._handle_response)

def _handle_response(self, response):
self.result(response.code)

@private
def private(self):
# Should not be callable
Expand All @@ -42,13 +34,25 @@ def _private(self):
def internal_error(self):
raise Exception("Yar matey!")

@async
def async(self, url):
async_client = AsyncHTTPClient()
async_client.fetch(url, self._handle_response)

def _handle_response(self, response):
self.result(response.code)


class TestServer(object):

threads = {}

@classmethod
def start(cls, handler, port):
# threading, while functional for testing the built-in python
# clients, is an overly complicated solution for IOLoop based
# servers. After implementing a tornado-based JSON-RPC client
# and XML-RPC client, move this to an IOLoop based test case.
if not cls.threads.get(port):
cls.threads[port] = threading.Thread(
target=start_server,
Expand All @@ -65,9 +69,11 @@ class RPCTests(object):

server = None
handler = None
io_loop = None
port = 8002

def setUp(self):
super(RPCTests, self).setUp()
self.server = TestServer.start(self.handler, self.port)

def get_url(self):
Expand All @@ -87,6 +93,7 @@ def test_add(self):
self.assertEqual(result, 11)

def test_async(self):
# this should be refactored to use Async RPC clients...
url = 'http://www.google.com'
client = self.get_client()
result = client.async(url)
Expand Down
94 changes: 94 additions & 0 deletions tests/test_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import json
from tests.helpers import TestHandler
from tornado.httpclient import AsyncHTTPClient
from tornado.testing import AsyncHTTPTestCase
import tornado.web
from tornadorpc import async
from tornadorpc.xml import XMLRPCHandler
import xmlrpclib


class AsyncHandler(XMLRPCHandler, TestHandler):

@async
def async_method(self, url):
async_client = AsyncHTTPClient()
async_client.fetch(url, self._handle_response)

@async
def bad_async_method(self, url):
async_client = AsyncHTTPClient()
async_client.fetch(url, self._handle_response)
return 5

def _handle_response(self, response):
self.result(json.loads(response.body))


class AsyncXMLRPCClient(object):

def __init__(self, url, ioloop, fetcher):
self._url = url
self._ioloop = ioloop
self._fetcher = fetcher

def __getattr__(self, attribute):
return Caller(attribute, self)

def execute(self, method, params, keyword_params):
if params and keyword_params:
raise Exception(
"Can't have both keyword and positional arguments.")
arguments = params or keyword_params
body = xmlrpclib.dumps(arguments, methodname=method)
response = self._fetcher(self._url, method="POST", body=body)
result, _ = xmlrpclib.loads(response.body)
return result[0]


class Caller(object):

def __init__(self, namespace, client):
self._namespace = namespace
self._client = client

def __getattr__(self, namespace):
self._namespace += "." + namespace
return self

def __call__(self, *args, **kwargs):
return self._client.execute(self._namespace, args, kwargs)


class AsyncTests(AsyncHTTPTestCase):

def get_app(self):

class IndexHandler(tornado.web.RequestHandler):

def get(self):
self.finish({"foo": "bar"})

return tornado.web.Application([
("/", IndexHandler),
("/RPC2", AsyncHandler)
])

def get_client(self):
return AsyncXMLRPCClient(
url="/RPC2", ioloop=self.io_loop, fetcher=self.fetch)

def test_async_method(self):
client = self.get_client()
result = client.async_method(
"http://localhost:%d/" % (self.get_http_port()))
self.assertEqual({"foo": "bar"}, result)

def test_async_returns_non_none_raises_internal_error(self):
client = self.get_client()
try:
client.bad_async_method(
"http://localhost:%d/" % (self.get_http_port()))
self.fail("xmlrpclib.Fault should have been raised.")
except xmlrpclib.Fault, fault:
self.assertEqual(-32603, fault.faultCode)
1 change: 0 additions & 1 deletion tests/test_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ def test_internal_error(self):

def test_parse_error(self):
try:
print self.get_url()
urllib2.urlopen(self.get_url(), '<garbage/>')
except xmlrpclib.Fault, f:
self.assertEqual(-32700, f.faultCode)
Expand Down
13 changes: 5 additions & 8 deletions tornadorpc/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
You can use the utility functions like 'private' and 'start_server'.
"""

import logging
from tornado.web import RequestHandler
import tornado.web
import tornado.ioloop
Expand Down Expand Up @@ -100,7 +99,7 @@ def dispatch(self, method_name, params):
Handler class. Currently supports only positional
or keyword arguments, not mixed.
"""
if method_name in dir(RequestHandler):
if hasattr(RequestHandler, method_name):
# Pre-existing, not an implemented attribute
return self.handler.result(self.faults.method_not_found())
method = self.handler
Expand All @@ -116,7 +115,7 @@ def dispatch(self, method_name, params):
# Not callable, so not a method
return self.handler.result(self.faults.method_not_found())
if method_name.startswith('_') or \
('private' in dir(method) and method.private is True):
getattr(method, 'private', False) is True:
# No, no. That's private.
return self.handler.result(self.faults.method_not_found())
args = []
Expand All @@ -141,14 +140,12 @@ def dispatch(self, method_name, params):
self.traceback(method_name, params)
return self.handler.result(self.faults.internal_error())

if 'async' in dir(method) and method.async:
if getattr(method, 'async', False):
# Asynchronous response -- the method should have called
# self.result(RESULT_VALUE)
if response is not None:
# This should be deprecated to use self.result
message = "Async results should use 'self.result()'"
message += " Return result will be ignored."
logging.warning(message)
return self.handler.result(self.faults.internal_error())
else:
# Synchronous result -- we call result manually.
return self.handler.result(response)
Expand Down Expand Up @@ -227,7 +224,7 @@ def check_method(self, attr_name, obj):
raise AttributeError('Private object or method.')
attr = getattr(obj, attr_name)

if 'private' in dir(attr) and attr.private:
if getattr(attr, 'private', False):
raise AttributeError('Private object or method.')
return attr

Expand Down

0 comments on commit 46eaec6

Please sign in to comment.