Skip to content

Commit

Permalink
Merge pull request #307 from Zentren/dynamic_schema_loading
Browse files Browse the repository at this point in the history
Schema loading from the wire
  • Loading branch information
haata authored Jun 19, 2023
2 parents b439993 + f59b3fd commit 8f3bfc3
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 4 deletions.
12 changes: 12 additions & 0 deletions capnp/helpers/deserialize.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

#include "capnp/dynamic.h"
#include "capnp/schema.capnp.h"

/// @brief Convert the dynamic struct to a Node::Reader
::capnp::schema::Node::Reader toReader(capnp::DynamicStruct::Reader reader)
{
// requires an intermediate step to AnyStruct before going directly to Node::Reader,
// since there exists no direct conversion from DynamicStruct::Reader to Node::Reader.
return reader.as<capnp::AnyStruct>().as<capnp::schema::Node>();
}
8 changes: 6 additions & 2 deletions capnp/helpers/helpers.pxd
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from capnp.includes.capnp_cpp cimport (
Maybe, PyPromise, VoidPromise, RemotePromise,
DynamicCapability, InterfaceSchema, EnumSchema, StructSchema, DynamicValue, Capability,
RpcSystem, MessageBuilder, Own, PyRefCounter
DynamicCapability, InterfaceSchema, EnumSchema, StructSchema, DynamicValue, Capability,
RpcSystem, MessageBuilder, Own, PyRefCounter, Node, DynamicStruct
)

from capnp.includes.schema_cpp cimport ByteArray
Expand Down Expand Up @@ -30,3 +30,7 @@ cdef extern from "capnp/helpers/rpcHelper.h":

cdef extern from "capnp/helpers/serialize.h":
ByteArray messageToPackedBytes(MessageBuilder &, size_t wordCount)

cdef extern from "capnp/helpers/deserialize.h":
Node.Reader toReader(DynamicStruct.Reader reader) except +reraise_kj_exception

2 changes: 1 addition & 1 deletion capnp/helpers/serialize.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ kj::Array< ::capnp::byte> messageToPackedBytes(capnp::MessageBuilder & message,
kj::ArrayOutputStream out(result.asPtr());
capnp::writePackedMessage(out, message);
return heapArray(out.getArray()); // TODO: make this non-copying somehow
}
}
6 changes: 6 additions & 0 deletions capnp/includes/capnp_cpp.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,12 @@ cdef extern from "capnp/dynamic.h" namespace " ::capnp":
DynamicStruct.Pipeline asStruct"releaseAs< ::capnp::DynamicStruct>"()
Type getType()

cdef extern from "capnp/schema-loader.h" namespace " ::capnp":
cdef cppclass SchemaLoader:
SchemaLoader()
Schema load(Node.Reader reader) except +reraise_kj_exception
Schema get(uint64_t id_) except +reraise_kj_exception

