• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nocarryr / python-dispatch / 3734659194

pending completion
3734659194

push

github

nocarryr
Pin Sphinx to 4.5.0

707 of 722 relevant lines covered (97.92%)

3.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.52
/pydispatch/utils.py
1
import weakref
4✔
2
from weakref import ref, _remove_dead_weakref
4✔
3
from _weakref import ref
4✔
4
import types
4✔
5
import asyncio
4✔
6

7
def get_method_vars(m):
4✔
8
    f = m.__func__
4✔
9
    obj = m.__self__
4✔
10
    return f, obj
4✔
11

12
def iscoroutinefunction(obj):
4✔
13
    return asyncio.iscoroutinefunction(obj)
4✔
14

15
class WeakMethodContainer(weakref.WeakValueDictionary):
4✔
16
    """Container to store weak references to callbacks
17

18
    Instance methods are stored using the underlying :term:`function` object
19
    and the instance id (using :func:`id(obj) <id>`) as the key (a two-tuple)
20
    and the object itself as the value. This ensures proper weak referencing.
21

22
    Functions are stored using the string "function" and the id of the function
23
    as the key (a two-tuple).
24
    """
25
    def add_method(self, m, **kwargs):
4✔
26
        """Add an instance method or function
27

28
        Args:
29
            m: The instance method or function to store
30
        """
31
        if isinstance(m, types.FunctionType):
4✔
32
            self['function', id(m)] = m
4✔
33
        else:
34
            f, obj = get_method_vars(m)
4✔
35
            wrkey = (f, id(obj))
4✔
36
            self[wrkey] = obj
4✔
37
    def del_method(self, m):
4✔
38
        """Remove an instance method or function if it exists
39

40
        Args:
41
            m: The instance method or function to remove
42
        """
43
        if isinstance(m, types.FunctionType) and not iscoroutinefunction(m):
4✔
44
            wrkey = ('function', id(m))
4✔
45
        else:
46
            f, obj = get_method_vars(m)
4✔
47
            wrkey = (f, id(obj))
4✔
48
        if wrkey in self:
4✔
49
            del self[wrkey]
4✔
50
    def del_instance(self, obj):
4✔
51
        """Remove any stored instance methods that belong to an object
52

53
        Args:
54
            obj: The instance object to remove
55
        """
56
        to_remove = set()
4✔
57
        for wrkey, _obj in self.iter_instances():
4✔
58
            if obj is _obj:
4✔
59
                to_remove.add(wrkey)
4✔
60
        for wrkey in to_remove:
4✔
61
            del self[wrkey]
4✔
62
    def iter_instances(self):
4✔
63
        """Iterate over the stored objects
64

65
        Yields:
66
            wrkey: The two-tuple key used to store the object
67
            obj: The instance or function object
68
        """
69
        for wrkey in set(self.keys()):
4✔
70
            obj = self.get(wrkey)
4✔
71
            if obj is None:
4✔
72
                continue
×
73
            yield wrkey, obj
4✔
74
    def iter_methods(self):
4✔
75
        """Iterate over stored functions and instance methods
76

77
        Yields:
78
            Instance methods or function objects
79
        """
80
        for wrkey, obj in self.iter_instances():
4✔
81
            f, obj_id = wrkey
4✔
82
            if f == 'function':
4✔
83
                yield self[wrkey]
4✔
84
            else:
85
                yield getattr(obj, f.__name__)
4✔
86

87
class InformativeDict(dict):
4✔
88
    def __delitem__(self, key):
4✔
89
        super(InformativeDict, self).__delitem__(key)
×
90
        self.del_callback(key)
×
91

92
class InformativeWVDict(weakref.WeakValueDictionary):
4✔
93
    """A WeakValueDictionary providing a callback for deletion
94

95
    Keyword Arguments:
96
        del_callback: A callback function that will be called when an item is
97
            either deleted or dereferenced. It will be called with the key as
98
            the only argument.
99
    """
100
    def __init__(self, **kwargs):
4✔
101
        self.del_callback = kwargs.get('del_callback')
4✔
102
        weakref.WeakValueDictionary.__init__(self)
4✔
103
        def remove(wr, selfref=ref(self)):
4✔
104
            self = selfref()
4✔
105
            if self is not None:
4✔
106
                if self._iterating:
4✔
107
                    self._pending_removals.append(wr.key)
×
108
                else:
109
                    # Atomic removal is necessary since this function
110
                    # can be called asynchronously by the GC
111
                    _remove_dead_weakref(self.data, wr.key)
4✔
112
                    self._data_del_callback(wr.key)
4✔
113
        self._remove = remove
4✔
114
        self.data = InformativeDict()
4✔
115
        self.data.del_callback = self._data_del_callback
4✔
116
    def _data_del_callback(self, key):
4✔
117
        self.del_callback(key)
4✔
118

119
class EmissionHoldLock:
4✔
120
    """Context manager used for :meth:`pydispatch.dispatch.Dispatcher.emission_lock`
121

122
    Supports use as a :term:`context manager` used in :keyword:`with` statements
123
    and an :term:`asynchronous context manager` when used in
124
    :keyword:`async with` statements.
125

126
    Args:
127
        event_instance: The :class:`~pydispatch.dispatch.Event` instance
128
            associated with the lock
129

130
    Attributes:
131
        event_instance: The :class:`~pydispatch.dispatch.Event` instance
132
            associated with the lock
133
        last_event: The positional and keyword arguments from the event's last
134
            emission as a two-tuple. If no events were triggered while the lock
135
            was held, :obj:`None`.
136
        held (bool): The internal state of the lock
137
    """
138
    def __init__(self, event_instance):
4✔
139
        self.event_instance = event_instance
4✔
140
        self.last_event = None
4✔
141
        self.held = False
4✔
142
    @property
4✔
143
    def aio_locks(self):
2✔
144
        d = getattr(self, '_aio_locks', None)
4✔
145
        if d is None:
4✔
146
            d = self._aio_locks = {}
4✔
147
        return d
4✔
148

149
    def acquire(self):
4✔
150
        if self.held:
4✔
151
            return
4✔
152
        self.held = True
4✔
153
        self.last_event = None
4✔
154
    def release(self):
4✔
155
        if not self.held:
4✔
156
            return
4✔
157
        if self.last_event is not None:
4✔
158
            args, kwargs = self.last_event
4✔
159
            self.last_event = None
4✔
160
            self.held = False
4✔
161
            self.event_instance(*args, **kwargs)
4✔
162

163
    async def acquire_async(self):
4✔
164
        self.acquire()
4✔
165
        lock = await self._build_aio_lock()
4✔
166
        if not lock.locked():
4✔
167
            await lock.acquire()
4✔
168
    async def release_async(self):
4✔
169
        lock = await self._build_aio_lock()
4✔
170
        if lock.locked:
4✔
171
            lock.release()
4✔
172
        self.release()
4✔
173

174
    def __enter__(self):
4✔
175
        self.acquire()
4✔
176
        return self
4✔
177
    def __exit__(self, *args):
4✔
178
        self.release()
4✔
179

180
    async def __aenter__(self):
4✔
181
        await self.acquire_async()
4✔
182
        return self
4✔
183
    async def __aexit__(self, *args):
4✔
184
        await self.release_async()
4✔
185

186
    async def _build_aio_lock(self):
4✔
187
        loop = asyncio.get_event_loop()
4✔
188
        key = id(loop)
4✔
189
        lock = self.aio_locks.get(key)
4✔
190
        if lock is None:
4✔
191
            lock = asyncio.Lock()
4✔
192
            self.aio_locks[key] = lock
4✔
193
        return lock
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc