From 93352a31d27284c05c80e6e37374156040ed23d4 Mon Sep 17 00:00:00 2001 From: Jason Paryani Date: Thu, 4 Sep 2014 14:21:27 -0700 Subject: [PATCH] Initial version of code generating plugin The plugin generates higher performance compiled versions of the structs --- capnp/__init__.py | 2 +- capnp/_gen.py | 37 ++++ capnp/includes/capnp_cpp.pxd | 2 + capnp/lib/capnp.pxd | 9 +- capnp/lib/capnp.pyx | 81 +++++++- capnp/schema.capnp | 383 +++++++++++++++++++++++++++++++++++ capnp/templates/module.pyx | 74 +++++++ capnp/templates/setup.py | 9 + requirements.txt | 3 +- setup.py | 6 +- 10 files changed, 597 insertions(+), 9 deletions(-) create mode 100644 capnp/_gen.py create mode 100644 capnp/schema.capnp create mode 100644 capnp/templates/module.pyx create mode 100644 capnp/templates/setup.py diff --git a/capnp/__init__.py b/capnp/__init__.py index 0685f93..2a04e92 100644 --- a/capnp/__init__.py +++ b/capnp/__init__.py @@ -1,7 +1,7 @@ """A python library wrapping the Cap'n Proto C++ library Example Usage:: - + import capnp addressbook = capnp.load('addressbook.capnp') diff --git a/capnp/_gen.py b/capnp/_gen.py new file mode 100644 index 0000000..bd626b3 --- /dev/null +++ b/capnp/_gen.py @@ -0,0 +1,37 @@ +from __future__ import print_function + +import capnp +import schema_capnp +import sys +from jinja2 import Environment, PackageLoader + +def main(): + env = Environment(loader=PackageLoader('capnp', 'templates')) + env.filters['format_name'] = lambda name: name[name.find(':')+1:] + + code = schema_capnp.CodeGeneratorRequest.read(sys.stdin) + code=code.to_dict() + code['nodes'] = [node for node in code['nodes'] if 'struct' in node] + for node in code['nodes']: + displayName = node['displayName'] + parent, path = displayName.split(':') + node['module_path'] = parent.replace('.', '_') + '.' + '.'.join([x[0].upper() + x[1:] for x in path.split('.')]) + node['module_name'] = path.replace('.', '_') + node['schema'] = '_{}_Schema'.format(node['module_name']) + is_union = False + for field in node['struct']['fields']: + if field['discriminantValue'] != 65535: + is_union = True + node['is_union'] = is_union + + module = env.get_template('module.pyx') + filename = code['requestedFiles'][0]['filename'].replace('.', '_') + '_cython.pyx' + # TODO: handle multiple files + with open(filename, 'w') as out: + out.write(module.render(code=code)) + + setup = env.get_template('setup.py') + with open('setup_capnp.py', 'w') as out: + out.write(setup.render(code=code)) + print('You now need to build the cython module by running `python setup_capnp.py build_ext --inplace`.') + print() diff --git a/capnp/includes/capnp_cpp.pxd b/capnp/includes/capnp_cpp.pxd index 1f2e3be..1e0c0e5 100644 --- a/capnp/includes/capnp_cpp.pxd +++ b/capnp/includes/capnp_cpp.pxd @@ -229,6 +229,7 @@ cdef extern from "capnp/dynamic.h" namespace " ::capnp": bint has(char *) except +reraise_kj_exception bint hasByField"has"(StructSchema.Field) except +reraise_kj_exception StructSchema getSchema() + uint64_t getId"getSchema().getProto().getId"() Maybe[StructSchema.Field] which() MessageSize totalSize() cppclass Pipeline: @@ -251,6 +252,7 @@ cdef extern from "capnp/dynamic.h" namespace " ::capnp": DynamicValueForward.Builder initByField"init"(StructSchema.Field, uint size) except +reraise_kj_exception DynamicValueForward.Builder initByField"init"(StructSchema.Field) except +reraise_kj_exception StructSchema getSchema() + uint64_t getId"getSchema().getProto().getId"() Maybe[StructSchema.Field] which() void adopt(char *, DynamicOrphan) except +reraise_kj_exception DynamicOrphan disown(char *) diff --git a/capnp/lib/capnp.pxd b/capnp/lib/capnp.pxd index 0b2a904..de44bdb 100644 --- a/capnp/lib/capnp.pxd +++ b/capnp/lib/capnp.pxd @@ -30,7 +30,7 @@ cdef class _DynamicStructReader: cdef object _obj_to_pin cdef object _schema - cdef _init(self, C_DynamicStruct.Reader other, object parent, bint isRoot=?) + cdef _init(self, C_DynamicStruct.Reader other, object parent, bint isRoot=?, bint tryRegistry=?) cpdef _get(self, field) cpdef _has(self, field) @@ -48,7 +48,7 @@ cdef class _DynamicStructBuilder: cdef bint _is_written cdef object _schema - cdef _init(self, DynamicStruct_Builder other, object parent, bint isRoot=?) + cdef _init(self, DynamicStruct_Builder other, object parent, bint isRoot=?, bint tryRegistry=?) cdef _check_write(self) cpdef to_bytes(_DynamicStructBuilder self) @@ -82,3 +82,8 @@ cdef class _Schema: cpdef as_enum(self) cpdef get_dependency(self, id) cpdef get_proto(self) + +cdef to_python_reader(C_DynamicValue.Reader self, object parent) +cdef to_python_builder(C_DynamicValue.Builder self, object parent) +cdef _to_dict(msg, bint verbose) +cdef _setDynamicFieldWithField(DynamicStruct_Builder thisptr, _StructSchemaField field, value, parent) diff --git a/capnp/lib/capnp.pyx b/capnp/lib/capnp.pyx index 9789924..714730e 100644 --- a/capnp/lib/capnp.pyx +++ b/capnp/lib/capnp.pyx @@ -33,6 +33,14 @@ _CAPNP_VERSION_MINOR = capnp.CAPNP_VERSION_MINOR _CAPNP_VERSION_MICRO = capnp.CAPNP_VERSION_MICRO _CAPNP_VERSION = capnp.CAPNP_VERSION +cdef dict _type_registry = {} + +def register_type(id, klass): + _type_registry[id] = klass + +def deregister_all_types(): + _type_registry = {} + # By making it public, we'll be able to call it from capabilityHelper.h cdef public object wrap_dynamic_struct_reader(Response & r) with gil: return _Response()._init_childptr(new Response(moveResponse(r)), None) @@ -671,6 +679,17 @@ cdef _setBaseString(_DynamicSetterClasses thisptr, field, value): cdef C_DynamicValue.Reader temp = C_DynamicValue.Reader(temp_string) thisptr.set(field, temp) +cdef _setBytesField(DynamicStruct_Builder thisptr, _StructSchemaField field, value): + cdef capnp.StringPtr temp_string = capnp.StringPtr(value, len(value)) + cdef C_DynamicValue.Reader temp = C_DynamicValue.Reader(temp_string) + thisptr.setByField(field.thisptr, temp) + +cdef _setBaseStringField(DynamicStruct_Builder thisptr, _StructSchemaField field, value): + encoded_value = value.encode() + cdef capnp.StringPtr temp_string = capnp.StringPtr(encoded_value, len(encoded_value)) + cdef C_DynamicValue.Reader temp = C_DynamicValue.Reader(temp_string) + thisptr.setByField(field.thisptr, temp) + cdef _setDynamicField(_DynamicSetterClasses thisptr, field, value, parent): cdef C_DynamicValue.Reader temp value_type = type(value) @@ -717,6 +736,48 @@ cdef _setDynamicField(_DynamicSetterClasses thisptr, field, value, parent): else: raise ValueError("Tried to set field: '{}' with a value of: '{}' which is an unsupported type: '{}'".format(field, str(value), str(type(value)))) +cdef _setDynamicFieldWithField(DynamicStruct_Builder thisptr, _StructSchemaField field, value, parent): + cdef C_DynamicValue.Reader temp + value_type = type(value) + + if value_type is int or value_type is long: + if value < 0: + temp = C_DynamicValue.Reader(value) + else: + temp = C_DynamicValue.Reader(value) + thisptr.setByField(field.thisptr, temp) + elif value_type is float: + temp = C_DynamicValue.Reader(value) + thisptr.setByField(field.thisptr, temp) + elif value_type is bool: + temp = C_DynamicValue.Reader(value) + thisptr.setByField(field.thisptr, temp) + elif value_type is bytes: + _setBytesField(thisptr, field, value) + elif isinstance(value, basestring): + _setBaseStringField(thisptr, field, value) + elif value_type is list: + builder = to_python_builder(thisptr.init(field.proto.name, len(value)), parent) + _from_list(builder, value) + elif value_type is dict: + builder = to_python_builder(thisptr.getByField(field.thisptr), parent) + _from_dict(builder, value) + elif value is None: + temp = C_DynamicValue.Reader(VOID) + thisptr.setByField(field.thisptr, temp) + elif value_type is _DynamicStructBuilder: + thisptr.setByField(field.thisptr, _extract_dynamic_struct_builder(value)) + elif value_type is _DynamicStructReader: + thisptr.setByField(field.thisptr, _extract_dynamic_struct_reader(value)) + elif value_type is _DynamicCapabilityClient: + thisptr.setByField(field.thisptr, _extract_dynamic_client(value)) + elif value_type is _DynamicCapabilityServer or isinstance(value, _DynamicCapabilityServer): + thisptr.setByField(field.thisptr, _extract_dynamic_server(value)) + elif value_type is _DynamicEnum: + thisptr.setByField(field.thisptr, _extract_dynamic_enum(value)) + else: + raise ValueError("Tried to set field: '{}' with a value of: '{}' which is an unsupported type: '{}'".format(field, str(value), str(type(value)))) + cdef _to_dict(msg, bint verbose): msg_type = type(msg) if msg_type is _DynamicListBuilder or msg_type is _DynamicListReader or msg_type is _DynamicResizableListBuilder: @@ -736,6 +797,9 @@ cdef _to_dict(msg, bint verbose): return ret + if isinstance(msg, (_DynamicStructBuilder, _DynamicStructReader)): + return msg.to_dict() + if msg_type is _DynamicEnum: return str(msg) @@ -864,11 +928,16 @@ cdef class _DynamicStructReader: print person.name # using . syntax print getattr(person, 'field-with-hyphens') # for names that are invalid for python, use getattr """ - cdef _init(self, C_DynamicStruct.Reader other, object parent, bint isRoot=False): + cdef _init(self, C_DynamicStruct.Reader other, object parent, bint isRoot=False, bint tryRegistry = True): self.thisptr = other self._parent = parent self.is_root = isRoot self._schema = None + + if tryRegistry and len(_type_registry) > 0: + registered_type = _type_registry.get(self.thisptr.getId(), None) + if registered_type: + return registered_type[0](self) return self cpdef _get(self, field): @@ -966,12 +1035,17 @@ cdef class _DynamicStructBuilder: setattr(person, 'field-with-hyphens', 'foo') # for names that are invalid for python, use setattr print getattr(person, 'field-with-hyphens') # for names that are invalid for python, use getattr """ - cdef _init(self, DynamicStruct_Builder other, object parent, bint isRoot = False): + cdef _init(self, DynamicStruct_Builder other, object parent, bint isRoot = False, bint tryRegistry = True): self.thisptr = other self._parent = parent self.is_root = isRoot self._is_written = False self._schema = None + + if tryRegistry and len(_type_registry) > 0: + registered_type = _type_registry.get(self.thisptr.getId(), None) + if registered_type: + return registered_type[1](self) return self cdef _check_write(self): @@ -1067,8 +1141,7 @@ cdef class _DynamicStructBuilder: _setDynamicField(self.thisptr, field, value, self._parent) cpdef _set_by_field(self, _StructSchemaField field, value): - # TODO: make this faster - _setDynamicField(self.thisptr, field.proto.name, value, self._parent) + _setDynamicFieldWithField(self.thisptr, field, value, self._parent) def __setattr__(self, field, value): self._set(field, value) diff --git a/capnp/schema.capnp b/capnp/schema.capnp new file mode 100644 index 0000000..bb3532e --- /dev/null +++ b/capnp/schema.capnp @@ -0,0 +1,383 @@ +# Copyright (c) 2013, Kenton Varda +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using Cxx = import "c++.capnp"; + +@0xa93fc509624c72d9; +$Cxx.namespace("capnp::schema"); + +using Id = UInt64; +# The globally-unique ID of a file, type, or annotation. + +struct Node { + id @0 :Id; + + displayName @1 :Text; + # Name to present to humans to identify this Node. You should not attempt to parse this. Its + # format could change. It is not guaranteed to be unique. + # + # (On Zooko's triangle, this is the node's nickname.) + + displayNamePrefixLength @2 :UInt32; + # If you want a shorter version of `displayName` (just naming this node, without its surrounding + # scope), chop off this many characters from the beginning of `displayName`. + + scopeId @3 :Id; + # ID of the lexical parent node. Typically, the scope node will have a NestedNode pointing back + # at this node, but robust code should avoid relying on this (and, in fact, group nodes are not + # listed in the outer struct's nestedNodes, since they are listed in the fields). `scopeId` is + # zero if the node has no parent, which is normally only the case with files, but should be + # allowed for any kind of node (in order to make runtime type generation easier). + + nestedNodes @4 :List(NestedNode); + # List of nodes nested within this node, along with the names under which they were declared. + + struct NestedNode { + name @0 :Text; + # Unqualified symbol name. Unlike Node.name, this *can* be used programmatically. + # + # (On Zooko's triangle, this is the node's petname according to its parent scope.) + + id @1 :Id; + # ID of the nested node. Typically, the target node's scopeId points back to this node, but + # robust code should avoid relying on this. + } + + annotations @5 :List(Annotation); + # Annotations applied to this node. + + union { + # Info specific to each kind of node. + + file @6 :Void; + + struct :group { + dataWordCount @7 :UInt16; + # Size of the data section, in words. + + pointerCount @8 :UInt16; + # Size of the pointer section, in pointers (which are one word each). + + preferredListEncoding @9 :ElementSize; + # The preferred element size to use when encoding a list of this struct. If this is anything + # other than `inlineComposite` then the struct is one word or less in size and is a candidate + # for list packing optimization. + + isGroup @10 :Bool; + # If true, then this "struct" node is actually not an independent node, but merely represents + # some named union or group within a particular parent struct. This node's scopeId refers + # to the parent struct, which may itself be a union/group in yet another struct. + # + # All group nodes share the same dataWordCount and pointerCount as the top-level + # struct, and their fields live in the same ordinal and offset spaces as all other fields in + # the struct. + # + # Note that a named union is considered a special kind of group -- in fact, a named union + # is exactly equivalent to a group that contains nothing but an unnamed union. + + discriminantCount @11 :UInt16; + # Number of fields in this struct which are members of an anonymous union, and thus may + # overlap. If this is non-zero, then a 16-bit discriminant is present indicating which + # of the overlapping fields is active. This can never be 1 -- if it is non-zero, it must be + # two or more. + # + # Note that the fields of an unnamed union are considered fields of the scope containing the + # union -- an unnamed union is not its own group. So, a top-level struct may contain a + # non-zero discriminant count. Named unions, on the other hand, are equivalent to groups + # containing unnamed unions. So, a named union has its own independent schema node, with + # `isGroup` = true. + + discriminantOffset @12 :UInt32; + # If `discriminantCount` is non-zero, this is the offset of the union discriminant, in + # multiples of 16 bits. + + fields @13 :List(Field); + # Fields defined within this scope (either the struct's top-level fields, or the fields of + # a particular group; see `isGroup`). + # + # The fields are sorted by ordinal number, but note that because groups share the same + # ordinal space, the field's index in this list is not necessarily exactly its ordinal. + # On the other hand, the field's position in this list does remain the same even as the + # protocol evolves, since it is not possible to insert or remove an earlier ordinal. + # Therefore, for most use cases, if you want to identify a field by number, it may make the + # most sense to use the field's index in this list rather than its ordinal. + } + + enum :group { + enumerants@14 :List(Enumerant); + # Enumerants ordered by numeric value (ordinal). + } + + interface :group { + methods @15 :List(Method); + # Methods ordered by ordinal. + + extends @31 :List(Id); + # Superclasses of this interface. + } + + const :group { + type @16 :Type; + value @17 :Value; + } + + annotation :group { + type @18 :Type; + + targetsFile @19 :Bool; + targetsConst @20 :Bool; + targetsEnum @21 :Bool; + targetsEnumerant @22 :Bool; + targetsStruct @23 :Bool; + targetsField @24 :Bool; + targetsUnion @25 :Bool; + targetsGroup @26 :Bool; + targetsInterface @27 :Bool; + targetsMethod @28 :Bool; + targetsParam @29 :Bool; + targetsAnnotation @30 :Bool; + } + } +} + +struct Field { + # Schema for a field of a struct. + + name @0 :Text; + + codeOrder @1 :UInt16; + # Indicates where this member appeared in the code, relative to other members. + # Code ordering may have semantic relevance -- programmers tend to place related fields + # together. So, using code ordering makes sense in human-readable formats where ordering is + # otherwise irrelevant, like JSON. The values of codeOrder are tightly-packed, so the maximum + # value is count(members) - 1. Fields that are members of a union are only ordered relative to + # the other members of that union, so the maximum value there is count(union.members). + + annotations @2 :List(Annotation); + + const noDiscriminant :UInt16 = 0xffff; + + discriminantValue @3 :UInt16 = Field.noDiscriminant; + # If the field is in a union, this is the value which the union's discriminant should take when + # the field is active. If the field is not in a union, this is 0xffff. + + union { + slot :group { + # A regular, non-group, non-fixed-list field. + + offset @4 :UInt32; + # Offset, in units of the field's size, from the beginning of the section in which the field + # resides. E.g. for a UInt32 field, multiply this by 4 to get the byte offset from the + # beginning of the data section. + + type @5 :Type; + defaultValue @6 :Value; + + hadExplicitDefault @10 :Bool; + # Whether the default value was specified explicitly. Non-explicit default values are always + # zero or empty values. Usually, whether the default value was explicit shouldn't matter. + # The main use case for this flag is for structs representing method parameters: + # explicitly-defaulted parameters may be allowed to be omitted when calling the method. + } + + group :group { + # A group. + + typeId @7 :Id; + # The ID of the group's node. + } + } + + ordinal :union { + implicit @8 :Void; + explicit @9 :UInt16; + # The original ordinal number given to the field. You probably should NOT use this; if you need + # a numeric identifier for a field, use its position within the field array for its scope. + # The ordinal is given here mainly just so that the original schema text can be reproduced given + # the compiled version -- i.e. so that `capnp compile -ocapnp` can do its job. + } +} + +struct Enumerant { + # Schema for member of an enum. + + name @0 :Text; + + codeOrder @1 :UInt16; + # Specifies order in which the enumerants were declared in the code. + # Like Struct.Field.codeOrder. + + annotations @2 :List(Annotation); +} + +struct Method { + # Schema for method of an interface. + + name @0 :Text; + + codeOrder @1 :UInt16; + # Specifies order in which the methods were declared in the code. + # Like Struct.Field.codeOrder. + + paramStructType @2 :Id; + # ID of the parameter struct type. If a named parameter list was specified in the method + # declaration (rather than a single struct parameter type) then a corresponding struct type is + # auto-generated. Such an auto-generated type will not be listed in the interface's + # `nestedNodes` and its `scopeId` will be zero -- it is completely detached from the namespace. + + resultStructType @3 :Id; + # ID of the return struct type; similar to `paramStructType`. + + annotations @4 :List(Annotation); +} + +struct Type { + # Represents a type expression. + + union { + # The ordinals intentionally match those of Value. + + void @0 :Void; + bool @1 :Void; + int8 @2 :Void; + int16 @3 :Void; + int32 @4 :Void; + int64 @5 :Void; + uint8 @6 :Void; + uint16 @7 :Void; + uint32 @8 :Void; + uint64 @9 :Void; + float32 @10 :Void; + float64 @11 :Void; + text @12 :Void; + data @13 :Void; + + list :group { + elementType @14 :Type; + } + + enum :group { + typeId @15 :Id; + } + struct :group { + typeId @16 :Id; + } + interface :group { + typeId @17 :Id; + } + + anyPointer @18 :Void; + } +} + +struct Value { + # Represents a value, e.g. a field default value, constant value, or annotation value. + + union { + # The ordinals intentionally match those of Type. + + void @0 :Void; + bool @1 :Bool; + int8 @2 :Int8; + int16 @3 :Int16; + int32 @4 :Int32; + int64 @5 :Int64; + uint8 @6 :UInt8; + uint16 @7 :UInt16; + uint32 @8 :UInt32; + uint64 @9 :UInt64; + float32 @10 :Float32; + float64 @11 :Float64; + text @12 :Text; + data @13 :Data; + + list @14 :AnyPointer; + + enum @15 :UInt16; + struct @16 :AnyPointer; + + interface @17 :Void; + # The only interface value that can be represented statically is "null", whose methods always + # throw exceptions. + + anyPointer @18 :AnyPointer; + } +} + +struct Annotation { + # Describes an annotation applied to a declaration. Note AnnotationNode describes the + # annotation's declaration, while this describes a use of the annotation. + + id @0 :Id; + # ID of the annotation node. + + value @1 :Value; +} + +enum ElementSize { + # Possible element sizes for encoded lists. These correspond exactly to the possible values of + # the 3-bit element size component of a list pointer. + + empty @0; # aka "void", but that's a keyword. + bit @1; + byte @2; + twoBytes @3; + fourBytes @4; + eightBytes @5; + pointer @6; + inlineComposite @7; +} + +struct CodeGeneratorRequest { + nodes @0 :List(Node); + # All nodes parsed by the compiler, including for the files on the command line and their + # imports. + + requestedFiles @1 :List(RequestedFile); + # Files which were listed on the command line. + + struct RequestedFile { + id @0 :Id; + # ID of the file. + + filename @1 :Text; + # Name of the file as it appeared on the command-line (minus the src-prefix). You may use + # this to decide where to write the output. + + imports @2 :List(Import); + # List of all imported paths seen in this file. + + struct Import { + id @0 :Id; + # ID of the imported file. + + name @1 :Text; + # Name which *this* file used to refer to the foreign file. This may be a relative name. + # This information is provided because it might be useful for code generation, e.g. to + # generate #include directives in C++. We don't put this in Node.file because this + # information is only meaningful at compile time anyway. + # + # (On Zooko's triangle, this is the import's petname according to the importing file.) + } + } +} diff --git a/capnp/templates/module.pyx b/capnp/templates/module.pyx new file mode 100644 index 0000000..2b50fbd --- /dev/null +++ b/capnp/templates/module.pyx @@ -0,0 +1,74 @@ +# addressbook_fast.pyx +# distutils: language = c++ +# distutils: extra_compile_args = --std=c++11 +# distutils: include_dirs = /usr/local/lib/python2.7/site-packages +# cython: c_string_type = str +# cython: c_string_encoding = default +# cython: embedsignature = True + +import capnp +{%- for file in code.requestedFiles %} +import {{file.filename | replace('.', '_')}} +{% endfor %} +from capnp.includes.capnp_cpp cimport DynamicValue +from capnp.lib.capnp cimport _DynamicStructReader, _DynamicStructBuilder, _StructSchemaField, to_python_builder, to_python_reader, _to_dict, _setDynamicFieldWithField + +{%- for node in code.nodes %} +{{node.schema}} = {{node.module_path}}.schema + {%- for field in node.struct.fields %} +cdef _StructSchemaField {{node.module_name}}_{{field.name}} = {{node.schema}}.fields['{{field.name}}'] + {%- endfor %} + +cdef class {{node.module_name}}_Reader(_DynamicStructReader): + def __init__(self, _DynamicStructReader struct): + self._init(struct.thisptr, struct._parent, struct.is_root, False) + {% for field in node.struct.fields %} + cpdef _get_{{field.name}}(self): + cdef DynamicValue.Reader temp = self.thisptr.getByField({{node.module_name}}_{{field.name}}.thisptr) + return to_python_reader(temp, self._parent) + property {{field.name}}: + def __get__(self): + return self._get_{{field.name}}() + {%- endfor %} + + def to_dict(self, verbose=False): + return { + {% for field in node.struct.fields %} + '{{field.name}}': _to_dict(self.{{field.name}}, verbose), + {%- endfor %} + } + +cdef class {{node.module_name}}_Builder(_DynamicStructBuilder): + def __init__(self, _DynamicStructBuilder struct): + self._init(struct.thisptr, struct._parent, struct.is_root, False) + {% for field in node.struct.fields %} + cpdef _get_{{field.name}}(self): + cdef DynamicValue.Builder temp = self.thisptr.getByField({{node.module_name}}_{{field.name}}.thisptr) + return to_python_builder(temp, self._parent) + cpdef _set_{{field.name}}(self, value): + _setDynamicFieldWithField(self.thisptr, {{node.module_name}}_{{field.name}}, value, self._parent) + property {{field.name}}: + def __get__(self): + return self._get_{{field.name}}() + def __set__(self, value): + self._set_{{field.name}}(value) + {%- endfor %} + + def to_dict(self, verbose=False): + ret = { + {% for field in node.struct.fields %} + {% if field.discriminantValue == 65535 %} + '{{field.name}}': _to_dict(self.{{field.name}}, verbose), + {% endif %} + {%- endfor %} + } + + {% if node.is_union %} + which = self.which() + ret[which] = getattr(self, which) + {% endif %} + + return ret + +capnp.register_type({{node.id}}, ({{node.module_name}}_Reader, {{node.module_name}}_Builder)) +{% endfor %} diff --git a/capnp/templates/setup.py b/capnp/templates/setup.py new file mode 100644 index 0000000..46883bb --- /dev/null +++ b/capnp/templates/setup.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python +from distutils.core import setup +from Cython.Build import cythonize +import os + +setup( + name="{{code.requestedFiles[0] | replace('.', '_')}}", + ext_modules=cythonize('*_capnp_cython.pyx', language="c++") +) diff --git a/requirements.txt b/requirements.txt index 8b8993f..c98e80d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ +jinja2 >= 2.7.3 cython > 0.19 setuptools >= 0.8 pytest -tox \ No newline at end of file +tox diff --git a/setup.py b/setup.py index e88d004..b31be3b 100644 --- a/setup.py +++ b/setup.py @@ -53,11 +53,15 @@ setup( name="pycapnp", packages=["capnp"], version=VERSION, - package_data={'capnp': ['*.pxd', '*.h', '*.capnp', 'helpers/*.pxd', 'helpers/*.h', 'includes/*.pxd', 'lib/*.pxd', 'lib/*.py', 'lib/*.pyx']}, + package_data={'capnp': ['*.pxd', '*.h', '*.capnp', 'helpers/*.pxd', 'helpers/*.h', 'includes/*.pxd', 'lib/*.pxd', 'lib/*.py', 'lib/*.pyx', 'templates/*']}, ext_modules=cythonize('capnp/lib/*.pyx', language="c++"), install_requires=[ + 'jinja2 >= 2.7.3', 'cython > 0.19', 'setuptools >= 0.8'], + entry_points={ + 'console_scripts' : ['capnpc-cython = capnp._gen:main'] + }, # PyPi info description="A cython wrapping of the C++ Cap'n Proto library", long_description=long_description,