0001"""Refactored 'safe reference from dispatcher.py"""
0002
0003import weakref
0004import traceback
0005
0006
0007def safe_ref(target, on_delete=None):
0008    """Return a *safe* weak reference to a callable target.
0009
0010    - ``target``: The object to be weakly referenced, if it's a bound
0011      method reference, will create a BoundMethodWeakref, otherwise
0012      creates a simple weakref.
0013        
0014    - ``on_delete``: If provided, will have a hard reference stored to
0015      the callable to be called after the safe reference goes out of
0016      scope with the reference object, (either a weakref or a
0017      BoundMethodWeakref) as argument.
0018    """
0019    if hasattr(target, 'im_self'):
0020        if target.im_self is not None:
0021            # Turn a bound method into a BoundMethodWeakref instance.
0022            # Keep track of these instances for lookup by disconnect().
0023            assert hasattr(target, 'im_func'), (
0024                "safe_ref target %r has im_self, but no im_func, "
0025                "don't know how to create reference"
0026                % target
0027                )
0028            reference = BoundMethodWeakref(target=target, on_delete=on_delete)
0029            return reference
0030    if callable(on_delete):
0031        return weakref.ref(target, on_delete)
0032    else:
0033        return weakref.ref(target)
0034
0035
0036class BoundMethodWeakref(object):
0037    """'Safe' and reusable weak references to instance methods.
0038
0039    BoundMethodWeakref objects provide a mechanism for referencing a
0040    bound method without requiring that the method object itself
0041    (which is normally a transient object) is kept alive.  Instead,
0042    the BoundMethodWeakref object keeps weak references to both the
0043    object and the function which together define the instance method.
0044
0045    Attributes:
0046    
0047    - ``key``: The identity key for the reference, calculated by the
0048      class's calculate_key method applied to the target instance method.
0049
0050    - ``deletion_methods``: Sequence of callable objects taking single
0051      argument, a reference to this object which will be called when
0052      *either* the target object or target function is garbage
0053      collected (i.e. when this object becomes invalid).  These are
0054      specified as the on_delete parameters of safe_ref calls.
0055
0056    - ``weak_self``: Weak reference to the target object.
0057
0058    - ``weak_func``: Weak reference to the target function.
0059
0060    Class Attributes:
0061        
0062    - ``_all_instances``: Class attribute pointing to all live
0063      BoundMethodWeakref objects indexed by the class's
0064      calculate_key(target) method applied to the target objects.
0065      This weak value dictionary is used to short-circuit creation so
0066      that multiple references to the same (object, function) pair
0067      produce the same BoundMethodWeakref instance.
0068    """
0069
0070    _all_instances = weakref.WeakValueDictionary()
0071
0072    def __new__(cls, target, on_delete=None, *arguments, **named):
0073        """Create new instance or return current instance.
0074
0075        Basically this method of construction allows us to
0076        short-circuit creation of references to already- referenced
0077        instance methods.  The key corresponding to the target is
0078        calculated, and if there is already an existing reference,
0079        that is returned, with its deletion_methods attribute updated.
0080        Otherwise the new instance is created and registered in the
0081        table of already-referenced methods.
0082        """
0083        key = cls.calculate_key(target)
0084        current = cls._all_instances.get(key)
0085        if current is not None:
0086            current.deletion_methods.append(on_delete)
0087            return current
0088        else:
0089            base = super(BoundMethodWeakref, cls).__new__(cls)
0090            cls._all_instances[key] = base
0091            base.__init__(target, on_delete, *arguments, **named)
0092            return base
0093
0094    def __init__(self, target, on_delete=None):
0095        """Return a weak-reference-like instance for a bound method.
0096
0097        - ``target``: The instance-method target for the weak reference,
0098          must have im_self and im_func attributes and be
0099          reconstructable via the following, which is true of built-in
0100          instance methods::
0101            
0102            target.im_func.__get__( target.im_self )
0103
0104        - ``on_delete``: Optional callback which will be called when
0105          this weak reference ceases to be valid (i.e. either the
0106          object or the function is garbage collected).  Should take a
0107          single argument, which will be passed a pointer to this
0108          object.
0109        """
0110        def remove(weak, self=self):
0111            """Set self.isDead to True when method or instance is destroyed."""
0112            methods = self.deletion_methods[:]
0113            del self.deletion_methods[:]
0114            try:
0115                del self.__class__._all_instances[self.key]
0116            except KeyError:
0117                pass
0118            for function in methods:
0119                try:
0120                    if callable(function):
0121                        function(self)
0122                except Exception:
0123                    try:
0124                        traceback.print_exc()
0125                    except AttributeError, e:
0126                        print ('Exception during saferef %s '
0127                               'cleanup function %s: %s' % (self, function, e))
0128        self.deletion_methods = [on_delete]
0129        self.key = self.calculate_key(target)
0130        self.weak_self = weakref.ref(target.im_self, remove)
0131        self.weak_func = weakref.ref(target.im_func, remove)
0132        self.self_name = str(target.im_self)
0133        self.func_name = str(target.im_func.__name__)
0134
0135    def calculate_key(cls, target):
0136        """Calculate the reference key for this reference.
0137
0138        Currently this is a two-tuple of the id()'s of the target
0139        object and the target function respectively.
0140        """
0141        return (id(target.im_self), id(target.im_func))
0142    calculate_key = classmethod(calculate_key)
0143
0144    def __str__(self):
0145        """Give a friendly representation of the object."""
0146        return "%s(%s.%s)" % (
0147            self.__class__.__name__,
0148            self.self_name,
0149            self.func_name,
0150            )
0151
0152    __repr__ = __str__
0153
0154    def __nonzero__(self):
0155        """Whether we are still a valid reference."""
0156        return self() is not None
0157
0158    def __cmp__(self, other):
0159        """Compare with another reference."""
0160        if not isinstance(other, self.__class__):
0161            return cmp(self.__class__, type(other))
0162        return cmp(self.key, other.key)
0163
0164    def __call__(self):
0165        """Return a strong reference to the bound method.
0166
0167        If the target cannot be retrieved, then will return None,
0168        otherwise returns a bound instance method for our object and
0169        function.
0170
0171        Note: You may call this method any number of times, as it does
0172        not invalidate the reference.
0173        """
0174        target = self.weak_self()
0175        if target is not None:
0176            function = self.weak_func()
0177            if function is not None:
0178                return function.__get__(target)
0179        return None