Add support for protobuf API definitions
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)