
_borrowed = {}

def borrow(C, slotgather=lambda _slots: None):
    """
    Use implementation, but not identity or inheritance, from a class.  This is
    useful when you want to avoid mucking with class metadata - in particular,
    if you want to have a class which has no slots (and the semantics
    associated with that, i.e. no unintended attributes) which inherits from
    old-style and/or potentially __dict__-ful mixins.
    """
    if C in _borrowed:
        return _borrowed[C]
    if C.__name__.endswith("(Borrowed)"):
        slotgather(C.___slots___)
        return C
    D = dict(C.__dict__)
    oldslots = D.get('__slots__', ())
    slotgather(oldslots)
    for oldslot in oldslots:
        # member_descriptor let's hope
        del D[oldslot]
    D['__slots__'] = ()
    allslots = []

    def gatherforme(stuff):
        slotgather(stuff)
        allslots.extend(stuff)

    basetuple = []

    for base in C.__bases__:
        basetuple.append(borrow(base, gatherforme))

    allslots = dict.fromkeys(allslots).keys()
    D['___slots___'] = allslots
    new = type(C.__name__+"(Borrowed)", tuple(basetuple), D)
    _borrowed[C] = new
    return new

class SlotMetaMachine(type):
    def __new__(meta, name, bases, dictionary):
        slots = ['__weakref__'] + list(
            meta.determineSchema.im_func(meta, dictionary))
        if bases:
            borrowedBases = tuple(borrow(x, slots.extend) for x in bases)
        else:
            borrowedBases = ()
        slots = dict.fromkeys(slots).keys() # uniquify

        fin = []
        for slot in slots:
            for base in borrowedBases:
                if hasattr(base, slot):
                    break
            else:
                fin.append(slot)
        slots = fin
        dictionary['__slots__'] = slots
        nt = type.__new__(meta, name, borrowedBases, dictionary)
        if nt.__dictoffset__:
            raise AssertionError(
                "SlotMachine with __dict__ (this should be impossible)")
        return nt

    def determineSchema(meta, dictionary):
        if '__slots__' in dictionary:
            raise AssertionError(
                "When using SlotMachine, specify 'slots' not '__slots__'")
        return dictionary.get("slots", [])

class Attribute(object):
    def __init__(self, doc=''):
        self.doc = doc

    def requiredSlots(self, name):
        self.name = name
        yield name

class SetOnce(Attribute):
    def requiredSlots(self, name):
        self.name = name
        t = self.trueattr = ('_' + self.name)
        yield t

    def __set__(self, iself, value):
        if not hasattr(iself, self.trueattr):
            setattr(iself, self.trueattr, value)
        else:
            raise AttributeError('%s.%s may only be set once' % (
                    type(iself).__name__, self.name))

    def __get__(self, iself, type=None):
        if type is not None:
            return self
        return getattr(iself, self.trueattr)

class SchemaMetaMachine(SlotMetaMachine):
    def __new__(meta, name, bases, dictionary):
        return SlotMetaMachine.__new__(meta, name, bases, dictionary)

    def determineSchema(meta, dictionary):
        dictionary['__attributes__'] = {}
        for k, v in dictionary.items():
            if isinstance(v, Attribute):
                v.name = k
                for slot in v.requiredSlots(k):
                    if slot == v.name:
                        del dictionary[k]
                    yield slot

class SchemaMachine:
    __metaclass__ = SchemaMetaMachine

class SlotMachine:
    __metaclass__ = SlotMetaMachine



#### Examples Follow ####



class B(SchemaMachine):
    test = Attribute()
    initialized = SetOnce()
    other = SetOnce()


class A(SlotMachine):
    slots = ['a', 'initialized']

class C(object):
    __slots__ = ['a', 'b',
                 'c', 'initialized']

class D:
    def activate(self):
        self.initialized = 1
        self.test = 2
        self.a = 3
        self.b = 4
        self.c = 5


class X(B, A, C, D):
    pass


def test():
    b = B()

    b.test = 1
    b.test = 2
    b.initialized = 1
    try:
        b.initialized = 2
    except AttributeError:
        pass
    else:
        raise RuntimeError()

    x = X()
    print X.__dict__
    x.activate()
    try:
        x.initialized = 2
    except AttributeError:
        pass
    else:
        raise RuntimeError()

test()
