import unittest
from copy import copy
class Proto(object):
""" A prototype object.
"""
def __init__(self, wrapped=None, proto=None):
self._wrapped = wrapped
self.proto = proto
self._protos = [proto] # +/- like __mro__
try:
# instantialize the state of the clone
self.init(self)
except AttributeError:
pass
def __getattr__(self, name):
#### diferential inheritance ####
# __getattr__ is only called
# when the attribute is not found
try:
value = getattr(self._wrapped, name)
except AttributeError:
for proto in self._protos:
try:
value = getattr(proto, name)
break
except AttributeError:
continue
else:
raise AttributeError('Object %r have no %s attribute.'
% (self, name))
return value
def __repr__(self):
return '<object %r>' % self._wrapped
@property
def protos(self):
for proto in self._protos:
if proto is not None:
yield proto
# ask the protos for its protos...
for super_proto in proto.protos:
if super_proto is not None:
yield super_proto
def clone(self):
""" Create a new object, based on this one.
"""
wrapped = self._wrapped
try:
# if the object is immutable,
# no problem sharing it
hash(wrapped)
return Proto(wrapped, self)
except TypeError:
return Proto(copy(wrapped), self)
def append_proto(self, proto):
self._protos.append(proto)
def prepend_proto(self, proto):
self._protos.insert(0, proto)
def remove_proto(self, proto):
self._protos.remove(proto)
class TestProto(unittest.TestCase):
def test_wraps(self):
obj = Proto('hello')
self.assertFalse(obj.isupper())
self.assertTrue(obj.islower())
self.assertTrue(obj.isalpha())
c1 = obj.clone()
self.assertFalse(c1.isdigit())
self.assertFalse(c1.isspace())
self.assertEqual(c1.title(), 'Hello')
self.assertRaises(AttributeError, getattr, c1, 'append')
def test_clone_and_protos(self):
obj = Proto(42)
self.assertEqual(repr(obj), '<object 42>')
self.assertEqual(None, obj.proto)
clone = obj.clone()
proto_a = clone.proto
proto_b = list(clone.protos).pop()
self.assertEqual(proto_a, proto_b)
self.assertNotEqual(obj.proto, proto_a)
self.assertNotEqual(obj.proto, proto_b)
self.assertEqual(list(clone.protos), [obj])
c2 = clone.clone()
c3 = c2.clone()
c4 = c3.clone()
self.assertEqual(list(c2.protos), [clone, obj])
self.assertEqual(list(c3.protos), [c2, clone, obj])
self.assertEqual(list(c4.protos), [c3, c2, clone, obj])
def test_init(self):
Movie = Proto("Movie")
def init(self):
self.cast = []
Movie.init = init
some_movie = Movie.clone()
self.assertEqual(some_movie.cast, [])
some_movie.cast.append('Some Actor')
another_movie = Movie.clone()
self.assertEqual(another_movie.cast, [])
self.assertNotEqual(another_movie.cast, some_movie.cast)
def test_inheritance(self):
obj = Proto('foo')
obj.bar = 'baz'
c1 = obj.clone()
self.assertEqual(obj.bar, c1.bar)
self.assertEqual(c1.bar, 'baz')
obj.baz = 'foo'
self.assertEqual(obj.baz, c1.baz)
self.assertEqual(c1.baz, 'foo')
obj.quux = 'foobar'
self.assertEqual(obj.quux, c1.quux)
self.assertEqual(c1.quux, 'foobar')
c2 = c1.clone()
c3 = c2.clone()
self.assertEqual(obj.bar, c2.bar)
self.assertEqual(c2.bar, 'baz')
self.assertEqual(obj.quux, c2.quux)
self.assertEqual(c2.quux, 'foobar')
self.assertEqual(obj.bar, c3.bar)
self.assertEqual(c3.bar, 'baz')
self.assertEqual(obj.quux, c3.quux)
self.assertEqual(c3.quux, 'foobar')
self.assertEqual(c2.bar, c3.bar)
self.assertEqual(c1.bar, c2.bar)
self.assertEqual(c1.bar, c3.bar)
self.assertEqual(c2.quux, c3.quux)
self.assertEqual(c1.quux, c2.quux)
self.assertEqual(c1.quux, c3.quux)
obj.bar = 42
self.assertEqual(obj.bar, c3.bar)
self.assertEqual(c3.bar, 42)
obj2 = Proto('bar')
c3.prepend_proto(obj2)
obj2.baz = 'hello'
self.assertEqual(c3.bar, 42)
self.assertEqual(c3.baz, 'hello')
obj3 = Proto('baz')
c3.append_proto(obj3)
obj3.quuuux = 'quuuux'
obj3.bar = 'quuuux'
self.assertEqual(c3.bar, 42)
self.assertEqual(c3.baz, 'hello')
self.assertEqual(c3.quuuux, 'quuuux')
c3.remove_proto(obj3)
self.assertRaises(AttributeError, getattr, obj3, 'quuux')
if __name__ == '__main__':
unittest.main()