From 2e4efd040bbb5b6d2169a418f3625f3fac484405 Mon Sep 17 00:00:00 2001 From: Jason Paryani Date: Thu, 14 Nov 2013 20:59:21 -0800 Subject: [PATCH] Allow client requests to use positional args --- capnp/__init__.py | 6 +- capnp/capnp.pyx | 115 +++++++++++++++++++-------------- docs/capnp.rst | 92 ++++++++++++++++++++++++-- examples/example_capability.py | 7 +- test/test_capability.py | 25 ++++--- 5 files changed, 178 insertions(+), 67 deletions(-) diff --git a/capnp/__init__.py b/capnp/__init__.py index 5f03ea0..06e15fe 100644 --- a/capnp/__init__.py +++ b/capnp/__init__.py @@ -18,13 +18,13 @@ Example Usage:: alicePhone.type = 'mobile' f = open('example.bin', 'w') - addresses.writeTo(f) + addresses.write(f) f.close() # Reading f = open('example.bin') - addresses = addressbook.AddressBook.readFrom(f) + addresses = addressbook.AddressBook.read(f) for person in addresses.people: print(person.name, ':', person.email) @@ -33,7 +33,7 @@ Example Usage:: """ from .version import version as __version__ from .capnp import * -from .capnp import _DynamicStructReader, _DynamicStructBuilder, _DynamicResizableListBuilder, _DynamicListReader, _DynamicListBuilder, _DynamicOrphan, _DynamicResizableListBuilder, _MallocMessageBuilder, _PackedFdMessageReader, _StreamFdMessageReader, _write_message_to_fd, _write_packed_message_to_fd +from .capnp import _DynamicStructReader, _DynamicStructBuilder, _DynamicResizableListBuilder, _DynamicListReader, _DynamicListBuilder, _DynamicOrphan, _DynamicResizableListBuilder, _MallocMessageBuilder, _PackedFdMessageReader, _StreamFdMessageReader, _write_message_to_fd, _write_packed_message_to_fd, _StructModule, _InterfaceModule, _DynamicCapabilityClient, _CapabilityClient del capnp add_import_hook() # enable import hook by default diff --git a/capnp/capnp.pyx b/capnp/capnp.pyx index 96755e9..556a8ee 100644 --- a/capnp/capnp.pyx +++ b/capnp/capnp.pyx @@ -98,13 +98,13 @@ def _make_enum(enum_name, *sequential, **named): enums['reverse_mapping'] = reverse return type(enum_name, (), enums) -_Nature = _make_enum('Nature', +_Nature = _make_enum('_Nature', PRECONDITION = 0, LOCAL_BUG = 1, OS_ERROR = 2, NETWORK_FAILURE = 3, OTHER = 4) -_Durability = _make_enum('Durability', +_Durability = _make_enum('_Durability', PERMANENT = 0, TEMPORARY = 1, OVERLOADED = 2) @@ -142,8 +142,8 @@ cdef class _KjExceptionWrapper: # Extension classes can't inherit from Exception, so we're going to proxy wrap kj::Exception, and forward all calls to it from this Python class class KjException(Exception): - Nature = _Nature - Durability = _Durability + Nature = _make_enum('Nature', **{x : x for x in _Nature.reverse_mapping.values()}) + Durability = _make_enum('Durability', **{x : x for x in _Durability.reverse_mapping.values()}) def __init__(self, message=None, nature=None, durability=None, wrapper=None): if wrapper is not None: @@ -224,7 +224,7 @@ ctypedef fused _DynamicSetterClasses: Request ctypedef fused _PromiseTypes: - Promise + _Promise _RemotePromise _VoidPromise PromiseFulfillerPair @@ -233,21 +233,6 @@ cdef extern from "Python.h": cdef int PyObject_AsReadBuffer(object, void** b, Py_ssize_t* c) cdef int PyObject_AsWriteBuffer(object, void** b, Py_ssize_t* c) -Type = _make_enum('DynamicValue.Type', - UNKNOWN = capnp.TYPE_UNKNOWN, - VOID = capnp.TYPE_VOID, - BOOL = capnp.TYPE_BOOL, - INT = capnp.TYPE_INT, - UINT = capnp.TYPE_UINT, - FLOAT = capnp.TYPE_FLOAT, - TEXT = capnp.TYPE_TEXT, - DATA = capnp.TYPE_DATA, - LIST = capnp.TYPE_LIST, - ENUM = capnp.TYPE_ENUM, - STRUCT = capnp.TYPE_STRUCT, - CAPABILITY = capnp.TYPE_CAPABILITY, - OBJECT = capnp.TYPE_OBJECT) - # Templated classes are weird in cython. I couldn't put it in a pxd header for some reason cdef extern from "capnp/list.h" namespace " ::capnp": cdef cppclass List[T]: @@ -800,9 +785,9 @@ cdef class _DynamicStructReader: cpdef as_builder(self): """A method for casting this Builder to a Reader - Don't use this method unless you know what you're doing. + This is a copying operation with respect to the message's buffer. Changes in the new builder will not reflect in the original reader. - :rtype: :class:`_DynamicStructReader` + :rtype: :class:`_DynamicStructBuilder` """ builder = _MallocMessageBuilder() return builder.set_root(self) @@ -998,7 +983,7 @@ cdef class _DynamicStructBuilder: cpdef as_reader(self): """A method for casting this Builder to a Reader - Don't use this method unless you know what you're doing. + This is a non-copying operation with respect to the message's buffer. This means changes to the fields in the original struct will carry over to the new reader. :rtype: :class:`_DynamicStructReader` """ @@ -1008,6 +993,16 @@ cdef class _DynamicStructBuilder: reader._obj_to_pin = self return reader + cpdef copy(self): + """A method for copying this Builder + + This is a copying operation with respect to the message's buffer. Changes in the new builder will not reflect in the original reader. + + :rtype: :class:`_DynamicStructBuilder` + """ + builder = _MallocMessageBuilder() + return builder.set_root(self) + property schema: """A property that returns the _StructSchema object matching this writer""" def __get__(self): @@ -1156,7 +1151,7 @@ cdef class _CallContext: def __get__(self): return self._get_results() -cdef class Promise: +cdef class _Promise: cdef PyPromise * thisptr cdef public bint is_consumed @@ -1187,7 +1182,7 @@ cdef class Promise: Py_INCREF(func) Py_INCREF(error_func) - return Promise()._init(capnp.then(deref(self.thisptr), func, error_func)) + return _Promise()._init(capnp.then(deref(self.thisptr), func, error_func)) cdef class _VoidPromise: cdef VoidPromise * thisptr @@ -1248,7 +1243,7 @@ cdef class _RemotePromise: return ret cpdef as_pypromise(self) except +reraise_kj_exception: - Promise()._init(convert_to_pypromise(deref(self.thisptr))) + _Promise()._init(convert_to_pypromise(deref(self.thisptr))) cpdef then(self, func, error_func=None) except +reraise_kj_exception: if self.is_consumed: @@ -1294,7 +1289,7 @@ cdef class EventLoop: cdef UnixEventLoop thisptr cpdef evalLater(self, func): Py_INCREF(func) - return Promise()._init(capnp.evalLater(self.thisptr, func)) + return _Promise()._init(capnp.evalLater(self.thisptr, func)) cpdef wait(self, _PromiseTypes promise) except +reraise_kj_exception: if promise.is_consumed: @@ -1307,7 +1302,7 @@ cdef class EventLoop: self.thisptr.wait_void(moveVoidPromise(deref(promise.thisptr))) elif _PromiseTypes is PromiseFulfillerPair: self.thisptr.wait_void(moveVoidPromise(deref(promise.thisptr).promise)) - elif _PromiseTypes is Promise: + elif _PromiseTypes is _Promise: ret = self.thisptr.wait(movePromise(deref(promise.thisptr))) else: raise ValueError("Not a valid promise type") @@ -1384,9 +1379,26 @@ cdef class _DynamicCapabilityClient: self._server = server return self - cpdef _send_helper(self, name, firstSegmentWordSize, kwargs) except +reraise_kj_exception: + cpdef _find_method_args(self, method_name): + s = self.schema + meth = None + for meth in s.node.interface.methods: + if meth.name == method_name: + break + + params = s.get_dependency(meth.paramStructType).node + if params.scopeId != 0: + raise ValueError("Cannot call method `%s` with positional args, since its param struct is not implicitly defined and thus does not have a set order of arguments") + + return [f.name for f in params.struct.fields] + + cpdef _send_helper(self, name, firstSegmentWordSize, args, kwargs) except +reraise_kj_exception: cdef Request * request = new Request(self.thisptr.newRequest(name, firstSegmentWordSize)) + if args is not None: + for arg_name, arg_val in zip(self._find_method_args(name), args): + _setDynamicFieldPtr(request, arg_name, arg_val, self) + for key, val in kwargs.items(): _setDynamicFieldPtr(request, key, val, self) @@ -1399,7 +1411,7 @@ cdef class _DynamicCapabilityClient: return self._request_helper(name, firstSegmentWordSize) def _send(self, name, *args, firstSegmentWordSize=0, **kwargs): - return self._send_helper(name, firstSegmentWordSize, kwargs) + return self._send_helper(name, firstSegmentWordSize, args, kwargs) def __getattr__(self, name): if name.endswith('_request'): @@ -1552,6 +1564,9 @@ cdef class PromiseFulfillerPair: self.thisptr = copyPromiseFulfillerPair(newPromiseAndFulfiller(loop.thisptr)) self.is_consumed = False + cpdef fulfill(self): + pass #TODO + cdef class _Schema: cdef C_Schema thisptr cdef _init(self, C_Schema other): @@ -1573,6 +1588,11 @@ cdef class _Schema: cpdef get_proto(self): return _NodeReader().init(self.thisptr.getProto()) + property node: + """The raw schema node""" + def __get__(self): + return _DynamicStructReader()._init(self.thisptr.getProto(), self) + cdef class _StructSchema: cdef C_StructSchema thisptr cdef object __fieldnames, __union_fields, __non_union_fields @@ -1619,7 +1639,7 @@ cdef class _StructSchema: property node: """The raw schema node""" def __get__(self): - return _DynamicStructReader()._init(self.thisptr.getProto(), None) + return _DynamicStructReader()._init(self.thisptr.getProto(), self) cpdef get_dependency(self, id): return _Schema()._init(self.thisptr.getDependency(id)) @@ -1657,7 +1677,7 @@ cdef class _InterfaceSchema: property node: """The raw schema node""" def __get__(self): - return _DynamicStructReader()._init(self.thisptr.getProto(), None) + return _DynamicStructReader()._init(self.thisptr.getProto(), self) cpdef get_dependency(self, id): return _Schema()._init(self.thisptr.getDependency(id)) @@ -1680,7 +1700,7 @@ class _StructABCMeta(type): def __instancecheck__(cls, obj): return isinstance(obj, cls.__base__) and obj.schema == cls._schema -class StructModule(object): +class _StructModule(object): def __init__(self, schema): self.schema = schema @@ -1702,7 +1722,7 @@ class StructModule(object): :type buf: buffer :param buf: Any Python object that supports the readable buffer interface. If buf is mutable, then changes to the object will be reflected in the returned Reader, which may be surprising. If buf is an ordinary bytes object, then there should be no concern. :type bool: builder - :param buf: If true, return a builder object""" + :param buf: If true, return a builder object. This will allow you to change the contents of `buf`, so do this with care.""" if builder: message = _FlatMessageBuilder(buf) else: @@ -1719,9 +1739,20 @@ class StructModule(object): _from_dict(msg, d) return msg def from_object(self, obj): + _warnings.warn('This method is deprecated and will be removed in the 0.5 release. Use the `as_builder` or `copy` functions instead', UserWarning) builder = _MallocMessageBuilder() return builder.set_root(obj) +class _InterfaceModule(object): + def __init__(self, schema): + self.schema = schema + + def _new_client(self, server, loop): + return _DynamicCapabilityClient()._init_vals(self.schema, server, loop) + + def new_server(self, server): + return _DynamicCapabilityServer(self.schema, server) + cdef class SchemaParser: """A class for loading Cap'n Proto schema files. @@ -1793,7 +1824,7 @@ cdef class SchemaParser: schema = nodeSchema.get_nested(node.name) proto = schema.get_proto() if proto.isStruct: - local_module = StructModule(schema.as_struct()) + local_module = _StructModule(schema.as_struct()) class Reader(_DynamicStructReader): """An abstract base class. Readers are 'instances' of this class.""" __metaclass__ = _StructABCMeta @@ -1811,22 +1842,12 @@ cdef class SchemaParser: local_module.Reader = Reader local_module.Builder = Builder - + module.__dict__[node.name] = local_module elif proto.isConst: module.__dict__[node.name] = schema.as_const_value() elif proto.isInterface: - def new_client(bound_local_module): - def helper(server, loop): - return _DynamicCapabilityClient()._init_vals(bound_local_module, server, loop) - return helper - def new_server(bound_local_module): - def helper(server): - return _DynamicCapabilityServer(bound_local_module, server) - return helper - local_module.schema = schema.as_interface() - local_module.new_client = new_client(local_module) - local_module.new_server = new_server(local_module) + local_module = _InterfaceModule(schema.as_interface()) module.__dict__[node.name] = local_module diff --git a/docs/capnp.rst b/docs/capnp.rst index 354e911..b1afa42 100644 --- a/docs/capnp.rst +++ b/docs/capnp.rst @@ -7,15 +7,30 @@ API Reference .. currentmodule:: capnp -Functions -------------- -.. autofunction:: load - Internal Classes ---------------- These classes are internal to the library. You will never need to allocate one yourself, but you may end up using some of their member methods. +Modules +~~~~~~~~~~ +These are classes that are made for you when you import a Cap'n Proto file:: + + import capnp + import addressbook_capnp + + print type(addressbook_capnp.Person) # capnp.capnp._StructModule + +.. autoclass:: _StructModule + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: _InterfaceModule + :members: + :undoc-members: + :inherited-members: + Readers ~~~~~~~~~~ .. autoclass:: _DynamicStructReader @@ -45,9 +60,78 @@ Builders :undoc-members: :inherited-members: +RPC +~~~~~~~~~~~~~~~ + +.. autoclass:: _DynamicCapabilityClient + :members: + :undoc-members: + :inherited-members: + + +.. autoclass:: _CapabilityClient + :members: + :undoc-members: + :inherited-members: + Miscellaneous ~~~~~~~~~~~~~ .. autoclass:: _DynamicOrphan :members: :undoc-members: :inherited-members: + +Functions +------------- +.. autofunction:: load +.. autofunction:: add_import_hook +.. autofunction:: remove_import_hook + +Classes +---------------- + +RPC +~~~~~~~~~~~~~~~ +.. autoclass:: EventLoop + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: FdAsyncIoStream + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: RpcClient + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: RpcServer + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: Restorer + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: KjException + :members: + :undoc-members: + :inherited-members: + +.. autoclass:: PromiseFulfillerPair + :members: + :undoc-members: + :inherited-members: + +Miscellaneous +~~~~~~~~~~~~~ +.. autoclass:: SchemaParser + :members: + :undoc-members: + :inherited-members: + + diff --git a/examples/example_capability.py b/examples/example_capability.py index 7626c36..664875c 100644 --- a/examples/example_capability.py +++ b/examples/example_capability.py @@ -17,9 +17,10 @@ def example_simple_rpc(): loop = capnp.EventLoop() - read, write = socket.socketpair(socket.AF_UNIX) - read_stream = capnp.FdAsyncIoStream(read.fileno()) - write_stream = capnp.FdAsyncIoStream(write.fileno()) + import os + read, write = os.pipe() + read_stream = capnp.FdAsyncIoStream(write) + write_stream = capnp.FdAsyncIoStream(read) restorer = capnp.Restorer(capability.TestSturdyRefObjectId, _restore) server = capnp.RpcServer(loop, write_stream, restorer) diff --git a/test/test_capability.py b/test/test_capability.py index 100a994..660ca8d 100644 --- a/test/test_capability.py +++ b/test/test_capability.py @@ -26,7 +26,7 @@ class PipelineServer: def test_client(capability): loop = capnp.EventLoop() - client = capability.TestInterface.new_client(Server(), loop) + client = capability.TestInterface._new_client(Server(), loop) req = client._request('foo') req.i = 5 @@ -60,7 +60,7 @@ def test_client(capability): def test_simple_client(capability): loop = capnp.EventLoop() - client = capability.TestInterface.new_client(Server(), loop) + client = capability.TestInterface._new_client(Server(), loop) remote = client._send('foo', i=5) response = loop.wait(remote) @@ -73,6 +73,11 @@ def test_simple_client(capability): assert response.x == '26' + remote = client.foo(5) + response = loop.wait(remote) + + assert response.x == '26' + with pytest.raises(ValueError): remote = client.foo(i='foo') @@ -85,8 +90,8 @@ def test_simple_client(capability): def test_pipeline(capability): loop = capnp.EventLoop() - client = capability.TestPipeline.new_client(PipelineServer(), loop) - foo_client = capability.TestInterface.new_client(Server(), loop) + client = capability.TestPipeline._new_client(PipelineServer(), loop) + foo_client = capability.TestInterface._new_client(Server(), loop) remote = client.getCap(n=5, inCap=foo_client) @@ -110,7 +115,7 @@ class BadServer: def test_exception_client(capability): loop = capnp.EventLoop() - client = capability.TestInterface.new_client(BadServer(), loop) + client = capability.TestInterface._new_client(BadServer(), loop) remote = client._send('foo', i=5) with pytest.raises(capnp.KjException): @@ -129,8 +134,8 @@ class BadPipelineServer: def test_exception_chain(capability): loop = capnp.EventLoop() - client = capability.TestPipeline.new_client(BadPipelineServer(), loop) - foo_client = capability.TestInterface.new_client(BadServer(), loop) + client = capability.TestPipeline._new_client(BadPipelineServer(), loop) + foo_client = capability.TestInterface._new_client(BadServer(), loop) remote = client.getCap(n=5, inCap=foo_client) @@ -142,8 +147,8 @@ def test_exception_chain(capability): def test_pipeline_exception(capability): loop = capnp.EventLoop() - client = capability.TestPipeline.new_client(BadPipelineServer(), loop) - foo_client = capability.TestInterface.new_client(BadServer(), loop) + client = capability.TestPipeline._new_client(BadPipelineServer(), loop) + foo_client = capability.TestInterface._new_client(BadServer(), loop) remote = client.getCap(n=5, inCap=foo_client) @@ -159,7 +164,7 @@ def test_pipeline_exception(capability): def test_casting(capability): loop = capnp.EventLoop() - client = capability.TestExtends.new_client(Server(), loop) + client = capability.TestExtends._new_client(Server(), loop) client2 = client.upcast(capability.TestInterface) client3 = client2.cast_as(capability.TestInterface)