Change load to use a global SchemaParser. Make structs settable as field

This commit is contained in:
Jason Paryani
2013-08-29 21:03:00 -07:00
parent 1d09bb71dd
commit f7fbe5bdb3
3 changed files with 143 additions and 34 deletions

View File

@@ -9,7 +9,7 @@ Example Usage::
# Building # Building
message = capnp.MallocMessageBuilder() message = capnp.MallocMessageBuilder()
addressBook = message.initRoot(addressbook.AddressBook) addressBook = message.initRoot(addressbook.AddressBook)
people = addressBook.init('people', 2) people = addressBook.init('people', 1)
alice = people[0] alice = people[0]
alice.id = 123 alice.id = 123

View File

@@ -249,12 +249,27 @@ cdef class _DynamicListBuilder:
cdef C_DynamicValue.Reader temp = C_DynamicValue.Reader(value) cdef C_DynamicValue.Reader temp = C_DynamicValue.Reader(value)
self.thisptr.set(index, temp) self.thisptr.set(index, temp)
cdef _setattrDynamicStructBuilder(self, index, _DynamicStructBuilder value):
cdef C_DynamicValue.Reader temp = C_DynamicValue.Reader(value.thisptr.asReader())
self.thisptr.set(index, temp)
cdef _setattrDynamicStructReader(self, index, _DynamicStructReader value):
cdef C_DynamicValue.Reader temp = C_DynamicValue.Reader(value.thisptr)
self.thisptr.set(index, temp)
def __setitem__(self, index, value): def __setitem__(self, index, value):
size = self.thisptr.size() size = self.thisptr.size()
if index >= size: if index >= size:
raise IndexError('Out of bounds') raise IndexError('Out of bounds')
index = index % size index = index % size
self._setitem(index, value) value_type = type(value)
if value_type is _DynamicStructBuilder:
self._setattrDynamicStructBuilder(index, value)
elif value_type is _DynamicStructReader:
self._setattrDynamicStructReader(index, value)
else:
self._setitem(index, value)
def __len__(self): def __len__(self):
return self.thisptr.size() return self.thisptr.size()
@@ -474,8 +489,17 @@ cdef class _DynamicStructBuilder:
cdef C_DynamicValue.Reader temp = C_DynamicValue.Reader(VOID) cdef C_DynamicValue.Reader temp = C_DynamicValue.Reader(VOID)
self.thisptr.set(field, temp) self.thisptr.set(field, temp)
cdef _setattrDynamicStructBuilder(self, field, _DynamicStructBuilder value):
cdef C_DynamicValue.Reader temp = C_DynamicValue.Reader(value.thisptr.asReader())
self.thisptr.set(field, temp)
cdef _setattrDynamicStructReader(self, field, _DynamicStructReader value):
cdef C_DynamicValue.Reader temp = C_DynamicValue.Reader(value.thisptr)
self.thisptr.set(field, temp)
def __setattr__(self, field, value): def __setattr__(self, field, value):
value_type = type(value) value_type = type(value)
if value_type is int: if value_type is int:
self._setattrInt(field, value) self._setattrInt(field, value)
elif value_type is float: elif value_type is float:
@@ -486,6 +510,10 @@ cdef class _DynamicStructBuilder:
self._setattrString(field, value) self._setattrString(field, value)
elif value is None: elif value is None:
self._setattrVoid(field) self._setattrVoid(field)
elif value_type is _DynamicStructBuilder:
self._setattrDynamicStructBuilder(field, value)
elif value_type is _DynamicStructReader:
self._setattrDynamicStructReader(field, value)
else: else:
raise ValueError("Non primitive type") raise ValueError("Non primitive type")
@@ -515,7 +543,7 @@ cdef class _DynamicStructBuilder:
cpdef initResizableList(self, field): cpdef initResizableList(self, field):
"""Method for initializing fields that are of type list (of structs) """Method for initializing fields that are of type list (of structs)
This version of init returns a :class:`_DynamicResizableListBuilder` that allows you to add members one at a time (ie. if you don't know the size for sure). This is only meant for lists of Cap'n Proto objects, since you can just define a normal python and fill it with primitive types like int/float. This version of init returns a :class:`_DynamicResizableListBuilder` that allows you to add members one at a time (ie. if you don't know the size for sure). This is only meant for lists of Cap'n Proto objects, since for primitive types you can just define a normal python list and fill it yourself.
.. warning:: You need to call :meth:`_DynamicResizableListBuilder.finish` on the list object before serializing the Cap'n Proto message. Failure to do so will cause your objects not to be written out as well as leaking orphan structs into your message. .. warning:: You need to call :meth:`_DynamicResizableListBuilder.finish` on the list object before serializing the Cap'n Proto message. Failure to do so will cause your objects not to be written out as well as leaking orphan structs into your message.
@@ -692,7 +720,11 @@ cdef class _ParsedSchema:
cpdef getNested(self, name): cpdef getNested(self, name):
return _ParsedSchema()._init(self.thisptr.getNested(name)) return _ParsedSchema()._init(self.thisptr.getNested(name))
cdef class _SchemaParser: cdef class SchemaParser:
"""A class for loading Cap'n Proto schema files.
Do not use this class unless you're sure you know what you're doing. Use the convenience method :func:`load` instead.
"""
cdef C_SchemaParser * thisptr cdef C_SchemaParser * thisptr
def __cinit__(self): def __cinit__(self):
self.thisptr = new C_SchemaParser() self.thisptr = new C_SchemaParser()
@@ -700,7 +732,7 @@ cdef class _SchemaParser:
def __dealloc__(self): def __dealloc__(self):
del self.thisptr del self.thisptr
def parseDiskFile(self, displayName, diskPath, imports): def _parseDiskFile(self, displayName, diskPath, imports):
cdef StringPtr * importArray = <StringPtr *>malloc(sizeof(StringPtr) * len(imports)) cdef StringPtr * importArray = <StringPtr *>malloc(sizeof(StringPtr) * len(imports))
for i in range(len(imports)): for i in range(len(imports)):
@@ -715,6 +747,69 @@ cdef class _SchemaParser:
return ret return ret
def load(self, file_name, display_name=None, imports=[]):
"""Load a Cap'n Proto schema from a file
You will have to load a schema before you can begin doing anything
meaningful with this library. Loading a schema is much like loading
a Python module (and load even returns a `ModuleType`). Once it's been
loaded, you use it much like any other Module::
parser = capnp.SchemaParser()
addressbook = parser.load('addressbook.capnp')
print addressbook.qux # qux is a top level constant
# 123
message = capnp.MallocMessageBuilder()
person = message.initRoot(addressbook.Person)
:type file_name: str
:param file_name: A relative or absolute path to a Cap'n Proto schema
:type display_name: str
:param display_name: The name internally used by the Cap'n Proto library
for the loaded schema. By default, it's just os.path.basename(file_name)
:type imports: list
:param imports: A list of str directories to add to the import path.
:rtype: ModuleType
:return: A module corresponding to the loaded schema. You can access
parsed schemas and constants with . syntax
:Raises: :exc:`exceptions.ValueError` if `file_name` doesn't exist
"""
def _load(nodeSchema, module):
module._nodeSchema = nodeSchema
nodeProto = nodeSchema.getProto()
module._nodeProto = nodeProto
for node in nodeProto.nestedNodes:
local_module = _ModuleType(node.name)
module.__dict__[node.name] = local_module
schema = nodeSchema.getNested(node.name)
proto = schema.getProto()
if proto.isStruct:
local_module.schema = schema.asStruct()
elif proto.isConst:
module.__dict__[node.name] = schema.asConstValue()
_load(schema, local_module)
if display_name is None:
display_name = _os.path.basename(file_name)
module = _ModuleType(display_name)
parser = self
module._parser = parser
fileSchema = parser._parseDiskFile(display_name, file_name, imports)
_load(fileSchema, module)
return module
cdef class MessageBuilder: cdef class MessageBuilder:
"""An abstract base class for building Cap'n Proto messages """An abstract base class for building Cap'n Proto messages
@@ -941,11 +1036,13 @@ def writePackedMessageToFd(int fd, MessageBuilder message):
from types import ModuleType as _ModuleType from types import ModuleType as _ModuleType
import os as _os import os as _os
_global_schema_parser = None
def load(file_name, display_name=None, imports=[]): def load(file_name, display_name=None, imports=[]):
"""Load a Cap'n Proto schema from a file """Load a Cap'n Proto schema from a file
You will have to load a schema before you can begin doing anything You will have to load a schema before you can begin doing anything
meaningful with this library. Loading a schema is much like Loading meaningful with this library. Loading a schema is much like loading
a Python module (and load even returns a `ModuleType`). Once it's been a Python module (and load even returns a `ModuleType`). Once it's been
loaded, you use it much like any other Module:: loaded, you use it much like any other Module::
@@ -972,32 +1069,8 @@ def load(file_name, display_name=None, imports=[]):
:Raises: :exc:`exceptions.ValueError` if `file_name` doesn't exist :Raises: :exc:`exceptions.ValueError` if `file_name` doesn't exist
""" """
def _load(nodeSchema, module): global _global_schema_parser
module._nodeSchema = nodeSchema if _global_schema_parser is None:
nodeProto = nodeSchema.getProto() _global_schema_parser = SchemaParser()
module._nodeProto = nodeProto
for node in nodeProto.nestedNodes: return _global_schema_parser.load(file_name, display_name, imports)
local_module = _ModuleType(node.name)
module.__dict__[node.name] = local_module
schema = nodeSchema.getNested(node.name)
proto = schema.getProto()
if proto.isStruct:
local_module.schema = schema.asStruct()
elif proto.isConst:
module.__dict__[node.name] = schema.asConstValue()
_load(schema, local_module)
if display_name is None:
display_name = _os.path.basename(file_name)
module = _ModuleType(display_name)
parser = _SchemaParser()
module._parser = parser
fileSchema = parser.parseDiskFile(display_name, file_name, imports)
_load(fileSchema, module)
return module

View File

@@ -8,6 +8,14 @@ this_dir = os.path.dirname(__file__)
def addressbook(): def addressbook():
return capnp.load(os.path.join(this_dir, 'addressbook.capnp')) return capnp.load(os.path.join(this_dir, 'addressbook.capnp'))
@pytest.fixture
def foo():
return capnp.load(os.path.join(this_dir, 'foo.capnp'))
@pytest.fixture
def bar():
return capnp.load(os.path.join(this_dir, 'bar.capnp'))
def test_basic_load(): def test_basic_load():
capnp.load(os.path.join(this_dir, 'addressbook.capnp')) capnp.load(os.path.join(this_dir, 'addressbook.capnp'))
@@ -17,3 +25,31 @@ def test_constants(addressbook):
def test_classes(addressbook): def test_classes(addressbook):
assert addressbook.AddressBook assert addressbook.AddressBook
assert addressbook.Person assert addressbook.Person
def test_import(foo, bar):
m = capnp.MallocMessageBuilder()
foo = m.initRoot(foo.Foo)
m2 = capnp.MallocMessageBuilder()
bar = m2.initRoot(bar.Bar)
foo.name = 'foo'
bar.foo = foo
assert bar.foo.name == 'foo'
def test_failed_import():
s = capnp.SchemaParser()
s2 = capnp.SchemaParser()
foo = s.load(os.path.join(this_dir, 'foo.capnp'))
bar = s2.load(os.path.join(this_dir, 'bar.capnp'))
m = capnp.MallocMessageBuilder()
foo = m.initRoot(foo.Foo)
m2 = capnp.MallocMessageBuilder()
bar = m2.initRoot(bar.Bar)
foo.name = 'foo'
with pytest.raises(ValueError):
bar.foo = foo