import unittest import collections from . import InMemoryDatabase class DictProxy(collections.MutableMapping): def __init__(self): self.store = {} def __len__(self): return len(self.store) def __iter__(self): return iter(self.store) def __getitem__(self, key): return self.store[key] def __setitem__(self, key, new_value): self.store[key] = new_value def __delitem__(self, key): del self.store[key] class PythonTest(unittest.TestCase): def test_accessor_generation(self): class AccessorsDict(DictProxy): def __setitem__(self, key, new_value): if not callable(new_value): self.store["get_" + key] = lambda self: getattr(self, key) super().__setitem__(key, new_value) class AccessorsMetaclass(type): @classmethod def __prepare__(metacls, name, bases, **kwds): return AccessorsDict() def __new__(cls, name, bases, namespace, **kwds): return super().__new__(cls, name, bases, namespace.store) class Point(object, metaclass=AccessorsMetaclass): x = 0 y = 0 def distance(self): import math return math.sqrt(pow(self.get_x(), 2) + pow(self.get_y(), 2)) point = Point() point.x = 3 point.y = 4 assert point.get_x() == 3 assert point.get_y() == 4 assert point.distance() == 5 def test_trace_method_calls(self): methods_called = [] class TraceDict(DictProxy): def __setitem__(self, key, new_value): if callable(new_value): def trace_wrapper(self, *args, **kwargs): methods_called.append(new_value.__name__) return new_value(self, *args, **kwargs) to_set = trace_wrapper else: to_set = new_value super().__setitem__(key, to_set) class TraceClass(type): @classmethod def __prepare__(metacls, name, bases, **kwds): return TraceDict() def __new__(cls, name, bases, namespace, **kwds): return super().__new__(cls, name, bases, namespace.store) class Point(object, metaclass=TraceClass): def get_x(self): return self.x def get_y(self): return self.y point = Point() point.x = 2 point.y = 3 assert methods_called == [] assert point.get_x() == 2 assert methods_called == ['get_x'] assert point.get_y() == 3 assert methods_called == ['get_x', 'get_y'] def test_db_backed_object(self): class DatabaseDict(DictProxy): def __init__(self, db): super().__init__() self.store["db"] = db def __getattr__(self, name): db_key = str(hash(self)) + ":" + name return self.db.lookup(db_key) self.store["__getattr__"] = __getattr__ def __setattr__(self, name, new_value): db_key = str(hash(self)) + ":" + name self.db.insert(db_key, new_value) self.store["__setattr__"] = __setattr__ class DatabaseBackedClass(type): @classmethod def __prepare__(metacls, name, bases, **kwds): return DatabaseDict(kwds["db"]) def __new__(cls, name, bases, namespace, **kwds): return super().__new__(cls, name, bases, namespace.store) def __init__(cls, name, bases, namespace, **kwds): super().__init__(name, bases, namespace) class Point(object, metaclass=DatabaseBackedClass, db=InMemoryDatabase()): def get_x(self): return self.x def get_y(self): return self.y def set_x(self, new_value): self.x = new_value point = Point() point.x = 3 point.y = 7 assert point.get_x() == 3 assert point.get_y() == 7 assert point.__dict__ == {} point.set_x(12) assert point.get_x() == 12 assert point.get_y() == 7 assert point.__dict__ == {} assert len(Point.db.store) == 2 point2 = Point() point2.x = 123 point2.y = -5 assert point.get_x() == 12 assert point.get_y() == 7 assert point.__dict__ == {} assert point2.get_x() == 123 assert point2.get_y() == -5 assert point2.__dict__ == {} assert len(Point.db.store) == 4