diff --git a/mopidy/utils/jsonrpc.py b/mopidy/utils/jsonrpc.py index d8000332..46d5f58e 100644 --- a/mopidy/utils/jsonrpc.py +++ b/mopidy/utils/jsonrpc.py @@ -8,18 +8,23 @@ import pykka class JsonRpcWrapper(object): """ - Wraps objects and make them accessible through JSON-RPC 2.0 messaging. + Wraps an object and makes it accessible through JSON-RPC 2.0 messaging. - This class takes responsibility of communicating with the objects and + This class takes responsibility of communicating with the object and processing of JSON-RPC 2.0 messages. The transport of the messages over HTTP, WebSocket, TCP, or whatever is of no concern to this class. - The objects can either be Pykka actors or plain objects. Only their public - methods will be exposed, not any attributes. + Only the object's public methods will be exposed. Attributes are not + exposed by themself, but public methods on public attributes are exposed, + using dotted paths from the exposed object to the method at the end of the + path. - If a method returns an object with a ``get()`` method, it is assumed to be - a future object. Any futures is completed and their value unwrapped before - the JSON RPC wrapper returns the response. + To expose multiple objects, simply create a new "parent" object and assign + the other objects you want to expose to attributes on the "parent" object. + You then wrap the "parent" object. + + If a method returns a :class:`pykka.Future`, the future will be completed + and its value unwrapped before the JSON-RPC wrapper returns the response. For further details on the JSON-RPC 2.0 spec, see http://www.jsonrpc.org/specification @@ -27,8 +32,8 @@ class JsonRpcWrapper(object): :param objects: dict of names mapped to objects to be exposed """ - def __init__(self, objects, decoders=None, encoders=None): - self.objects = objects + def __init__(self, obj, decoders=None, encoders=None): + self.obj = obj self.decoder = get_combined_json_decoder(decoders or []) self.encoder = get_combined_json_encoder(encoders or []) @@ -155,8 +160,7 @@ class JsonRpcWrapper(object): def _get_method(self, name): try: path = name.split('.') - root = path.pop(0) - this = self.objects[root] + this = self.obj for part in path: if part.startswith('_'): raise AttributeError diff --git a/tests/utils/jsonrpc_test.py b/tests/utils/jsonrpc_test.py index c9972d96..022525f4 100644 --- a/tests/utils/jsonrpc_test.py +++ b/tests/utils/jsonrpc_test.py @@ -12,6 +12,10 @@ from mopidy.utils import jsonrpc from tests import unittest +class ExportedObject(object): + pass + + class Calculator(object): def model(self): return 'TI83' @@ -36,11 +40,14 @@ class JsonRpcTestBase(unittest.TestCase): def setUp(self): self.backend = dummy.DummyBackend.start(audio=None).proxy() self.core = core.Core.start(backends=[self.backend]).proxy() + + exported = ExportedObject() + exported.hello = lambda: 'Hello, world!' + exported.core = self.core + exported.calculator = Calculator() + self.jrw = jsonrpc.JsonRpcWrapper( - objects={ - 'core': self.core, - 'calculator': Calculator(), - }, + obj=exported, encoders=[models.ModelJSONEncoder], decoders=[models.model_json_decoder]) @@ -111,6 +118,19 @@ class JsonRpcSerializationTest(JsonRpcTestBase): class JsonRpcSingleCommandTest(JsonRpcTestBase): + def test_call_method_on_root(self): + request = { + 'jsonrpc': '2.0', + 'method': 'hello', + 'id': 1, + } + response = self.jrw.handle_data(request) + + self.assertEqual(response['jsonrpc'], '2.0') + self.assertEqual(response['id'], 1) + self.assertNotIn('error', response) + self.assertEqual(response['result'], 'Hello, world!') + def test_call_method_on_plain_object(self): request = { 'jsonrpc': '2.0',