a marker for caching base class for all signals internal attributes receivers receiverkey id weakrefreceiver create a new signal for convenience we create empty caches even if they are not used a note about caching if usecaching is defined then for each distinct sender we cache the receivers that sender has in senderreceiverscache the cache is cleaned when connect or disconnect is called and populated on send connect receiver to sender for signal arguments receiver a function or an instance method which is to receive signals receivers must be hashable objects receivers can be asynchronous if weak is true then receiver must be weak referenceable receivers must be able to accept keyword arguments if a receiver is connected with a dispatchuid argument it will not be added if another receiver was already connected with that dispatchuid sender the sender to which the receiver should respond must either be a python object or none to receive events from any sender weak whether to use weak references to the receiver by default the module will attempt to use weak references to the receiver objects if this parameter is false then strong references will be used dispatchuid an identifier used to uniquely identify a particular instance of a receiver this will usually be a string though it may be anything hashable if debug is on check that we got a good receiver check for kwargs check for bound methods disconnect receiver from sender for signal if weak references are used disconnect need not be called the receiver will be removed from dispatch automatically arguments receiver the registered receiver to disconnect may be none if dispatchuid is specified sender the registered sender to disconnect dispatchuid the unique identifier of the receiver to disconnect send signal from sender to all connected receivers if any receiver raises an error the error propagates back through send terminating the dispatch loop so it s possible that all receivers won t be called if an error is raised if any receivers are asynchronous they are called after all the synchronous receivers via a single call to asynctosync they are also executed concurrently with asyncio gather arguments sender the sender of the signal either a specific object or none named named arguments which will be passed to receivers return a list of tuple pairs receiver response send signal from sender to all connected receivers in async mode all sync receivers will be wrapped by synctoasync if any receiver raises an error the error propagates back through send terminating the dispatch loop so it s possible that all receivers won t be called if an error is raised if any receivers are synchronous they are grouped and called behind a synctoasync adaption before executing any asynchronous receivers if any receivers are asynchronous they are grouped and executed concurrently with asyncio gather arguments sender the sender of the signal either a specific object or none named named arguments which will be passed to receivers return a list of tuple pairs receiver response send signal from sender to all connected receivers catching errors if any receivers are asynchronous they are called after all the synchronous receivers via a single call to asynctosync they are also executed concurrently with asyncio gather arguments sender the sender of the signal can be any python object normally one registered with a connect if you actually want something to occur named named arguments which will be passed to receivers return a list of tuple pairs receiver response if any receiver raises an error specifically any subclass of exception return the error instance as the result for that receiver call each receiver with whatever arguments it can accept return a list of tuple pairs receiver response send signal from sender to all connected receivers catching errors if any receivers are synchronous they are grouped and called behind a synctoasync adaption before executing any asynchronous receivers if any receivers are asynchronous they are grouped and executed concurrently with asyncio gather arguments sender the sender of the signal can be any python object normally one registered with a connect if you actually want something to occur named named arguments which will be passed to receivers return a list of tuple pairs receiver response if any receiver raises an error specifically any subclass of exception return the error instance as the result for that receiver call each receiver with whatever arguments it can accept return a list of tuple pairs receiver response note caller is assumed to hold self lock filter sequence of receivers to get resolved live receivers this checks for weak references and resolves them then returning only live receivers we could end up here with noreceivers even if we do check this case in send prior to calling livereceivers due to concurrent send call note we must cache the weakref versions dereference the weak reference mark that the self receivers list has dead weakrefs if so we will clean those up in connect disconnect and livereceivers while holding self lock note that doing the cleanup here isn t a good idea removereceiver will be called as side effect of garbage collection and so the call can happen while we are already holding self lock a decorator for connecting receivers to signals used by passing in the signal or list of signals and keyword arguments to connect receiverpostsave sendermymodel def signalreceiversender kwargs receiverpostsave postdelete sendermymodel def signalsreceiversender kwargs a marker for caching base class for all signals internal attributes receivers receiverkey id weakref receiver create a new signal for convenience we create empty caches even if they are not used a note about caching if use_caching is defined then for each distinct sender we cache the receivers that sender has in sender_receivers_cache the cache is cleaned when connect or disconnect is called and populated on send connect receiver to sender for signal arguments receiver a function or an instance method which is to receive signals receivers must be hashable objects receivers can be asynchronous if weak is true then receiver must be weak referenceable receivers must be able to accept keyword arguments if a receiver is connected with a dispatch_uid argument it will not be added if another receiver was already connected with that dispatch_uid sender the sender to which the receiver should respond must either be a python object or none to receive events from any sender weak whether to use weak references to the receiver by default the module will attempt to use weak references to the receiver objects if this parameter is false then strong references will be used dispatch_uid an identifier used to uniquely identify a particular instance of a receiver this will usually be a string though it may be anything hashable if debug is on check that we got a good receiver check for kwargs check for bound methods disconnect receiver from sender for signal if weak references are used disconnect need not be called the receiver will be removed from dispatch automatically arguments receiver the registered receiver to disconnect may be none if dispatch_uid is specified sender the registered sender to disconnect dispatch_uid the unique identifier of the receiver to disconnect send signal from sender to all connected receivers if any receiver raises an error the error propagates back through send terminating the dispatch loop so it s possible that all receivers won t be called if an error is raised if any receivers are asynchronous they are called after all the synchronous receivers via a single call to async_to_sync they are also executed concurrently with asyncio gather arguments sender the sender of the signal either a specific object or none named named arguments which will be passed to receivers return a list of tuple pairs receiver response send signal from sender to all connected receivers in async mode all sync receivers will be wrapped by sync_to_async if any receiver raises an error the error propagates back through send terminating the dispatch loop so it s possible that all receivers won t be called if an error is raised if any receivers are synchronous they are grouped and called behind a sync_to_async adaption before executing any asynchronous receivers if any receivers are asynchronous they are grouped and executed concurrently with asyncio gather arguments sender the sender of the signal either a specific object or none named named arguments which will be passed to receivers return a list of tuple pairs receiver response send signal from sender to all connected receivers catching errors if any receivers are asynchronous they are called after all the synchronous receivers via a single call to async_to_sync they are also executed concurrently with asyncio gather arguments sender the sender of the signal can be any python object normally one registered with a connect if you actually want something to occur named named arguments which will be passed to receivers return a list of tuple pairs receiver response if any receiver raises an error specifically any subclass of exception return the error instance as the result for that receiver call each receiver with whatever arguments it can accept return a list of tuple pairs receiver response send signal from sender to all connected receivers catching errors if any receivers are synchronous they are grouped and called behind a sync_to_async adaption before executing any asynchronous receivers if any receivers are asynchronous they are grouped and executed concurrently with asyncio gather arguments sender the sender of the signal can be any python object normally one registered with a connect if you actually want something to occur named named arguments which will be passed to receivers return a list of tuple pairs receiver response if any receiver raises an error specifically any subclass of exception return the error instance as the result for that receiver call each receiver with whatever arguments it can accept return a list of tuple pairs receiver response note caller is assumed to hold self lock filter sequence of receivers to get resolved live receivers this checks for weak references and resolves them then returning only live receivers we could end up here with no_receivers even if we do check this case in send prior to calling _live_receivers due to concurrent send call note we must cache the weakref versions dereference the weak reference mark that the self receivers list has dead weakrefs if so we will clean those up in connect disconnect and _live_receivers while holding self lock note that doing the cleanup here isn t a good idea _remove_receiver will be called as side effect of garbage collection and so the call can happen while we are already holding self lock a decorator for connecting receivers to signals used by passing in the signal or list of signals and keyword arguments to connect receiver post_save sender mymodel def signal_receiver sender kwargs receiver post_save post_delete sender mymodel def signals_receiver sender kwargs
import asyncio
import logging
import threading
import weakref

