.. _quickstart: Quickstart =================== This assumes you already have the capnp library installed. If you don't, please follow the instructions at :ref:`Installation ` first. In general, this library is a very light wrapping of the `Cap'n Proto C++ library `_. You can refer to its docs for more advanced concepts, or just to get a basic idea of how the python library is structured. Load a Cap'n Proto Schema ------------------------- First you need to import the library:: import capnp Then you can load the Cap'n Proto schema with:: import addressbook_capnp This will look all through all the directories in your sys.path/PYTHONPATH, and try to find a file of the form 'addressbook.capnp'. If you want to disable the import hook magic that `import capnp` adds, and load manually, here's how:: capnp.remove_import_hook() addressbook_capnp = capnp.load('addressbook.capnp') For future reference, here is the Cap'n Proto schema. Also available in the github repository under `examples/addressbook.capnp `_:: # addressbook.capnp 0x934efea7f017fff0; const qux :UInt32 = 123; struct Person { id @0 :UInt32; name @1 :Text; email @2 :Text; phones @3 :List(PhoneNumber); struct PhoneNumber { number @0 :Text; type @1 :Type; enum Type { mobile @0; home @1; work @2; } } employment @4 union { unemployed @5 :Void; employer @6 :Text; school @7 :Text; selfEmployed @8 :Void; # We assume that a person is only one of these. } } struct AddressBook { people @0 :List(Person); } Const values ~~~~~~~~~~~~~~ Const values show up just as you'd expect under the loaded schema. For example:: print addressbook_capnp.qux # 123 Build a message ------------------ Initialize a New Cap'n Proto Object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Now that you have a message buffer, you need to allocate an actual object that is from your schema. In this case, we will allocate an `AddressBook`:: addresses = addressbook_capnp.AddressBook.new_message() Notice that we used `addressbook_capnp` from the previous section: `Load a Cap'n Proto Schema`_. List ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Allocating a list inside of an object requires use of the `init` function:: people = addresses.init('people', 2) For now, let's grab the first element out of this list and assign it to a variable named `alice`:: alice = people[0] Primitive Types ~~~~~~~~~~~~~~~~~~~~~~~~~~~ For all primitive types, from the Cap'n Proto docs: - Boolean: Bool - Integers: Int8, Int16, Int32, Int64 - Unsigned integers: UInt8, UInt16, UInt32, UInt64 - Floating-point: Float32, Float64 - Blobs: Text, Data You can assign straight to the variable with the corresponding Python type. For Blobs, you use strings. Assignment happens just by using the `.` syntax on the object you contstructed above:: alice.id = 123 alice.name = 'Alice' alice.email = 'alice@example.com' Enums ~~~~~~~~~~~~~~ First we'll allocate a length one list of phonenumbers for `alice`:: alicePhone = alice.init('phones', 1)[0] Note that even though it was a length 1 list, it was still a list that was returned, and we extracted the first (and only) element with `[0]`. Now, enums are treated like strings, and you just assign to them like there were a Text field:: alicePhone.type = 'mobile' If you assign an invalid value to one, you will get a ValueError:: alicePhone.type = 'foo' --------------------------------------------------------------------------- ValueError Traceback (most recent call last) ... ValueError: src/capnp/schema.c++:326: requirement not met: enum has no such enumerant; name = foo Unions ~~~~~~~~~~~~~~~~~~ For the most part, you just treat them like structs:: alice.employment.school = "MIT" Now the `school` field is the active part of the union, and we've assigned `'MIT'` to it. You can query which field is set in a union with `which()`, shown in `Reading Unions`_ Also, one weird case is for Void types in Unions (and in general, but Void is really only used in Unions). For these, you will have to assign `None` to them:: bob.employment.unemployed = None Writing to a File ~~~~~~~~~~~~~~~~~~~ Once you're done assigning to all the fields in a message, you can write it to a file like so:: f = open('example.bin', 'w') addresses.write(f) There is also a `write_packed` function, that writes out the message more space-efficientally. If you use write_packed, make sure to use read_packed when reading the message. Read a message ----------------- Reading from a file ~~~~~~~~~~~~~~~~~~~~~~ Much like before, you will have to de-serialize the message from a file descriptor:: f = open('example.bin') addresses = addressbook_capnp.AddressBook.read(f) Note that this very much needs to match the type you wrote out. In general, you will always be sending the same message types out over a given channel or you should wrap all your types in an unnamed union. Unnamed unions are defined in the .capnp file like so:: struct Message { union { person @0 :Person; addressbook @1 :AddressBook; } } Reading Fields ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Fields are very easy to read. You just use the `.` syntax as before. Lists behave just like normal Python lists:: for person in addresses.people: print(person.name, ':', person.email) for phone in person.phones: print(phone.type, ':', phone.number) Reading Unions ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The only tricky one is unions, where you need to call `.which()` to determine the union type. The `.which()` call returns an enum, ie. a string, corresponding to the field name:: which = person.employment.which() print(which) if which == 'unemployed': print('unemployed') elif which == 'employer': print('employer:', person.employment.employer) elif which == 'school': print('student at:', person.employment.school) elif which == 'selfEmployed': print('self employed') print() Serializing/Deserializing -------------- Files ~~~~~~~~~~~~~~~~~~~~~~~~~~ As shown in the examples above, there is file serialization with `write()`:: addresses = addressbook_capnp.AddressBook.new_message() ... f = open('example.bin', 'w') addresses.write(f) And similarly for reading:: f = open('example.bin') addresses = addressbook_capnp.AddressBook.read(f) Dictionaries ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There is a convenience method for converting Cap'n Proto messages to a dictionary. This works for both Builder and Reader type messages:: alice.to_dict() There is also a convenience method for reading for reading a dict in and building a Builder message out of it. This the inverse of the above:: my_dict = {'name' : 'alice'} alice = addressbook.Person.from_dict(my_dict) Byte Strings/Buffers ~~~~~~~~~~~~~~~~~~~~~ There is serialization to a byte string available:: encoded_message = alice.to_bytes() And a corresponding from_bytes function:: alice = addressbook.Person.from_bytes(encoded_message) Full Example ------------------ Here is a full example reproduced from `examples/example.py `_:: from __future__ import print_function import os import capnp import addressbook_capnp def writeAddressBook(file): addresses = addressbook_capnp.AddressBook.new_message() people = addresses.init('people', 2) alice = people[0] alice.id = 123 alice.name = 'Alice' alice.email = 'alice@example.com' alicePhones = alice.init('phones', 1) alicePhones[0].number = "555-1212" alicePhones[0].type = 'mobile' alice.employment.school = "MIT" bob = people[1] bob.id = 456 bob.name = 'Bob' bob.email = 'bob@example.com' bobPhones = bob.init('phones', 2) bobPhones[0].number = "555-4567" bobPhones[0].type = 'home' bobPhones[1].number = "555-7654" bobPhones[1].type = 'work' bob.employment.unemployed = None addresses.write(file) def printAddressBook(file): addresses = addressbook_capnp.AddressBook.read(file) for person in addresses.people: print(person.name, ':', person.email) for phone in person.phones: print(phone.type, ':', phone.number) which = person.employment.which() print(which) if which == 'unemployed': print('unemployed') elif which == 'employer': print('employer:', person.employment.employer) elif which == 'school': print('student at:', person.employment.school) elif which == 'selfEmployed': print('self employed') print() if __name__ == '__main__': f = open('example', 'w') writeAddressBook(f) f = open('example', 'r') printAddressBook(f)