Add support for protobuf API definitions
diff --git a/experiments/addressbook.proto b/experiments/addressbook.proto
new file mode 100644
index 0000000..bfdceea
--- /dev/null
+++ b/experiments/addressbook.proto
@@ -0,0 +1,33 @@
+// See README.txt for information and build instructions.
+
+syntax = "proto3";
+
+package tutorial;
+
+option java_package = "com.example.tutorial";
+option java_outer_classname = "AddressBookProtos";
+option csharp_namespace = "Google.Protobuf.Examples.AddressBook";
+
+message Person {
+ string name = 1;
+ int32 id = 2; // Unique ID number for this person.
+ string email = 3;
+
+ enum PhoneType {
+ MOBILE = 0;
+ HOME = 1;
+ WORK = 2;
+ }
+
+ message PhoneNumber {
+ string number = 1;
+ PhoneType type = 2;
+ }
+
+ repeated PhoneNumber phones = 4;
+}
+
+// Our address book file is just one of these.
+message AddressBook {
+ repeated Person people = 1;
+}
diff --git a/experiments/addressbook_pb2.py b/experiments/addressbook_pb2.py
new file mode 100644
index 0000000..e545fb3
--- /dev/null
+++ b/experiments/addressbook_pb2.py
@@ -0,0 +1,206 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: addressbook.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name='addressbook.proto',
+ package='tutorial',
+ syntax='proto3',
+ serialized_pb=b'\n\x11\x61\x64\x64ressbook.proto\x12\x08tutorial\"\xd5\x01\n\x06Person\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\x05\x12\r\n\x05\x65mail\x18\x03 \x01(\t\x12,\n\x06phones\x18\x04 \x03(\x0b\x32\x1c.tutorial.Person.PhoneNumber\x1aG\n\x0bPhoneNumber\x12\x0e\n\x06number\x18\x01 \x01(\t\x12(\n\x04type\x18\x02 \x01(\x0e\x32\x1a.tutorial.Person.PhoneType\"+\n\tPhoneType\x12\n\n\x06MOBILE\x10\x00\x12\x08\n\x04HOME\x10\x01\x12\x08\n\x04WORK\x10\x02\"/\n\x0b\x41\x64\x64ressBook\x12 \n\x06people\x18\x01 \x03(\x0b\x32\x10.tutorial.PersonBP\n\x14\x63om.example.tutorialB\x11\x41\x64\x64ressBookProtos\xaa\x02$Google.Protobuf.Examples.AddressBookb\x06proto3'
+)
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+
+
+_PERSON_PHONETYPE = _descriptor.EnumDescriptor(
+ name='PhoneType',
+ full_name='tutorial.Person.PhoneType',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='MOBILE', index=0, number=0,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='HOME', index=1, number=1,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='WORK', index=2, number=2,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=202,
+ serialized_end=245,
+)
+_sym_db.RegisterEnumDescriptor(_PERSON_PHONETYPE)
+
+
+_PERSON_PHONENUMBER = _descriptor.Descriptor(
+ name='PhoneNumber',
+ full_name='tutorial.Person.PhoneNumber',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='number', full_name='tutorial.Person.PhoneNumber.number', index=0,
+ number=1, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=b"".decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='type', full_name='tutorial.Person.PhoneNumber.type', index=1,
+ number=2, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=129,
+ serialized_end=200,
+)
+
+_PERSON = _descriptor.Descriptor(
+ name='Person',
+ full_name='tutorial.Person',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='name', full_name='tutorial.Person.name', index=0,
+ number=1, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=b"".decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='id', full_name='tutorial.Person.id', index=1,
+ number=2, type=5, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='email', full_name='tutorial.Person.email', index=2,
+ number=3, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=b"".decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='phones', full_name='tutorial.Person.phones', index=3,
+ number=4, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[_PERSON_PHONENUMBER, ],
+ enum_types=[
+ _PERSON_PHONETYPE,
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=32,
+ serialized_end=245,
+)
+
+
+_ADDRESSBOOK = _descriptor.Descriptor(
+ name='AddressBook',
+ full_name='tutorial.AddressBook',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='people', full_name='tutorial.AddressBook.people', index=0,
+ number=1, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=247,
+ serialized_end=294,
+)
+
+_PERSON_PHONENUMBER.fields_by_name['type'].enum_type = _PERSON_PHONETYPE
+_PERSON_PHONENUMBER.containing_type = _PERSON
+_PERSON.fields_by_name['phones'].message_type = _PERSON_PHONENUMBER
+_PERSON_PHONETYPE.containing_type = _PERSON
+_ADDRESSBOOK.fields_by_name['people'].message_type = _PERSON
+DESCRIPTOR.message_types_by_name['Person'] = _PERSON
+DESCRIPTOR.message_types_by_name['AddressBook'] = _ADDRESSBOOK
+
+Person = _reflection.GeneratedProtocolMessageType('Person', (_message.Message,), dict(
+
+ PhoneNumber = _reflection.GeneratedProtocolMessageType('PhoneNumber', (_message.Message,), dict(
+ DESCRIPTOR = _PERSON_PHONENUMBER,
+ __module__ = 'addressbook_pb2'
+ # @@protoc_insertion_point(class_scope:tutorial.Person.PhoneNumber)
+ ))
+ ,
+ DESCRIPTOR = _PERSON,
+ __module__ = 'addressbook_pb2'
+ # @@protoc_insertion_point(class_scope:tutorial.Person)
+ ))
+_sym_db.RegisterMessage(Person)
+_sym_db.RegisterMessage(Person.PhoneNumber)
+
+AddressBook = _reflection.GeneratedProtocolMessageType('AddressBook', (_message.Message,), dict(
+ DESCRIPTOR = _ADDRESSBOOK,
+ __module__ = 'addressbook_pb2'
+ # @@protoc_insertion_point(class_scope:tutorial.AddressBook)
+ ))
+_sym_db.RegisterMessage(AddressBook)
+
+
+DESCRIPTOR.has_options = True
+DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), b'\n\024com.example.tutorialB\021AddressBookProtos\252\002$Google.Protobuf.Examples.AddressBook')
+# @@protoc_insertion_point(module_scope)
diff --git a/experiments/encoing_test.py b/experiments/encoing_test.py
new file mode 100644
index 0000000..d7ddbfc
--- /dev/null
+++ b/experiments/encoing_test.py
@@ -0,0 +1,300 @@
+#!/usr/bin/env python
+
+"""
+We use this module to time-test various alternatives for building,
+serializing, and de-serializing objects.
+"""
+
+import time
+from copy import copy, deepcopy
+
+import addressbook_pb2
+from random import randint
+from uuid import uuid4
+from simplejson import dumps, loads, JSONEncoder
+
+
+class ProtoBufs(object):
+
+ FILENAME = 'addressbook.bin.pb'
+
+ @staticmethod
+ def makeAddressBook(n):
+
+ addressbook = addressbook_pb2.AddressBook()
+
+ for i in xrange(n):
+ person = addressbook.people.add()
+ person.id = i
+ person.name = uuid4().get_hex()
+ person.email = person.name + '@abc.com'
+
+ phone1 = person.phones.add()
+ phone1.number = str(randint(1000000000, 7999999999))
+ phone1.type = addressbook_pb2.Person.HOME
+
+ phone2 = person.phones.add()
+ phone2.number = str(randint(1000000000, 7999999999))
+ phone2.type = addressbook_pb2.Person.MOBILE
+
+ return addressbook
+
+ @staticmethod
+ def serialize(addressbook):
+ return addressbook.SerializeToString()
+
+ @staticmethod
+ def deserialize(str):
+ addressbook = addressbook_pb2.AddressBook()
+ addressbook.ParseFromString(str)
+ return addressbook
+
+ @staticmethod
+ def diff(addressbook1, addressbook2):
+ assert isinstance(addressbook1, addressbook_pb2.AddressBook)
+ assert isinstance(addressbook2, addressbook_pb2.AddressBook)
+ assert addressbook1 == addressbook2
+
+
+class JsonWithPythonData(object):
+
+ FILENAME = 'addressbook.native.json'
+
+ @staticmethod
+ def makeAddressBook(n):
+
+ addressbook = dict(people=[])
+ people = addressbook['people']
+
+ for i in xrange(n):
+ name = uuid4().get_hex()
+ people.append(dict(
+ id=i,
+ name=name,
+ email=name + '@abc.com',
+ phones=[
+ dict(
+ number=str(randint(1000000000, 7999999999)),
+ type='HOME'
+ ),
+ dict(
+ number=str(randint(1000000000, 7999999999)),
+ type='MOBILE'
+ )
+ ]
+ ))
+
+ return addressbook
+
+ @staticmethod
+ def serialize(addressbook):
+ return dumps(addressbook)
+
+ @staticmethod
+ def deserialize(str):
+ return loads(str)
+
+ @staticmethod
+ def diff(addressbook1, addressbook2):
+ assert isinstance(addressbook1, dict)
+ assert isinstance(addressbook2, dict)
+ assert addressbook1 == addressbook2
+
+
+class JsonWithPythonClasses(object):
+
+ class JsonEncoded(object):
+ def __eq__(self, other):
+ return self.__dict__ == other.__dict__
+
+ class Phone(JsonEncoded):
+
+ def __init__(self, number, type):
+ self.number = number
+ self.type = type
+
+ @property
+ def number(self):
+ return self._type
+ @number.setter
+ def number(self, number):
+ assert isinstance(number, str)
+ self._number = number
+
+ @property
+ def type(self):
+ return self._type
+ @number.setter
+ def type(self, type):
+ assert isinstance(type, str)
+ self._type = type
+
+ class Person(JsonEncoded):
+
+ def __init__(self, id, name, email, phones=list()):
+ self.id = id
+ self.name = name
+ self.email = email
+ self.phones = phones
+
+ @property
+ def id(self):
+ return self._id
+ @id.setter
+ def id(self, id):
+ assert isinstance(id, int)
+ self._id = id
+
+ @property
+ def name(self):
+ return self._name
+ @name.setter
+ def name(self, name):
+ assert isinstance(name, str)
+ self._name = name
+
+ @property
+ def email(self):
+ return self._email
+ @email.setter
+ def email(self, email):
+ assert isinstance(email, str)
+ self._email = email
+
+ @property
+ def phones(self):
+ return self._phones
+ @phones.setter
+ def phones(self, phones):
+ assert isinstance(phones, list)
+ self._phones = phones
+
+
+ class AddressBook(JsonEncoded):
+
+ def __init__(self, people=list()):
+ self.people = people
+
+ @property
+ def people(self):
+ return self._people
+ @people.setter
+ def people(self, people):
+ assert isinstance(people, list)
+ self._people = people
+
+ cls_map = {
+ Phone.__name__: Phone,
+ Person.__name__: Person,
+ AddressBook.__name__: AddressBook
+ }
+
+ class CustomEncoder(JSONEncoder):
+ def default(self, o):
+ if isinstance(o, JsonWithPythonClasses.JsonEncoded):
+ d = dict((k.strip('_'), v) for k, v in o.__dict__.iteritems())
+ d['_class'] = o.__class__.__name__
+ return d
+ return super(self).default(o)
+
+ @staticmethod
+ def as_object(dct):
+ if '_class' in dct and dct['_class'] in JsonWithPythonClasses.cls_map:
+ kw = deepcopy(dct)
+ cls_name = kw.pop('_class')
+ cls = JsonWithPythonClasses.cls_map[cls_name]
+ return cls(**kw)
+ return dct
+
+ FILENAME = 'addressbook.class.json'
+
+ @staticmethod
+ def makeAddressBook(n):
+
+ addressbook = JsonWithPythonClasses.AddressBook()
+ people = addressbook.people
+
+ for i in xrange(n):
+ name = uuid4().get_hex()
+ person = JsonWithPythonClasses.Person(
+ id=i,
+ name=name,
+ email=name + '@abc.com',
+ phones=[
+ JsonWithPythonClasses.Phone(
+ number=str(randint(1000000000, 7999999999)),
+ type='HOME'
+ ),
+ JsonWithPythonClasses.Phone(
+ number=str(randint(1000000000, 7999999999)),
+ type='MOBILE'
+ )
+ ]
+ )
+ people.append(person)
+
+ return addressbook
+
+ @staticmethod
+ def serialize(addressbook):
+ return dumps(addressbook, cls=JsonWithPythonClasses.CustomEncoder)
+
+ @staticmethod
+ def deserialize(str):
+ return loads(str, object_hook=JsonWithPythonClasses.as_object)
+
+ @staticmethod
+ def diff(addressbook1, addressbook2):
+ assert isinstance(addressbook1, JsonWithPythonClasses.AddressBook)
+ assert isinstance(addressbook2, JsonWithPythonClasses.AddressBook)
+ assert len(addressbook1.people) == len(addressbook2.people)
+ for i in xrange(len(addressbook1.people)):
+ assert addressbook1.people[i] == addressbook2.people[i], \
+ '\n%s\n!=\n%s' % (addressbook1.people[i].__dict__,
+ addressbook2.people[i].__dict__)
+ assert addressbook1 == addressbook2
+
+
+def timetest(cls, n):
+
+ generator = cls()
+
+ # generate addressbook
+
+ t = time.time()
+ addressbook = generator.makeAddressBook(n)
+ t_make = time.time() - t
+
+ # serialize addressbook to string and save it to file
+ t = time.time()
+ str = generator.serialize(addressbook)
+ t_serialize = time.time() - t
+ size = len(str)
+
+ with open(cls.FILENAME, 'wb') as f:
+ f.write(str)
+
+ # deserialize by reading it back from file
+ t = time.time()
+ addressbook2 = generator.deserialize(str)
+ t_deserialize = time.time() - t
+
+ generator.diff(addressbook, addressbook2)
+
+ print "%-30s %12lf %12lf %12lf %10d" % \
+ (cls.__name__,
+ 1e6 * t_make / n,
+ 1e6 * t_serialize / n,
+ 1e6 * t_deserialize / n,
+ size / n)
+
+
+def run_tests(n):
+ print "%-30s %12s %12s %12s %10s" % \
+ ('Method', 'Gen [us]', 'Ser [us]', 'Des [us]', 'Size [bytes]')
+ timetest(ProtoBufs, n)
+ timetest(JsonWithPythonData, n)
+ timetest(JsonWithPythonClasses, n)
+
+
+run_tests(10000)