from asgiref.sync import async_to_sync, iscoroutinefunction, sync_to_async

from django.utils.inspect import func_accepts_kwargs

logger = logging.getLogger("django.dispatch")


def _make_id(target):
    if hasattr(target, "__func__"):
        return (id(target.__self__), id(target.__func__))
    return id(target)


NONE_ID = _make_id(None)


NO_RECEIVERS = object()


class Signal:
    

    def __init__(self, use_caching=False):
        
        self.receivers = []
        self.lock = threading.Lock()
        self.use_caching = use_caching
        
        
        
        
        
        self.sender_receivers_cache = weakref.WeakKeyDictionary() if use_caching else {}
        self._dead_receivers = False

    def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
        
        from django.conf import settings

        
        if settings.configured and settings.DEBUG:
            if not callable(receiver):
                raise TypeError("Signal receivers must be callable.")
            
            if not func_accepts_kwargs(receiver):
                raise ValueError(
                    "Signal receivers must accept keyword arguments (**kwargs)."
                )

        if dispatch_uid:
            lookup_key = (dispatch_uid, _make_id(sender))
        else:
            lookup_key = (_make_id(receiver), _make_id(sender))

        is_async = iscoroutinefunction(receiver)

        if weak:
            ref = weakref.ref
            receiver_object = receiver
            
            if hasattr(receiver, "__self__") and hasattr(receiver, "__func__"):
                ref = weakref.WeakMethod
                receiver_object = receiver.__self__
            receiver = ref(receiver)
            weakref.finalize(receiver_object, self._remove_receiver)

        with self.lock:
            self._clear_dead_receivers()
            if not any(r_key == lookup_key for r_key, _, _ in self.receivers):
                self.receivers.append((lookup_key, receiver, is_async))
            self.sender_receivers_cache.clear()

    def disconnect(self, receiver=None, sender=None, dispatch_uid=None):
        
        if dispatch_uid:
            lookup_key = (dispatch_uid, _make_id(sender))
        else:
            lookup_key = (_make_id(receiver), _make_id(sender))

        disconnected = False
        with self.lock:
            self._clear_dead_receivers()
            for index in range(len(self.receivers)):
                r_key, *_ = self.receivers[index]
                if r_key == lookup_key:
                    disconnected = True
                    del self.receivers[index]
                    break
            self.sender_receivers_cache.clear()
        return disconnected

    def has_listeners(self, sender=None):
        sync_receivers, async_receivers = self._live_receivers(sender)
        return bool(sync_receivers) or bool(async_receivers)

    def send(self, sender, **named):
        
        if (
            not self.receivers
            or self.sender_receivers_cache.get(sender) is NO_RECEIVERS
        ):
            return []
        responses = []
        sync_receivers, async_receivers = self._live_receivers(sender)
        for receiver in sync_receivers:
            response = receiver(signal=self, sender=sender, **named)
            responses.append((receiver, response))
        if async_receivers:

            async def asend():
                async_responses = await asyncio.gather(
                    *(
                        receiver(signal=self, sender=sender, **named)
                        for receiver in async_receivers
                    )
                )
                return zip(async_receivers, async_responses)

            responses.extend(async_to_sync(asend)())
        return responses

    async def asend(self, sender, **named):
        
        if (
            not self.receivers
            or self.sender_receivers_cache.get(sender) is NO_RECEIVERS
        ):
            return []
        sync_receivers, async_receivers = self._live_receivers(sender)
        if sync_receivers:

            @sync_to_async
            def sync_send():
                responses = []
                for receiver in sync_receivers:
                    response = receiver(signal=self, sender=sender, **named)
                    responses.append((receiver, response))
                return responses

        else:
            sync_send = list

        responses, async_responses = await asyncio.gather(
            sync_send(),
            asyncio.gather(
                *(
                    receiver(signal=self, sender=sender, **named)
                    for receiver in async_receivers
                )
            ),
        )
        responses.extend(zip(async_receivers, async_responses))
        return responses

    def _log_robust_failure(self, receiver, err):
        logger.error(
            "Error calling %s in Signal.send_robust() (%s)",
            receiver.__qualname__,
            err,
            exc_info=err,
        )

    def send_robust(self, sender, **named):
        
        if (
            not self.receivers
            or self.sender_receivers_cache.get(sender) is NO_RECEIVERS
        ):
            return []

        
        
        responses = []
        sync_receivers, async_receivers = self._live_receivers(sender)
        for receiver in sync_receivers:
            try:
                response = receiver(signal=self, sender=sender, **named)
            except Exception as err:
                self._log_robust_failure(receiver, err)
                responses.append((receiver, err))
            else:
                responses.append((receiver, response))
        if async_receivers:

            async def asend_and_wrap_exception(receiver):
                try:
                    response = await receiver(signal=self, sender=sender, **named)
                except Exception as err:
                    self._log_robust_failure(receiver, err)
                    return err
                return response

            async def asend():
                async_responses = await asyncio.gather(
                    *(
                        asend_and_wrap_exception(receiver)
                        for receiver in async_receivers
                    )
                )
                return zip(async_receivers, async_responses)

            responses.extend(async_to_sync(asend)())
        return responses

    async def asend_robust(self, sender, **named):
        
        if (
            not self.receivers
            or self.sender_receivers_cache.get(sender) is NO_RECEIVERS
        ):
            return []

        
        
        sync_receivers, async_receivers = self._live_receivers(sender)

        if sync_receivers:

            @sync_to_async
            def sync_send():
                responses = []
                for receiver in sync_receivers:
                    try:
                        response = receiver(signal=self, sender=sender, **named)
                    except Exception as err:
                        self._log_robust_failure(receiver, err)
                        responses.append((receiver, err))
                    else:
                        responses.append((receiver, response))
                return responses

        else:
            sync_send = list

        async def asend_and_wrap_exception(receiver):
            try:
                response = await receiver(signal=self, sender=sender, **named)
            except Exception as err:
                self._log_robust_failure(receiver, err)
                return err
            return response

        responses, async_responses = await asyncio.gather(
            sync_send(),
            asyncio.gather(
                *(asend_and_wrap_exception(receiver) for receiver in async_receivers),
            ),
        )
        responses.extend(zip(async_receivers, async_responses))
        return responses

    def _clear_dead_receivers(self):
        
        if self._dead_receivers:
            self._dead_receivers = False
            self.receivers = [
                r
                for r in self.receivers
                if not (isinstance(r[1], weakref.ReferenceType) and r[1]() is None)
            ]

    def _live_receivers(self, sender):
        
        receivers = None
        if self.use_caching and not self._dead_receivers:
            receivers = self.sender_receivers_cache.get(sender)
            
            
            if receivers is NO_RECEIVERS:
                return [], []
        if receivers is None:
            with self.lock:
                self._clear_dead_receivers()
                senderkey = _make_id(sender)
                receivers = []
                for (_receiverkey, r_senderkey), receiver, is_async in self.receivers:
                    if r_senderkey == NONE_ID or r_senderkey == senderkey:
                        receivers.append((receiver, is_async))
                if self.use_caching:
                    if not receivers:
                        self.sender_receivers_cache[sender] = NO_RECEIVERS
                    else:
                        
                        self.sender_receivers_cache[sender] = receivers
        non_weak_sync_receivers = []
        non_weak_async_receivers = []
        for receiver, is_async in receivers:
            if isinstance(receiver, weakref.ReferenceType):
                
                receiver = receiver()
                if receiver is not None:
                    if is_async:
                        non_weak_async_receivers.append(receiver)
                    else:
                        non_weak_sync_receivers.append(receiver)
            else:
                if is_async:
                    non_weak_async_receivers.append(receiver)
                else:
                    non_weak_sync_receivers.append(receiver)
        return non_weak_sync_receivers, non_weak_async_receivers

    def _remove_receiver(self, receiver=None):
        
        
        
        
        
        
        self._dead_receivers = True


def receiver(signal, **kwargs):
    

    def _decorator(func):
        if isinstance(signal, (list, tuple)):
            for s in signal:
                s.connect(func, **kwargs)
        else:
            signal.connect(func, **kwargs)
        return func

    return _decorator