cdef extern from "capnp/schema-parser.h" namespace " ::capnp":
cdef cppclass ParsedSchema(Schema) nogil:
ParsedSchema getNested(char * name) except +reraise_kj_exception
Expand Down
5 changes: 4 additions & 1 deletion capnp/lib/capnp.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ from capnp.includes cimport schema_cpp
from capnp.includes.capnp_cpp cimport (
Schema as C_Schema, StructSchema as C_StructSchema, InterfaceSchema as C_InterfaceSchema,
EnumSchema as C_EnumSchema, ListSchema as C_ListSchema, DynamicStruct as C_DynamicStruct,
DynamicValue as C_DynamicValue, Type as C_Type, DynamicList as C_DynamicList,
DynamicValue as C_DynamicValue, Type as C_Type, DynamicList as C_DynamicList, SchemaLoader as C_SchemaLoader,
SchemaParser as C_SchemaParser, ParsedSchema as C_ParsedSchema, VOID, ArrayPtr, StringPtr,
String, StringTree, DynamicOrphan as C_DynamicOrphan, AnyPointer as C_DynamicObject,
DynamicCapability as C_DynamicCapability, Request, Response, RemotePromise, Promise,
Expand All @@ -30,6 +30,9 @@ cdef class _StringArrayPtr:
cdef size_t size
cdef ArrayPtr[StringPtr] asArrayPtr(self) except +reraise_kj_exception

cdef class SchemaLoader:
cdef C_SchemaLoader * thisptr

cdef class SchemaParser:
cdef C_SchemaParser * thisptr
cdef public dict modules_by_id
Expand Down
33 changes: 33 additions & 0 deletions capnp/lib/capnp.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,11 @@ cdef class _NodeReader:
property isEnum:
def __get__(self):
return self.thisptr.isEnum()

property node:
"""A property that returns the NodeReader as a DynamicStructReader."""
def __get__(self):
return _DynamicStructReader()._init(self.thisptr, self)


cdef class _NestedNodeReader:
Expand Down Expand Up @@ -3231,6 +3236,34 @@ cdef class _StringArrayPtr:
return ArrayPtr[StringPtr](self.thisptr, self.size)


cdef class SchemaLoader:
""" Class which can be used to construct Schema objects from schema::Nodes as defined in
schema.capnp.
This class wraps capnproto/c++/src/capnp/schema-loader.h directly."""
def __cinit__(self):
self.thisptr = new C_SchemaLoader()

def __dealloc__(self):
del self.thisptr

def load(self, _NodeReader reader):
"""Loads the given schema node. Validates the node and throws an exception if invalid. This
makes a copy of the schema, so the object passed in can be destroyed after this returns.
"""
return _Schema()._init(self.thisptr.load(reader.thisptr))

def load_dynamic(self, _DynamicStructReader reader):
"""Loads the given schema node with self.load, but converts from a _DynamicStructReader
first."""
return _Schema()._init(self.thisptr.load(helpers.toReader(reader.thisptr)))

def get(self, id_):
"""Gets the schema for the given ID, throwing an exception if it isn't present."""
return _Schema()._init(self.thisptr.get(<uint64_t>id_))


cdef class SchemaParser:
"""A class for loading Cap'n Proto schema files.
Expand Down
4 changes: 4 additions & 0 deletions docs/capnp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ Miscellaneous
:undoc-members:
:inherited-members:

.. autoclass:: SchemaLoader
:members:
:undoc-members:
:inherited-members:

Functions
---------
Expand Down
13 changes: 13 additions & 0 deletions test/foo.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,16 @@ struct Foo {
name @1 :Text;
}


struct Baz{
text @0 :Text;
qux @1 :Qux;
}

struct Qux{
id @0 :UInt64;
}

interface Wrapper {
wrapped @0 (object :AnyPointer);
}
28 changes: 28 additions & 0 deletions test/test_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,31 @@ def test_bundled_import_hook():
# stream.capnp should be bundled, or provided by the system capnproto
capnp.add_import_hook()
import stream_capnp # noqa: F401


async def test_load_capnp(foo):
# test dynamically loading
loader = capnp.SchemaLoader()
loader.load(foo.Baz.schema.get_proto())
loader.load_dynamic(foo.Qux.schema.get_proto().node)

schema = loader.get(foo.Baz.schema.get_proto().node.id).as_struct()
assert "text" in schema.fieldnames
assert "qux" in schema.fieldnames
assert schema.fields["qux"].proto.slot.type.which == "struct"

class Wrapper(foo.Wrapper.Server):
async def wrapped(self, object, **kwargs):
assert isinstance(object, capnp.lib.capnp._DynamicObjectReader)
baz_ = object.as_struct(schema)
assert baz_.text == "test"
assert baz_.qux.id == 2

# test calling into the wrapper with a Baz message.
baz_ = foo.Baz.new_message()
baz_.text = "test"
baz_.qux.id = 2

wrapper = foo.Wrapper._new_client(Wrapper())
remote = wrapper.wrapped(baz_)
await remote

0 comments on commit 8f3bfc3

Please sign in to comment.