1# extracted from Louie, http://pylouie.org/
2# updated for Python 3
3#
4# Copyright (c) 2006 Patrick K. O'Brien, Mike C. Fletcher,
5#                    Matthew R. Scott
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions are
9# met:
10#
11#     * Redistributions of source code must retain the above copyright
12#       notice, this list of conditions and the following disclaimer.
13#
14#     * Redistributions in binary form must reproduce the above
15#       copyright notice, this list of conditions and the following
16#       disclaimer in the documentation and/or other materials provided
17#       with the distribution.
18#
19#     * Neither the name of the <ORGANIZATION> nor the names of its
20#       contributors may be used to endorse or promote products derived
21#       from this software without specific prior written permission.
22#
23# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34#
35"""Refactored 'safe reference from dispatcher.py"""
36
37import operator
38import sys
39import traceback
40import weakref
41
42
43try:
44    callable
45except NameError:
46    def callable(object):
47        return hasattr(object, '__call__')
48
49
50if sys.version_info < (3,):
51    get_self = operator.attrgetter('im_self')
52    get_func = operator.attrgetter('im_func')
53else:
54    get_self = operator.attrgetter('__self__')
55    get_func = operator.attrgetter('__func__')
56
57
58def safe_ref(target, on_delete=None):
59    """Return a *safe* weak reference to a callable target.
60
61    - ``target``: The object to be weakly referenced, if it's a bound
62      method reference, will create a BoundMethodWeakref, otherwise
63      creates a simple weakref.
64
65    - ``on_delete``: If provided, will have a hard reference stored to
66      the callable to be called after the safe reference goes out of
67      scope with the reference object, (either a weakref or a
68      BoundMethodWeakref) as argument.
69    """
70    try:
71        im_self = get_self(target)
72    except AttributeError:
73        if callable(on_delete):
74            return weakref.ref(target, on_delete)
75        else:
76            return weakref.ref(target)
77    else:
78        if im_self is not None:
79            # Turn a bound method into a BoundMethodWeakref instance.
80            # Keep track of these instances for lookup by disconnect().
81            assert hasattr(target, 'im_func') or hasattr(target, '__func__'), (
82                "safe_ref target %r has im_self, but no im_func, "
83                "don't know how to create reference" % target)
84            reference = BoundMethodWeakref(target=target, on_delete=on_delete)
85            return reference
86
87
88class BoundMethodWeakref(object):
89    """'Safe' and reusable weak references to instance methods.
90
91    BoundMethodWeakref objects provide a mechanism for referencing a
92    bound method without requiring that the method object itself
93    (which is normally a transient object) is kept alive.  Instead,
94    the BoundMethodWeakref object keeps weak references to both the
95    object and the function which together define the instance method.
96
97    Attributes:
98
99    - ``key``: The identity key for the reference, calculated by the
100      class's calculate_key method applied to the target instance method.
101
102    - ``deletion_methods``: Sequence of callable objects taking single
103      argument, a reference to this object which will be called when
104      *either* the target object or target function is garbage
105      collected (i.e. when this object becomes invalid).  These are
106      specified as the on_delete parameters of safe_ref calls.
107
108    - ``weak_self``: Weak reference to the target object.
109
110    - ``weak_func``: Weak reference to the target function.
111
112    Class Attributes:
113
114    - ``_all_instances``: Class attribute pointing to all live
115      BoundMethodWeakref objects indexed by the class's
116      calculate_key(target) method applied to the target objects.
117      This weak value dictionary is used to short-circuit creation so
118      that multiple references to the same (object, function) pair
119      produce the same BoundMethodWeakref instance.
120    """
121
122    _all_instances = weakref.WeakValueDictionary()
123
124    def __new__(cls, target, on_delete=None, *arguments, **named):
125        """Create new instance or return current instance.
126
127        Basically this method of construction allows us to
128        short-circuit creation of references to already- referenced
129        instance methods.  The key corresponding to the target is
130        calculated, and if there is already an existing reference,
131        that is returned, with its deletion_methods attribute updated.
132        Otherwise the new instance is created and registered in the
133        table of already-referenced methods.
134        """
135        key = cls.calculate_key(target)
136        current = cls._all_instances.get(key)
137        if current is not None:
138            current.deletion_methods.append(on_delete)
139            return current
140        else:
141            base = super(BoundMethodWeakref, cls).__new__(cls)
142            cls._all_instances[key] = base
143            base.__init__(target, on_delete, *arguments, **named)
144            return base
145
146    def __init__(self, target, on_delete=None):
147        """Return a weak-reference-like instance for a bound method.
148
149        - ``target``: The instance-method target for the weak reference,
150          must have im_self and im_func attributes and be
151          reconstructable via the following, which is true of built-in
152          instance methods::
153
154            target.im_func.__get__( target.im_self )
155
156        - ``on_delete``: Optional callback which will be called when
157          this weak reference ceases to be valid (i.e. either the
158          object or the function is garbage collected).  Should take a
159          single argument, which will be passed a pointer to this
160          object.
161        """
162        def remove(weak, self=self):
163            """Set self.isDead to True when method or instance is destroyed."""
164            methods = self.deletion_methods[:]
165            del self.deletion_methods[:]
166            try:
167                del self.__class__._all_instances[self.key]
168            except KeyError:
169                pass
170            for function in methods:
171                try:
172                    if callable(function):
173                        function(self)
174                except Exception:
175                    try:
176                        traceback.print_exc()
177                    except AttributeError:
178                        e = sys.exc_info()[1]
179                        print ('Exception during saferef %s '
180                               'cleanup function %s: %s' % (self, function, e))
181        self.deletion_methods = [on_delete]
182        self.key = self.calculate_key(target)
183        im_self = get_self(target)
184        im_func = get_func(target)
185        self.weak_self = weakref.ref(im_self, remove)
186        self.weak_func = weakref.ref(im_func, remove)
187        self.self_name = str(im_self)
188        self.func_name = str(im_func.__name__)
189
190    def calculate_key(cls, target):
191        """Calculate the reference key for this reference.
192
193        Currently this is a two-tuple of the id()'s of the target
194        object and the target function respectively.
195        """
196        return (id(get_self(target)), id(get_func(target)))
197    calculate_key = classmethod(calculate_key)
198
199    def __str__(self):
200        """Give a friendly representation of the object."""
201        return "%s(%s.%s)" % (
202            self.__class__.__name__,
203            self.self_name,
204            self.func_name,
205            )
206
207    __repr__ = __str__
208
209    def __nonzero__(self):
210        """Whether we are still a valid reference."""
211        return self() is not None
212
213    def __cmp__(self, other):
214        """Compare with another reference."""
215        if not isinstance(other, self.__class__):
216            return cmp(self.__class__, type(other))
217        return cmp(self.key, other.key)
218
219    def __call__(self):
220        """Return a strong reference to the bound method.
221
222        If the target cannot be retrieved, then will return None,
223        otherwise returns a bound instance method for our object and
224        function.
225
226        Note: You may call this method any number of times, as it does
227        not invalidate the reference.
228        """
229        target = self.weak_self()
230        if target is not None:
231            function = self.weak_func()
232            if function is not None:
233                return function.__get__(target)
234        return None
235