#
#  weak dictionaries
#  Module and documentation by Eric S. Tiedemann <est@hyperreal.org>
#
#  How does this all work?  Well, the extension module weak._weak
#  provides a type of object, the `weak', that simply stores the object
#  it's initialized with without incrementing it's reference count.  The
#  only thing you can do with a weak is to call it, which returns the
#  stored object.
#
#  Weak dictionaries store their values wrapped in weaks in a private
#  dictionary.  They also attach another kind of object provided by
#  weak._weak, the dictsnapper, to the stored object value under an
#  attribute name that's unique to that dictionary and that can't be used
#  as an attribute in normal python code.  dictsnappers are initialized
#  with the dictionary, key and value in question and stores these, but
#  doesn't increment the value's reference count.
#
#  All this means that the references by the weak dictionary and the
#  dictwrapper to the value object aren't counted in it's reference
#  count.  The value object has the only reference to it's dictsnapper,
#  so when the last non-weak reference to the value object goes away and
#  its deallocator is called, the deallocator of the dictsnapper is
#  called and that's what removes the value object's weak object from the
#  weak dictionary.
#
#  The dictionary is also careful to remove the dictsnapper from a value
#  object when the object is removed (via deletion or replacement) from
#  the dictionary.  This helps ensure that no dangling references to the
#  dictionary keep it from being deallocated.
#
#  Note that the weak and dictsnapper *could* be combined into a single
#  structure that determined what situation it was being deleted in by
#  looking at the reference count of the value object.
#

import weak._weak

class dict:
    "A weak dictionary class."
    
    def __init__(self):
        self.__weaks = {}
        self.__magic = ' weak map snapper ' + str(id(self))

    # This ensures that objects that used to be in this weak dict don't
    # retain references to our weak dictionary.
    def __clearitem(self, k):
        oldw = self.__weaks.get(k)
        if oldw:
            magic = self.__magic
            oldv = oldw()
            if hasattr(oldv, self.__magic):
                # NB: the delattr causes the weak to be removed from our dict!
                delattr(oldv, self.__magic)
            else:
                # We could get here if someone stored the same object
                # under multiple keys.
                del self.__weaks[k]

    def __setitem__(self, k, v):
        self.__clearitem(k)
        w = weak._weak.weak(v)
        setattr(v, self.__magic, weak._weak.dictsnapper(self.__weaks, k, v))
        self.__weaks[k] = w
            
    def __len__(self): return len(self.__weaks)
    
    def __getitem__(self, k):
        w = self.__weaks.get(k)
        if w:
            return w()
        else:
            raise KeyError, k

    def get(self, k, default = None):
        w = self.__weaks.get(k)
        if w:
            return w()
        else:
            return default

    def has_key(self, k):
        return self.__weaks.has_key(k)

    def keys(self):
        return self.__weaks.keys()
    
    def __delitem__(self, k):
        if self.__weaks.has_key(k):
            self.__clearitem(k)
        else:
            raise KeyError, k
        
    def clear(self):
        for k in self.__weaks.keys():
            self.__clearitem(k)

    def copy(self):
        m = dict()
        for k in self.keys():
            m[k] = self.__weaks[k]
        return m

    def update(self, b):
        for k, v in b.items(): self[k] = v

    def items(self):
        map(lambda k, self=self: (k, self[k]), self.__weaks.keys())
        
    def values(self):
        map(lambda k, self=self: self[k], self.__weaks.keys())
