"""Weak reference support for Python.

This module is an implementation of PEP 205:

http://python.sourceforge.net/peps/pep-0205.html
"""

import UserDict

from _weakref import \
     getweakrefcount, \
     getweakrefs, \
     ref, \
     proxy, \
     ReferenceError, \
     CallableProxyType, \
     ProxyType, \
     ReferenceType

ProxyTypes = (ProxyType, CallableProxyType)

__all__ = ["ref", "proxy", "getweakrefcount", "getweakrefs",
           "WeakKeyDictionary", "ReferenceType", "ProxyType",
           "CallableProxyType", "ProxyTypes", "WeakValueDictionary"]

class WeakValueDictionary(UserDict.UserDict):

    # We inherit the constructor without worrying about the input
    # dictionary; since it uses our .update() method, we get the right
    # checks (if the other dictionary is a WeakValueDictionary,
    # objects are unwrapped on the way out, and we always wrap on the
    # way in).

    def __getitem__(self, key):
        o = self.data.get(key)()
        if o is None:
            raise KeyError, key
        else:
            return o

    def __repr__(self):
        return "<WeakValueDictionary at %s>" % id(self)

    def __setitem__(self, key, value):
        def remove(o, data=self.data, key=key):
            del data[key]
        self.data[key] = ref(value, remove)

    def copy(self):
        new = WeakValueDictionary()
        for key, ref in self.data.items():
            o = ref()
            if o is not None:
                new[key] = o
        return new

    def get(self, key, default=None):
        try:
            ref = self.data[key]
        except KeyError:
            return default
        else:
            o = ref()
            if o is None:
                # This should only happen
                return default
            else:
                return o

    def items(self):
        L = []
        for key, ref in self.data.items():
            o = ref()
            if o is not None:
                L.append((key, o))
        return L

    def popitem(self):
        while 1:
            key, ref = self.data.popitem()
            o = ref()
            if o is not None:
                return key, o

    def setdefault(self, key, default):
        try:
            ref = self.data[key]
        except KeyError:
            def remove(o, data=self.data, key=key):
                del data[key]
            ref = ref(default, remove)
            self.data[key] = ref
            return default
        else:
            return ref()

    def update(self, dict):
        d = self.data
        L = []
        for key, o in dict.items():
            def remove(o, data=d, key=key):
                del data[key]
            L.append((key, ref(o, remove)))
        for key, r in L:
            d[key] = r

    def values(self):
        L = []
        for ref in self.data.values():
            o = ref()
            if o is not None:
                L.append(o)
        return L


class WeakKeyDictionary(UserDict.UserDict):

    def __init__(self, dict=None):
        self.data = {}
        if dict is not None: self.update(dict)
        def remove(k, data=self.data):
            del data[k]
        self._remove = remove

    def __getitem__(self, key):
        return self.data[ref(key)]

    def __repr__(self):
        return "<WeakKeyDictionary at %s>" % id(self)

    def __setitem__(self, key, value):
        self.data[ref(key, self._remove)] = value

    def copy(self):
        new = WeakKeyDictionary()
        for key, value in self.data.items():
            o = key()
            if o is not None:
                new[o] = value
        return new

    def get(self, key, default=None):
        return self.data.get(ref(key),default)

    def has_key(self, key):
        return self.data.has_key(ref(key))

    def items(self):
        L = []
        for key, value in self.data.items():
            o = key()
            if o is not None:
                L.append((o, value))
        return L

    def keys(self):
        L = []
        for ref in self.data.keys():
            o = ref()
            if o is not None:
                L.append(o)
        return L

    def popitem(self):
        while 1:
            key, value = self.data.popitem()
            o = key()
            if o is not None:
                return o, value

    def setdefault(self, key, default):
        return self.data.setdefault(ref(key, self._remove),default)

    def update(self, dict):
        d = self.data
        L = []
        for key, value in dict.items():
            L.append((ref(key, self._remove), value))
        for key, r in L:
            d[key] = r

# no longer needed
del UserDict
