Major refactoring of django.dispatch with an eye towards speed. The net result is that signals are up to 90% faster.
Though some attempts and backwards-compatibility were made, speed trumped compatibility. Thus, as usual, check BackwardsIncompatibleChanges for the complete list of backwards-incompatible changes. Thanks to Jeremy Dunck and Keith Busell for the bulk of the work; some ideas from Brian Herring's previous work (refs #4561) were incorporated. Documentation is, sigh, still forthcoming. Fixes #6814 and #3951 (with the new dispatch_uid argument to connect). git-svn-id: http://code.djangoproject.com/svn/django/trunk@8223 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
d06b474251
commit
34a3bd5225
|
@ -2,7 +2,6 @@
|
|||
Creates permissions for all installed apps that need permissions.
|
||||
"""
|
||||
|
||||
from django.dispatch import dispatcher
|
||||
from django.db.models import get_models, signals
|
||||
from django.contrib.auth import models as auth_app
|
||||
|
||||
|
@ -16,7 +15,7 @@ def _get_all_permissions(opts):
|
|||
perms.append((_get_permission_codename(action, opts), u'Can %s %s' % (action, opts.verbose_name_raw)))
|
||||
return perms + list(opts.permissions)
|
||||
|
||||
def create_permissions(app, created_models, verbosity):
|
||||
def create_permissions(app, created_models, verbosity, **kwargs):
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.auth.models import Permission
|
||||
app_models = get_models(app)
|
||||
|
@ -45,7 +44,7 @@ def create_superuser(app, created_models, verbosity, **kwargs):
|
|||
call_command("createsuperuser", interactive=True)
|
||||
break
|
||||
|
||||
if 'create_permissions' not in [i.__name__ for i in dispatcher.getAllReceivers(signal=signals.post_syncdb)]:
|
||||
dispatcher.connect(create_permissions, signal=signals.post_syncdb)
|
||||
if 'create_superuser' not in [i.__name__ for i in dispatcher.getAllReceivers(signal=signals.post_syncdb, sender=auth_app)]:
|
||||
dispatcher.connect(create_superuser, sender=auth_app, signal=signals.post_syncdb)
|
||||
signals.post_syncdb.connect(create_permissions,
|
||||
dispatch_uid = "django.contrib.auth.management.create_permissions")
|
||||
signals.post_syncdb.connect(create_superuser,
|
||||
sender=auth_app, dispatch_uid = "django.contrib.auth.management.create_superuser")
|
||||
|
|
|
@ -8,7 +8,6 @@ from django.db import connection
|
|||
from django.db.models import signals
|
||||
from django.db.models.fields.related import RelatedField, Field, ManyToManyRel
|
||||
from django.db.models.loading import get_model
|
||||
from django.dispatch import dispatcher
|
||||
from django.utils.functional import curry
|
||||
|
||||
class GenericForeignKey(object):
|
||||
|
@ -29,12 +28,12 @@ class GenericForeignKey(object):
|
|||
self.cache_attr = "_%s_cache" % name
|
||||
|
||||
# For some reason I don't totally understand, using weakrefs here doesn't work.
|
||||
dispatcher.connect(self.instance_pre_init, signal=signals.pre_init, sender=cls, weak=False)
|
||||
signals.pre_init.connect(self.instance_pre_init, sender=cls, weak=False)
|
||||
|
||||
# Connect myself as the descriptor for this field
|
||||
setattr(cls, name, self)
|
||||
|
||||
def instance_pre_init(self, signal, sender, args, kwargs):
|
||||
def instance_pre_init(self, signal, sender, args, kwargs, **_kwargs):
|
||||
"""
|
||||
Handles initializing an object with the generic FK instaed of
|
||||
content-type/object-id fields.
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.dispatch import dispatcher
|
||||
from django.db.models import get_apps, get_models, signals
|
||||
from django.utils.encoding import smart_unicode
|
||||
|
||||
def update_contenttypes(app, created_models, verbosity=2):
|
||||
def update_contenttypes(app, created_models, verbosity=2, **kwargs):
|
||||
"""
|
||||
Creates content types for models in the given app, removing any model
|
||||
entries that no longer have a matching model class.
|
||||
|
@ -37,7 +36,7 @@ def update_all_contenttypes(verbosity=2):
|
|||
for app in get_apps():
|
||||
update_contenttypes(app, None, verbosity)
|
||||
|
||||
dispatcher.connect(update_contenttypes, signal=signals.post_syncdb)
|
||||
signals.post_syncdb.connect(update_contenttypes)
|
||||
|
||||
if __name__ == "__main__":
|
||||
update_all_contenttypes()
|
||||
|
|
|
@ -2,12 +2,11 @@
|
|||
Creates the default Site object.
|
||||
"""
|
||||
|
||||
from django.dispatch import dispatcher
|
||||
from django.db.models import signals
|
||||
from django.contrib.sites.models import Site
|
||||
from django.contrib.sites import models as site_app
|
||||
|
||||
def create_default_site(app, created_models, verbosity):
|
||||
def create_default_site(app, created_models, verbosity, **kwargs):
|
||||
if Site in created_models:
|
||||
if verbosity >= 2:
|
||||
print "Creating example.com Site object"
|
||||
|
@ -15,4 +14,4 @@ def create_default_site(app, created_models, verbosity):
|
|||
s.save()
|
||||
Site.objects.clear_cache()
|
||||
|
||||
dispatcher.connect(create_default_site, sender=site_app, signal=signals.post_syncdb)
|
||||
signals.post_syncdb.connect(create_default_site, sender=site_app)
|
||||
|
|
|
@ -2,7 +2,6 @@ import sys
|
|||
|
||||
from django import http
|
||||
from django.core import signals
|
||||
from django.dispatch import dispatcher
|
||||
from django.utils.encoding import force_unicode
|
||||
|
||||
class BaseHandler(object):
|
||||
|
@ -122,7 +121,7 @@ class BaseHandler(object):
|
|||
except: # Handle everything else, including SuspiciousOperation, etc.
|
||||
# Get the exception info now, in case another exception is thrown later.
|
||||
exc_info = sys.exc_info()
|
||||
receivers = dispatcher.send(signal=signals.got_request_exception, request=request)
|
||||
receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
|
||||
return self.handle_uncaught_exception(request, resolver, exc_info)
|
||||
|
||||
def handle_uncaught_exception(self, request, resolver, exc_info):
|
||||
|
|
|
@ -5,7 +5,6 @@ from django import http
|
|||
from django.core import signals
|
||||
from django.core.handlers.base import BaseHandler
|
||||
from django.core.urlresolvers import set_script_prefix
|
||||
from django.dispatch import dispatcher
|
||||
from django.utils import datastructures
|
||||
from django.utils.encoding import force_unicode, smart_str
|
||||
|
||||
|
@ -174,7 +173,7 @@ class ModPythonHandler(BaseHandler):
|
|||
self.load_middleware()
|
||||
|
||||
set_script_prefix(req.get_options().get('django.root', ''))
|
||||
dispatcher.send(signal=signals.request_started)
|
||||
signals.request_started.send(sender=self.__class__)
|
||||
try:
|
||||
try:
|
||||
request = self.request_class(req)
|
||||
|
@ -188,7 +187,7 @@ class ModPythonHandler(BaseHandler):
|
|||
response = middleware_method(request, response)
|
||||
response = self.apply_response_fixes(request, response)
|
||||
finally:
|
||||
dispatcher.send(signal=signals.request_finished)
|
||||
signals.request_finished.send(sender=self.__class__)
|
||||
|
||||
# Convert our custom HttpResponse object back into the mod_python req.
|
||||
req.content_type = response['Content-Type']
|
||||
|
|
|
@ -9,7 +9,6 @@ from django import http
|
|||
from django.core import signals
|
||||
from django.core.handlers import base
|
||||
from django.core.urlresolvers import set_script_prefix
|
||||
from django.dispatch import dispatcher
|
||||
from django.utils import datastructures
|
||||
from django.utils.encoding import force_unicode
|
||||
|
||||
|
@ -207,7 +206,7 @@ class WSGIHandler(base.BaseHandler):
|
|||
self.initLock.release()
|
||||
|
||||
set_script_prefix(base.get_script_name(environ))
|
||||
dispatcher.send(signal=signals.request_started)
|
||||
signals.request_started.send(sender=self.__class__)
|
||||
try:
|
||||
try:
|
||||
request = self.request_class(environ)
|
||||
|
@ -221,7 +220,7 @@ class WSGIHandler(base.BaseHandler):
|
|||
response = middleware_method(request, response)
|
||||
response = self.apply_response_fixes(request, response)
|
||||
finally:
|
||||
dispatcher.send(signal=signals.request_finished)
|
||||
signals.request_finished.send(sender=self.__class__)
|
||||
|
||||
try:
|
||||
status_text = STATUS_CODE_TEXT[response.status_code]
|
||||
|
|
|
@ -15,7 +15,6 @@ class Command(NoArgsCommand):
|
|||
def handle_noargs(self, **options):
|
||||
from django.conf import settings
|
||||
from django.db import connection, transaction, models
|
||||
from django.dispatch import dispatcher
|
||||
from django.core.management.sql import sql_flush, emit_post_sync_signal
|
||||
|
||||
verbosity = int(options.get('verbosity', 1))
|
||||
|
|
|
@ -492,6 +492,6 @@ def emit_post_sync_signal(created_models, verbosity, interactive):
|
|||
app_name = app.__name__.split('.')[-2]
|
||||
if verbosity >= 2:
|
||||
print "Running post-sync handlers for application", app_name
|
||||
dispatcher.send(signal=models.signals.post_syncdb, sender=app,
|
||||
app=app, created_models=created_models,
|
||||
verbosity=verbosity, interactive=interactive)
|
||||
models.signals.post_syncdb.send(sender=app, app=app,
|
||||
created_models=created_models, verbosity=verbosity,
|
||||
interactive=interactive)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
request_started = object()
|
||||
request_finished = object()
|
||||
got_request_exception = object()
|
||||
from django.dispatch import Signal
|
||||
|
||||
request_started = Signal()
|
||||
request_finished = Signal()
|
||||
got_request_exception = Signal(providing_args=["request"])
|
||||
|
|
|
@ -2,7 +2,6 @@ import os
|
|||
from django.conf import settings
|
||||
from django.core import signals
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.dispatch import dispatcher
|
||||
from django.utils.functional import curry
|
||||
|
||||
__all__ = ('backend', 'connection', 'DatabaseError', 'IntegrityError')
|
||||
|
@ -58,17 +57,19 @@ IntegrityError = backend.IntegrityError
|
|||
|
||||
# Register an event that closes the database connection
|
||||
# when a Django request is finished.
|
||||
dispatcher.connect(connection.close, signal=signals.request_finished)
|
||||
def close_connection(**kwargs):
|
||||
connection.close()
|
||||
signals.request_finished.connect(close_connection)
|
||||
|
||||
# Register an event that resets connection.queries
|
||||
# when a Django request is started.
|
||||
def reset_queries():
|
||||
def reset_queries(**kwargs):
|
||||
connection.queries = []
|
||||
dispatcher.connect(reset_queries, signal=signals.request_started)
|
||||
signals.request_started.connect(reset_queries)
|
||||
|
||||
# Register an event that rolls back the connection
|
||||
# when a Django request has an exception.
|
||||
def _rollback_on_exception():
|
||||
def _rollback_on_exception(**kwargs):
|
||||
from django.db import transaction
|
||||
transaction.rollback_unless_managed()
|
||||
dispatcher.connect(_rollback_on_exception, signal=signals.got_request_exception)
|
||||
signals.got_request_exception.connect(_rollback_on_exception)
|
||||
|
|
|
@ -19,7 +19,6 @@ from django.db.models.options import Options
|
|||
from django.db import connection, transaction
|
||||
from django.db.models import signals
|
||||
from django.db.models.loading import register_models, get_model
|
||||
from django.dispatch import dispatcher
|
||||
from django.utils.functional import curry
|
||||
from django.utils.encoding import smart_str, force_unicode, smart_unicode
|
||||
from django.core.files.move import file_move_safe
|
||||
|
@ -161,14 +160,14 @@ class ModelBase(type):
|
|||
if hasattr(cls, 'get_absolute_url'):
|
||||
cls.get_absolute_url = curry(get_absolute_url, opts, cls.get_absolute_url)
|
||||
|
||||
dispatcher.send(signal=signals.class_prepared, sender=cls)
|
||||
signals.class_prepared.send(sender=cls)
|
||||
|
||||
|
||||
class Model(object):
|
||||
__metaclass__ = ModelBase
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
dispatcher.send(signal=signals.pre_init, sender=self.__class__, args=args, kwargs=kwargs)
|
||||
signals.pre_init.send(sender=self.__class__, args=args, kwargs=kwargs)
|
||||
|
||||
# There is a rather weird disparity here; if kwargs, it's set, then args
|
||||
# overrides it. It should be one or the other; don't duplicate the work
|
||||
|
@ -239,7 +238,7 @@ class Model(object):
|
|||
pass
|
||||
if kwargs:
|
||||
raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]
|
||||
dispatcher.send(signal=signals.post_init, sender=self.__class__, instance=self)
|
||||
signals.post_init.send(sender=self.__class__, instance=self)
|
||||
|
||||
def __repr__(self):
|
||||
return smart_str(u'<%s: %s>' % (self.__class__.__name__, unicode(self)))
|
||||
|
@ -288,8 +287,7 @@ class Model(object):
|
|||
cls = self.__class__
|
||||
meta = self._meta
|
||||
signal = True
|
||||
dispatcher.send(signal=signals.pre_save, sender=self.__class__,
|
||||
instance=self, raw=raw)
|
||||
signals.pre_save.send(sender=self.__class__, instance=self, raw=raw)
|
||||
else:
|
||||
meta = cls._meta
|
||||
signal = False
|
||||
|
@ -351,8 +349,8 @@ class Model(object):
|
|||
transaction.commit_unless_managed()
|
||||
|
||||
if signal:
|
||||
dispatcher.send(signal=signals.post_save, sender=self.__class__,
|
||||
instance=self, created=(not record_exists), raw=raw)
|
||||
signals.post_save.send(sender=self.__class__, instance=self,
|
||||
created=(not record_exists), raw=raw)
|
||||
|
||||
save_base.alters_data = True
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ except ImportError:
|
|||
from django.db import connection, get_creation_module
|
||||
from django.db.models import signals
|
||||
from django.db.models.query_utils import QueryWrapper
|
||||
from django.dispatch import dispatcher
|
||||
from django.conf import settings
|
||||
from django.core import validators
|
||||
from django import oldforms
|
||||
|
@ -819,9 +818,9 @@ class FileField(Field):
|
|||
setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self))
|
||||
setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self))
|
||||
setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_field, save=True: instance._save_FIELD_file(self, filename, raw_field, save))
|
||||
dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls)
|
||||
signals.post_delete.connect(self.delete_file, sender=cls)
|
||||
|
||||
def delete_file(self, instance):
|
||||
def delete_file(self, instance, **kwargs):
|
||||
if getattr(instance, self.attname):
|
||||
file_name = getattr(instance, 'get_%s_filename' % self.name)()
|
||||
# If the file exists and no other object of this type references it,
|
||||
|
|
|
@ -9,7 +9,6 @@ from django.utils.functional import curry
|
|||
from django.core import validators
|
||||
from django import oldforms
|
||||
from django import forms
|
||||
from django.dispatch import dispatcher
|
||||
|
||||
try:
|
||||
set
|
||||
|
@ -74,7 +73,7 @@ def add_lazy_relation(cls, field, relation, operation):
|
|||
value = (cls, field, operation)
|
||||
pending_lookups.setdefault(key, []).append(value)
|
||||
|
||||
def do_pending_lookups(sender):
|
||||
def do_pending_lookups(sender, **kwargs):
|
||||
"""
|
||||
Handle any pending relations to the sending model. Sent from class_prepared.
|
||||
"""
|
||||
|
@ -82,7 +81,7 @@ def do_pending_lookups(sender):
|
|||
for cls, field, operation in pending_lookups.pop(key, []):
|
||||
operation(field, sender, cls)
|
||||
|
||||
dispatcher.connect(do_pending_lookups, signal=signals.class_prepared)
|
||||
signals.class_prepared.connect(do_pending_lookups)
|
||||
|
||||
def manipulator_valid_rel_key(f, self, field_data, all_data):
|
||||
"Validates that the value is a valid foreign key"
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import copy
|
||||
|
||||
from django.db.models.query import QuerySet, EmptyQuerySet, insert_query
|
||||
from django.dispatch import dispatcher
|
||||
from django.db.models import signals
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
|
||||
def ensure_default_manager(sender):
|
||||
def ensure_default_manager(sender, **kwargs):
|
||||
cls = sender
|
||||
if not getattr(cls, '_default_manager', None) and not cls._meta.abstract:
|
||||
# Create the default manager, if needed.
|
||||
|
@ -16,7 +15,7 @@ def ensure_default_manager(sender):
|
|||
pass
|
||||
cls.add_to_class('objects', Manager())
|
||||
|
||||
dispatcher.connect(ensure_default_manager, signal=signals.class_prepared)
|
||||
signals.class_prepared.connect(ensure_default_manager)
|
||||
|
||||
class Manager(object):
|
||||
# Tracks each time a Manager instance is created. Used to retain order.
|
||||
|
|
|
@ -2,7 +2,6 @@ from django.core.exceptions import ObjectDoesNotExist
|
|||
from django import oldforms
|
||||
from django.core import validators
|
||||
from django.db.models.fields import FileField, AutoField
|
||||
from django.dispatch import dispatcher
|
||||
from django.db.models import signals
|
||||
from django.utils.functional import curry
|
||||
from django.utils.datastructures import DotExpandedDict
|
||||
|
@ -11,12 +10,12 @@ from django.utils.encoding import smart_str
|
|||
from django.utils.translation import ugettext as _
|
||||
from django.utils import datetime_safe
|
||||
|
||||
def add_manipulators(sender):
|
||||
def add_manipulators(sender, **kwargs):
|
||||
cls = sender
|
||||
cls.add_to_class('AddManipulator', AutomaticAddManipulator)
|
||||
cls.add_to_class('ChangeManipulator', AutomaticChangeManipulator)
|
||||
|
||||
dispatcher.connect(add_manipulators, signal=signals.class_prepared)
|
||||
signals.class_prepared.connect(add_manipulators)
|
||||
|
||||
class ManipulatorDescriptor(object):
|
||||
# This class provides the functionality that makes the default model
|
||||
|
|
|
@ -7,7 +7,6 @@ from django.db import connection, transaction, IntegrityError
|
|||
from django.db.models.fields import DateField
|
||||
from django.db.models.query_utils import Q, select_related_descend
|
||||
from django.db.models import signals, sql
|
||||
from django.dispatch import dispatcher
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
||||
|
||||
|
@ -810,8 +809,7 @@ def delete_objects(seen_objs):
|
|||
|
||||
# Pre-notify all instances to be deleted.
|
||||
for pk_val, instance in items:
|
||||
dispatcher.send(signal=signals.pre_delete, sender=cls,
|
||||
instance=instance)
|
||||
signals.pre_delete.send(sender=cls, instance=instance)
|
||||
|
||||
pk_list = [pk for pk,instance in items]
|
||||
del_query = sql.DeleteQuery(cls, connection)
|
||||
|
@ -845,8 +843,7 @@ def delete_objects(seen_objs):
|
|||
if field.rel and field.null and field.rel.to in seen_objs:
|
||||
setattr(instance, field.attname, None)
|
||||
|
||||
dispatcher.send(signal=signals.post_delete, sender=cls,
|
||||
instance=instance)
|
||||
signals.post_delete.send(sender=cls, instance=instance)
|
||||
setattr(instance, cls._meta.pk.attname, None)
|
||||
|
||||
transaction.commit_unless_managed()
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
class_prepared = object()
|
||||
from django.dispatch import Signal
|
||||
|
||||
pre_init= object()
|
||||
post_init = object()
|
||||
class_prepared = Signal(providing_args=["class"])
|
||||
|
||||
pre_save = object()
|
||||
post_save = object()
|
||||
pre_init = Signal(providing_args=["instance", "args", "kwargs"])
|
||||
post_init = Signal(providing_args=["instance"])
|
||||
|
||||
pre_delete = object()
|
||||
post_delete = object()
|
||||
pre_save = Signal(providing_args=["instance", "raw"])
|
||||
post_save = Signal(providing_args=["instance", "raw", "created"])
|
||||
|
||||
post_syncdb = object()
|
||||
pre_delete = Signal(providing_args=["instance"])
|
||||
post_delete = Signal(providing_args=["instance"])
|
||||
|
||||
post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive"])
|
||||
|
|
|
@ -11,7 +11,6 @@ from copy import deepcopy
|
|||
|
||||
from django.utils.tree import Node
|
||||
from django.utils.datastructures import SortedDict
|
||||
from django.dispatch import dispatcher
|
||||
from django.db import connection
|
||||
from django.db.models import signals
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
|
@ -1670,7 +1669,7 @@ def order_modified_iter(cursor, trim, sentinel):
|
|||
sentinel):
|
||||
yield [r[:-trim] for r in rows]
|
||||
|
||||
def setup_join_cache(sender):
|
||||
def setup_join_cache(sender, **kwargs):
|
||||
"""
|
||||
The information needed to join between model fields is something that is
|
||||
invariant over the life of the model, so we cache it in the model's Options
|
||||
|
@ -1680,5 +1679,5 @@ def setup_join_cache(sender):
|
|||
"""
|
||||
sender._meta._join_cache = {}
|
||||
|
||||
dispatcher.connect(setup_join_cache, signal=signals.class_prepared)
|
||||
signals.class_prepared.connect(setup_join_cache)
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
"""Multi-consumer multi-producer dispatching mechanism
|
||||
"""
|
||||
__version__ = "1.0.0"
|
||||
__author__ = "Patrick K. O'Brien"
|
||||
__license__ = "BSD-style, see license.txt for details"
|
||||
|
||||
Originally based on pydispatch (BSD) http://pypi.python.org/pypi/PyDispatcher/2.0.1
|
||||
See license.txt for original license.
|
||||
|
||||
Heavily modified for Django's purposes.
|
||||
"""
|
||||
|
||||
from django.dispatch.dispatcher import Signal
|
|
@ -1,495 +1,243 @@
|
|||
"""Multiple-producer-multiple-consumer signal-dispatching
|
||||
|
||||
dispatcher is the core of the PyDispatcher system,
|
||||
providing the primary API and the core logic for the
|
||||
system.
|
||||
|
||||
Module attributes of note:
|
||||
|
||||
Any -- Singleton used to signal either "Any Sender" or
|
||||
"Any Signal". See documentation of the _Any class.
|
||||
Anonymous -- Singleton used to signal "Anonymous Sender"
|
||||
See documentation of the _Anonymous class.
|
||||
|
||||
Internal attributes:
|
||||
WEAKREF_TYPES -- tuple of types/classes which represent
|
||||
weak references to receivers, and thus must be de-
|
||||
referenced on retrieval to retrieve the callable
|
||||
object
|
||||
connections -- { senderkey (id) : { signal : [receivers...]}}
|
||||
senders -- { senderkey (id) : weakref(sender) }
|
||||
used for cleaning up sender references on sender
|
||||
deletion
|
||||
sendersBack -- { receiverkey (id) : [senderkey (id)...] }
|
||||
used for cleaning up receiver references on receiver
|
||||
deletion, (considerably speeds up the cleanup process
|
||||
vs. the original code.)
|
||||
"""
|
||||
import weakref
|
||||
from django.dispatch import saferef, robustapply, errors
|
||||
import warnings
|
||||
try:
|
||||
set
|
||||
except NameError:
|
||||
from sets import Set as set # Python 2.3 fallback
|
||||
|
||||
__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
|
||||
__cvsid__ = "$Id: dispatcher.py,v 1.9 2005/09/17 04:55:57 mcfletch Exp $"
|
||||
__version__ = "$Revision: 1.9 $"[11:-2]
|
||||
|
||||
|
||||
class _Parameter:
|
||||
"""Used to represent default parameter values."""
|
||||
def __repr__(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
class _Any(_Parameter):
|
||||
"""Singleton used to signal either "Any Sender" or "Any Signal"
|
||||
|
||||
The Any object can be used with connect, disconnect,
|
||||
send, or sendExact to signal that the parameter given
|
||||
Any should react to all senders/signals, not just
|
||||
a particular sender/signal.
|
||||
"""
|
||||
Any = _Any()
|
||||
|
||||
class _Anonymous(_Parameter):
|
||||
"""Singleton used to signal "Anonymous Sender"
|
||||
|
||||
The Anonymous object is used to signal that the sender
|
||||
of a message is not specified (as distinct from being
|
||||
"any sender"). Registering callbacks for Anonymous
|
||||
will only receive messages sent without senders. Sending
|
||||
with anonymous will only send messages to those receivers
|
||||
registered for Any or Anonymous.
|
||||
|
||||
Note:
|
||||
The default sender for connect is Any, while the
|
||||
default sender for send is Anonymous. This has
|
||||
the effect that if you do not specify any senders
|
||||
in either function then all messages are routed
|
||||
as though there was a single sender (Anonymous)
|
||||
being used everywhere.
|
||||
"""
|
||||
Anonymous = _Anonymous()
|
||||
from django.dispatch import saferef
|
||||
|
||||
WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
|
||||
|
||||
connections = {}
|
||||
senders = {}
|
||||
sendersBack = {}
|
||||
def _make_id(target):
|
||||
if hasattr(target, 'im_func'):
|
||||
return (id(target.im_self), id(target.im_func))
|
||||
return id(target)
|
||||
|
||||
class Signal(object):
|
||||
"""Base class for all signals
|
||||
|
||||
def connect(receiver, signal=Any, sender=Any, weak=True):
|
||||
"""Connect receiver to sender for signal
|
||||
|
||||
receiver -- a callable Python object which is to receive
|
||||
messages/signals/events. Receivers must be hashable
|
||||
objects.
|
||||
|
||||
if weak is True, then receiver must be weak-referencable
|
||||
(more precisely saferef.safeRef() must be able to create
|
||||
a reference to the receiver).
|
||||
|
||||
Receivers are fairly flexible in their specification,
|
||||
as the machinery in the robustApply module takes care
|
||||
of most of the details regarding figuring out appropriate
|
||||
subsets of the sent arguments to apply to a given
|
||||
receiver.
|
||||
|
||||
Note:
|
||||
if receiver is itself a weak reference (a callable),
|
||||
it will be de-referenced by the system's machinery,
|
||||
so *generally* weak references are not suitable as
|
||||
receivers, though some use might be found for the
|
||||
facility whereby a higher-level library passes in
|
||||
pre-weakrefed receiver references.
|
||||
|
||||
signal -- the signal to which the receiver should respond
|
||||
|
||||
if Any, receiver will receive any signal from the
|
||||
indicated sender (which might also be Any, but is not
|
||||
necessarily Any).
|
||||
|
||||
Otherwise must be a hashable Python object other than
|
||||
None (DispatcherError raised on None).
|
||||
|
||||
sender -- the sender to which the receiver should respond
|
||||
|
||||
if Any, receiver will receive the indicated signals
|
||||
from any sender.
|
||||
|
||||
if Anonymous, receiver will only receive indicated
|
||||
signals from send/sendExact which do not specify a
|
||||
sender, or specify Anonymous explicitly as the sender.
|
||||
|
||||
Otherwise can be any python object.
|
||||
|
||||
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.
|
||||
|
||||
returns None, may raise DispatcherTypeError
|
||||
Internal attributes:
|
||||
receivers -- { receriverkey (id) : weakref(receiver) }
|
||||
"""
|
||||
if signal is None:
|
||||
raise errors.DispatcherTypeError(
|
||||
'Signal cannot be None (receiver=%r sender=%r)' % (receiver, sender)
|
||||
)
|
||||
if weak:
|
||||
receiver = saferef.safeRef(receiver, onDelete=_removeReceiver)
|
||||
senderkey = id(sender)
|
||||
|
||||
signals = connections.setdefault(senderkey, {})
|
||||
def __init__(self, providing_args=None):
|
||||
"""providing_args -- A list of the arguments this signal can pass along in
|
||||
a send() call.
|
||||
"""
|
||||
self.receivers = []
|
||||
if providing_args is None:
|
||||
providing_args = []
|
||||
self.providing_args = set(providing_args)
|
||||
|
||||
# Keep track of senders for cleanup.
|
||||
# Is Anonymous something we want to clean up?
|
||||
if sender not in (None, Anonymous, Any):
|
||||
def remove(object, senderkey=senderkey):
|
||||
_removeSender(senderkey=senderkey)
|
||||
# Skip objects that can not be weakly referenced, which means
|
||||
# they won't be automatically cleaned up, but that's too bad.
|
||||
try:
|
||||
weakSender = weakref.ref(sender, remove)
|
||||
senders[senderkey] = weakSender
|
||||
except:
|
||||
pass
|
||||
def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
|
||||
"""Connect receiver to sender for signal
|
||||
|
||||
receiverID = id(receiver)
|
||||
# get current set, remove any current references to
|
||||
# this receiver in the set, including back-references
|
||||
if signals.has_key(signal):
|
||||
receivers = signals[signal]
|
||||
_removeOldBackRefs(senderkey, signal, receiver, receivers)
|
||||
else:
|
||||
receivers = signals[signal] = []
|
||||
try:
|
||||
current = sendersBack.get(receiverID)
|
||||
if current is None:
|
||||
sendersBack[ receiverID ] = current = []
|
||||
if senderkey not in current:
|
||||
current.append(senderkey)
|
||||
except:
|
||||
pass
|
||||
receiver -- a function or an instance method which is to
|
||||
receive signals. Receivers must be
|
||||
hashable objects.
|
||||
|
||||
receivers.append(receiver)
|
||||
if weak is True, then receiver must be weak-referencable
|
||||
(more precisely saferef.safeRef() must be able to create
|
||||
a reference to the receiver).
|
||||
|
||||
Receivers must be able to accept keyword arguments.
|
||||
|
||||
If receivers have a dispatch_uid attribute, the receiver will
|
||||
not be added if another receiver already exists with that
|
||||
dispatch_uid.
|
||||
|
||||
def disconnect(receiver, signal=Any, sender=Any, weak=True):
|
||||
"""Disconnect receiver from sender for signal
|
||||
sender -- the sender to which the receiver should respond
|
||||
Must either be of type Signal, or None to receive events
|
||||
from any sender.
|
||||
|
||||
receiver -- the registered receiver to disconnect
|
||||
signal -- the registered signal to disconnect
|
||||
sender -- the registered sender to disconnect
|
||||
weak -- the weakref state to disconnect
|
||||
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.
|
||||
|
||||
disconnect reverses the process of connect,
|
||||
the semantics for the individual elements are
|
||||
logically equivalent to a tuple of
|
||||
(receiver, signal, sender, weak) used as a key
|
||||
to be deleted from the internal routing tables.
|
||||
(The actual process is slightly more complex
|
||||
but the semantics are basically the same).
|
||||
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.
|
||||
|
||||
Note:
|
||||
Using disconnect is not required to cleanup
|
||||
routing when an object is deleted, the framework
|
||||
will remove routes for deleted objects
|
||||
automatically. It's only necessary to disconnect
|
||||
if you want to stop routing to a live object.
|
||||
returns None
|
||||
"""
|
||||
from django.conf import settings
|
||||
|
||||
returns None, may raise DispatcherTypeError or
|
||||
DispatcherKeyError
|
||||
"""
|
||||
if signal is None:
|
||||
raise errors.DispatcherTypeError(
|
||||
'Signal cannot be None (receiver=%r sender=%r)' % (receiver, sender)
|
||||
)
|
||||
if weak: receiver = saferef.safeRef(receiver)
|
||||
senderkey = id(sender)
|
||||
try:
|
||||
signals = connections[senderkey]
|
||||
receivers = signals[signal]
|
||||
except KeyError:
|
||||
raise errors.DispatcherKeyError(
|
||||
"""No receivers found for signal %r from sender %r""" %(
|
||||
signal,
|
||||
sender
|
||||
)
|
||||
)
|
||||
try:
|
||||
# also removes from receivers
|
||||
_removeOldBackRefs(senderkey, signal, receiver, receivers)
|
||||
except ValueError:
|
||||
raise errors.DispatcherKeyError(
|
||||
"""No connection to receiver %s for signal %s from sender %s""" %(
|
||||
receiver,
|
||||
signal,
|
||||
sender
|
||||
)
|
||||
)
|
||||
_cleanupConnections(senderkey, signal)
|
||||
if settings.DEBUG:
|
||||
import inspect
|
||||
assert inspect.getargspec(receiver)[2] is not None, \
|
||||
"Signal receivers must accept keyword arguments (**kwargs)."
|
||||
|
||||
def getReceivers(sender=Any, signal=Any):
|
||||
"""Get list of receivers from global tables
|
||||
|
||||
This utility function allows you to retrieve the
|
||||
raw list of receivers from the connections table
|
||||
for the given sender and signal pair.
|
||||
|
||||
Note:
|
||||
there is no guarantee that this is the actual list
|
||||
stored in the connections table, so the value
|
||||
should be treated as a simple iterable/truth value
|
||||
rather than, for instance a list to which you
|
||||
might append new records.
|
||||
|
||||
Normally you would use liveReceivers(getReceivers(...))
|
||||
to retrieve the actual receiver objects as an iterable
|
||||
object.
|
||||
"""
|
||||
existing = connections.get(id(sender))
|
||||
if existing is not None:
|
||||
return existing.get(signal, [])
|
||||
return []
|
||||
|
||||
def liveReceivers(receivers):
|
||||
"""Filter sequence of receivers to get resolved, live receivers
|
||||
|
||||
This is a generator which will iterate over
|
||||
the passed sequence, checking for weak references
|
||||
and resolving them, then returning all live
|
||||
receivers.
|
||||
"""
|
||||
for receiver in receivers:
|
||||
if isinstance(receiver, WEAKREF_TYPES):
|
||||
# Dereference the weak reference.
|
||||
receiver = receiver()
|
||||
if receiver is not None:
|
||||
yield receiver
|
||||
if dispatch_uid:
|
||||
lookup_key = (dispatch_uid, _make_id(sender))
|
||||
else:
|
||||
yield receiver
|
||||
lookup_key = (_make_id(receiver), _make_id(sender))
|
||||
|
||||
if weak:
|
||||
receiver = saferef.safeRef(receiver, onDelete=self._remove_receiver)
|
||||
|
||||
|
||||
def getAllReceivers(sender=Any, signal=Any):
|
||||
"""Get list of all receivers from global tables
|
||||
|
||||
This gets all dereferenced receivers which should receive
|
||||
the given signal from sender, each receiver should
|
||||
be produced only once by the resulting generator
|
||||
"""
|
||||
receivers = {}
|
||||
# Get receivers that receive *this* signal from *this* sender.
|
||||
# Add receivers that receive *any* signal from *this* sender.
|
||||
# Add receivers that receive *this* signal from *any* sender.
|
||||
# Add receivers that receive *any* signal from *any* sender.
|
||||
l = []
|
||||
i = id(sender)
|
||||
if i in connections:
|
||||
sender_receivers = connections[i]
|
||||
if signal in sender_receivers:
|
||||
l.extend(sender_receivers[signal])
|
||||
if signal is not Any and Any in sender_receivers:
|
||||
l.extend(sender_receivers[Any])
|
||||
|
||||
if sender is not Any:
|
||||
i = id(Any)
|
||||
if i in connections:
|
||||
sender_receivers = connections[i]
|
||||
if sender_receivers is not None:
|
||||
if signal in sender_receivers:
|
||||
l.extend(sender_receivers[signal])
|
||||
if signal is not Any and Any in sender_receivers:
|
||||
l.extend(sender_receivers[Any])
|
||||
|
||||
for receiver in l:
|
||||
try:
|
||||
if not receiver in receivers:
|
||||
if isinstance(receiver, WEAKREF_TYPES):
|
||||
receiver = receiver()
|
||||
# this should only (rough guess) be possible if somehow, deref'ing
|
||||
# triggered a wipe.
|
||||
if receiver is None:
|
||||
continue
|
||||
receivers[receiver] = 1
|
||||
yield receiver
|
||||
except TypeError:
|
||||
# dead weakrefs raise TypeError on hash...
|
||||
pass
|
||||
|
||||
def send(signal=Any, sender=Anonymous, *arguments, **named):
|
||||
"""Send signal from sender to all connected receivers.
|
||||
|
||||
signal -- (hashable) signal value, see connect for details
|
||||
|
||||
sender -- the sender of the signal
|
||||
|
||||
if Any, only receivers registered for Any will receive
|
||||
the message.
|
||||
|
||||
if Anonymous, only receivers registered to receive
|
||||
messages from Anonymous or Any will receive the message
|
||||
|
||||
Otherwise can be any python object (normally one
|
||||
registered with a connect if you actually want
|
||||
something to occur).
|
||||
|
||||
arguments -- positional arguments which will be passed to
|
||||
*all* receivers. Note that this may raise TypeErrors
|
||||
if the receivers do not allow the particular arguments.
|
||||
Note also that arguments are applied before named
|
||||
arguments, so they should be used with care.
|
||||
|
||||
named -- named arguments which will be filtered according
|
||||
to the parameters of the receivers to only provide those
|
||||
acceptable to the receiver.
|
||||
|
||||
Return a list of tuple pairs [(receiver, response), ... ]
|
||||
|
||||
if any receiver raises an error, the error propagates back
|
||||
through send, terminating the dispatch loop, so it is quite
|
||||
possible to not have all receivers called if a raises an
|
||||
error.
|
||||
"""
|
||||
# Call each receiver with whatever arguments it can accept.
|
||||
# Return a list of tuple pairs [(receiver, response), ... ].
|
||||
responses = []
|
||||
for receiver in getAllReceivers(sender, signal):
|
||||
response = robustapply.robustApply(
|
||||
receiver,
|
||||
signal=signal,
|
||||
sender=sender,
|
||||
*arguments,
|
||||
**named
|
||||
)
|
||||
responses.append((receiver, response))
|
||||
return responses
|
||||
|
||||
|
||||
def sendExact(signal=Any, sender=Anonymous, *arguments, **named ):
|
||||
"""Send signal only to those receivers registered for exact message
|
||||
|
||||
sendExact allows for avoiding Any/Anonymous registered
|
||||
handlers, sending only to those receivers explicitly
|
||||
registered for a particular signal on a particular
|
||||
sender.
|
||||
"""
|
||||
responses = []
|
||||
for receiver in liveReceivers(getReceivers(sender, signal)):
|
||||
response = robustapply.robustApply(
|
||||
receiver,
|
||||
signal=signal,
|
||||
sender=sender,
|
||||
*arguments,
|
||||
**named
|
||||
)
|
||||
responses.append((receiver, response))
|
||||
return responses
|
||||
|
||||
|
||||
def _removeReceiver(receiver):
|
||||
"""Remove receiver from connections."""
|
||||
if not sendersBack:
|
||||
# During module cleanup the mapping will be replaced with None
|
||||
return False
|
||||
backKey = id(receiver)
|
||||
for senderkey in sendersBack.get(backKey,()):
|
||||
try:
|
||||
signals = connections[senderkey].keys()
|
||||
except KeyError,err:
|
||||
pass
|
||||
for r_key, _ in self.receivers:
|
||||
if r_key == lookup_key:
|
||||
break
|
||||
else:
|
||||
for signal in signals:
|
||||
try:
|
||||
receivers = connections[senderkey][signal]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
receivers.remove(receiver)
|
||||
except Exception, err:
|
||||
pass
|
||||
_cleanupConnections(senderkey, signal)
|
||||
try:
|
||||
del sendersBack[ backKey ]
|
||||
except KeyError:
|
||||
pass
|
||||
self.receivers.append((lookup_key, receiver))
|
||||
|
||||
def _cleanupConnections(senderkey, signal):
|
||||
"""Delete any empty signals for senderkey. Delete senderkey if empty."""
|
||||
try:
|
||||
receivers = connections[senderkey][signal]
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if not receivers:
|
||||
# No more connected receivers. Therefore, remove the signal.
|
||||
def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None):
|
||||
"""Disconnect receiver from sender for signal
|
||||
|
||||
receiver -- the registered receiver to disconnect. May be none if
|
||||
dispatch_uid is specified.
|
||||
sender -- the registered sender to disconnect
|
||||
weak -- the weakref state to disconnect
|
||||
dispatch_uid -- the unique identifier of the receiver to disconnect
|
||||
|
||||
disconnect reverses the process of connect.
|
||||
|
||||
If weak references are used, disconnect need not be called.
|
||||
The receiver will be remove from dispatch automatically.
|
||||
|
||||
returns None
|
||||
"""
|
||||
|
||||
if dispatch_uid:
|
||||
lookup_key = (dispatch_uid, _make_id(sender))
|
||||
else:
|
||||
lookup_key = (_make_id(receiver), _make_id(sender))
|
||||
|
||||
for idx, (r_key, _) in enumerate(self.receivers):
|
||||
if r_key == lookup_key:
|
||||
del self.receivers[idx]
|
||||
|
||||
def send(self, sender, **named):
|
||||
"""Send signal from sender to all connected receivers.
|
||||
|
||||
sender -- the sender of the signal
|
||||
Either a specific object or None.
|
||||
|
||||
named -- named arguments which will be passed to receivers.
|
||||
|
||||
Returns a list of tuple pairs [(receiver, response), ... ].
|
||||
|
||||
If any receiver raises an error, the error propagates back
|
||||
through send, terminating the dispatch loop, so it is quite
|
||||
possible to not have all receivers called if a raises an
|
||||
error.
|
||||
"""
|
||||
|
||||
responses = []
|
||||
if not self.receivers:
|
||||
return responses
|
||||
|
||||
for receiver in self._live_receivers(_make_id(sender)):
|
||||
response = receiver(signal=self, sender=sender, **named)
|
||||
responses.append((receiver, response))
|
||||
return responses
|
||||
|
||||
def send_robust(self, sender, **named):
|
||||
"""Send signal from sender to all connected receivers catching errors
|
||||
|
||||
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.
|
||||
These arguments must be a subset of the argument names
|
||||
defined in providing_args.
|
||||
|
||||
Return a list of tuple pairs [(receiver, response), ... ],
|
||||
may raise DispatcherKeyError
|
||||
|
||||
if any receiver raises an error (specifically any subclass of Exception),
|
||||
the error instance is returned as the result for that receiver.
|
||||
"""
|
||||
|
||||
responses = []
|
||||
if not self.receivers:
|
||||
return responses
|
||||
|
||||
# Call each receiver with whatever arguments it can accept.
|
||||
# Return a list of tuple pairs [(receiver, response), ... ].
|
||||
for receiver in self._live_receivers(_make_id(sender)):
|
||||
try:
|
||||
signals = connections[senderkey]
|
||||
except KeyError:
|
||||
pass
|
||||
response = receiver(signal=self, sender=sender, **named)
|
||||
except Exception, err:
|
||||
responses.append((receiver, err))
|
||||
else:
|
||||
del signals[signal]
|
||||
if not signals:
|
||||
# No more signal connections. Therefore, remove the sender.
|
||||
_removeSender(senderkey)
|
||||
responses.append((receiver, response))
|
||||
return responses
|
||||
|
||||
def _removeSender(senderkey):
|
||||
"""Remove senderkey from connections."""
|
||||
_removeBackrefs(senderkey)
|
||||
def _live_receivers(self, senderkey):
|
||||
"""Filter sequence of receivers to get resolved, live receivers
|
||||
|
||||
connections.pop(senderkey, None)
|
||||
senders.pop(senderkey, None)
|
||||
This checks for weak references
|
||||
and resolves them, then returning only live
|
||||
receivers.
|
||||
"""
|
||||
none_senderkey = _make_id(None)
|
||||
|
||||
for (receiverkey, r_senderkey), receiver in self.receivers:
|
||||
if r_senderkey == none_senderkey or r_senderkey == senderkey:
|
||||
if isinstance(receiver, WEAKREF_TYPES):
|
||||
# Dereference the weak reference.
|
||||
receiver = receiver()
|
||||
if receiver is not None:
|
||||
yield receiver
|
||||
else:
|
||||
yield receiver
|
||||
|
||||
def _removeBackrefs(senderkey):
|
||||
"""Remove all back-references to this senderkey"""
|
||||
for receiver_list in connections.pop(senderkey, {}).values():
|
||||
for receiver in receiver_list:
|
||||
_killBackref(receiver, senderkey)
|
||||
def _remove_receiver(self, receiver):
|
||||
"""Remove dead receivers from connections."""
|
||||
|
||||
to_remove = []
|
||||
for key, connected_receiver in self.receivers:
|
||||
if connected_receiver == receiver:
|
||||
to_remove.append(key)
|
||||
for key in to_remove:
|
||||
for idx, (r_key, _) in enumerate(self.receivers):
|
||||
if r_key == key:
|
||||
del self.receivers[idx]
|
||||
|
||||
def _removeOldBackRefs(senderkey, signal, receiver, receivers):
|
||||
"""Kill old sendersBack references from receiver
|
||||
|
||||
This guards against multiple registration of the same
|
||||
receiver for a given signal and sender leaking memory
|
||||
as old back reference records build up.
|
||||
|
||||
Also removes old receiver instance from receivers
|
||||
def connect(receiver, signal, sender=None, weak=True):
|
||||
"""
|
||||
try:
|
||||
index = receivers.index(receiver)
|
||||
# need to scan back references here and remove senderkey
|
||||
except ValueError:
|
||||
return False
|
||||
else:
|
||||
oldReceiver = receivers[index]
|
||||
del receivers[index]
|
||||
found = 0
|
||||
signals = connections.get(signal)
|
||||
if signals is not None:
|
||||
for sig,recs in connections.get(signal,{}).iteritems():
|
||||
if sig != signal:
|
||||
for rec in recs:
|
||||
if rec is oldReceiver:
|
||||
found = 1
|
||||
break
|
||||
if not found:
|
||||
_killBackref(oldReceiver, senderkey)
|
||||
return True
|
||||
return False
|
||||
For backward compatibility only. See Signal.connect()
|
||||
"""
|
||||
warnings.warn(
|
||||
category = DeprecationWarning,
|
||||
message = "dispatcher.connect() is deprecated; use Signal.connect() instead.",
|
||||
stacklevel = 2
|
||||
)
|
||||
return signal.connect(receiver, sender, weak)
|
||||
|
||||
def disconnect(receiver, signal, sender=None, weak=True):
|
||||
"""
|
||||
For backward compatibility only. See Signal.disconnect()
|
||||
"""
|
||||
warnings.warn(
|
||||
category = DeprecationWarning,
|
||||
message = "dispatcher.disconnect() is deprecated; use Signal.disconnect() instead.",
|
||||
stacklevel = 2
|
||||
)
|
||||
signal.disconnect(receiver, sender, weak)
|
||||
|
||||
def _killBackref(receiver, senderkey):
|
||||
"""Do the actual removal of back reference from receiver to senderkey"""
|
||||
receiverkey = id(receiver)
|
||||
receivers_list = sendersBack.get(receiverkey, ())
|
||||
while senderkey in receivers_list:
|
||||
try:
|
||||
receivers_list.remove(senderkey)
|
||||
except:
|
||||
break
|
||||
if not receivers_list:
|
||||
try:
|
||||
del sendersBack[ receiverkey ]
|
||||
except KeyError:
|
||||
pass
|
||||
return True
|
||||
def send(signal, sender=None, **named):
|
||||
"""
|
||||
For backward compatibility only. See Signal.send()
|
||||
"""
|
||||
warnings.warn(
|
||||
category = DeprecationWarning,
|
||||
message = "dispatcher.send() is deprecated; use Signal.send() instead.",
|
||||
stacklevel = 2
|
||||
)
|
||||
return signal.send(sender=sender, **named)
|
||||
|
||||
def sendExact(signal, sender, **named ):
|
||||
"""
|
||||
This function is deprecated, as it now has the same meaning as send.
|
||||
"""
|
||||
warnings.warn(
|
||||
category = DeprecationWarning,
|
||||
message = "dispatcher.sendExact() is deprecated; use Signal.send() instead.",
|
||||
stacklevel = 2
|
||||
)
|
||||
return signal.send(sender=sender, **named)
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
"""Error types for dispatcher mechanism
|
||||
"""
|
||||
|
||||
class DispatcherError(Exception):
|
||||
"""Base class for all Dispatcher errors"""
|
||||
class DispatcherKeyError(KeyError, DispatcherError):
|
||||
"""Error raised when unknown (sender,signal) set specified"""
|
||||
class DispatcherTypeError(TypeError, DispatcherError):
|
||||
"""Error raised when inappropriate signal-type specified (None)"""
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
PyDispatcher License
|
||||
django.dispatch was originally forked from PyDispatcher.
|
||||
|
||||
PyDispatcher License:
|
||||
|
||||
Copyright (c) 2001-2003, Patrick K. O'Brien and Contributors
|
||||
All rights reserved.
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
"""Module implementing error-catching version of send (sendRobust)"""
|
||||
from django.dispatch.dispatcher import Any, Anonymous, liveReceivers, getAllReceivers
|
||||
from django.dispatch.robustapply import robustApply
|
||||
|
||||
def sendRobust(
|
||||
signal=Any,
|
||||
sender=Anonymous,
|
||||
*arguments, **named
|
||||
):
|
||||
"""Send signal from sender to all connected receivers catching errors
|
||||
|
||||
signal -- (hashable) signal value, see connect for details
|
||||
|
||||
sender -- the sender of the signal
|
||||
|
||||
if Any, only receivers registered for Any will receive
|
||||
the message.
|
||||
|
||||
if Anonymous, only receivers registered to receive
|
||||
messages from Anonymous or Any will receive the message
|
||||
|
||||
Otherwise can be any python object (normally one
|
||||
registered with a connect if you actually want
|
||||
something to occur).
|
||||
|
||||
arguments -- positional arguments which will be passed to
|
||||
*all* receivers. Note that this may raise TypeErrors
|
||||
if the receivers do not allow the particular arguments.
|
||||
Note also that arguments are applied before named
|
||||
arguments, so they should be used with care.
|
||||
|
||||
named -- named arguments which will be filtered according
|
||||
to the parameters of the receivers to only provide those
|
||||
acceptable to the receiver.
|
||||
|
||||
Return a list of tuple pairs [(receiver, response), ... ]
|
||||
|
||||
if any receiver raises an error (specifically any subclass of Exception),
|
||||
the error instance is returned as the result for that receiver.
|
||||
"""
|
||||
# Call each receiver with whatever arguments it can accept.
|
||||
# Return a list of tuple pairs [(receiver, response), ... ].
|
||||
responses = []
|
||||
for receiver in liveReceivers(getAllReceivers(sender, signal)):
|
||||
try:
|
||||
response = robustApply(
|
||||
receiver,
|
||||
signal=signal,
|
||||
sender=sender,
|
||||
*arguments,
|
||||
**named
|
||||
)
|
||||
except Exception, err:
|
||||
responses.append((receiver, err))
|
||||
else:
|
||||
responses.append((receiver, response))
|
||||
return responses
|
|
@ -1,47 +0,0 @@
|
|||
"""Robust apply mechanism
|
||||
|
||||
Provides a function "call", which can sort out
|
||||
what arguments a given callable object can take,
|
||||
and subset the given arguments to match only
|
||||
those which are acceptable.
|
||||
"""
|
||||
|
||||
def function( receiver ):
|
||||
"""Get function-like callable object for given receiver
|
||||
|
||||
returns (function_or_method, codeObject, fromMethod)
|
||||
|
||||
If fromMethod is true, then the callable already
|
||||
has its first argument bound
|
||||
"""
|
||||
if hasattr(receiver, '__call__'):
|
||||
# receiver is a class instance; assume it is callable.
|
||||
# Reassign receiver to the actual method that will be called.
|
||||
if hasattr( receiver.__call__, 'im_func') or hasattr( receiver.__call__, 'im_code'):
|
||||
receiver = receiver.__call__
|
||||
if hasattr( receiver, 'im_func' ):
|
||||
# an instance-method...
|
||||
return receiver, receiver.im_func.func_code, 1
|
||||
elif not hasattr( receiver, 'func_code'):
|
||||
raise ValueError('unknown reciever type %s %s'%(receiver, type(receiver)))
|
||||
return receiver, receiver.func_code, 0
|
||||
|
||||
def robustApply(receiver, *arguments, **named):
|
||||
"""Call receiver with arguments and an appropriate subset of named
|
||||
"""
|
||||
receiver, codeObject, startIndex = function( receiver )
|
||||
acceptable = codeObject.co_varnames[startIndex+len(arguments):codeObject.co_argcount]
|
||||
for name in codeObject.co_varnames[startIndex:startIndex+len(arguments)]:
|
||||
if named.has_key( name ):
|
||||
raise TypeError(
|
||||
"""Argument %r specified both positionally and as a keyword for calling %r"""% (
|
||||
name, receiver,
|
||||
)
|
||||
)
|
||||
if not (codeObject.co_flags & 8):
|
||||
# fc does not have a **kwds type parameter, therefore
|
||||
# remove unacceptable arguments.
|
||||
for arg in named.keys():
|
||||
if arg not in acceptable:
|
||||
del named[arg]
|
||||
return receiver(*arguments, **named)
|
|
@ -1,4 +1,10 @@
|
|||
"""Refactored "safe reference" from dispatcher.py"""
|
||||
"""
|
||||
"Safe weakrefs", originally from pyDispatcher.
|
||||
|
||||
Provides a way to safely weakref any function, including bound methods (which
|
||||
aren't handled by the core weakref module).
|
||||
"""
|
||||
|
||||
import weakref, traceback
|
||||
|
||||
def safeRef(target, onDelete = None):
|
||||
|
@ -60,7 +66,9 @@ class BoundMethodWeakref(object):
|
|||
same BoundMethodWeakref instance.
|
||||
|
||||
"""
|
||||
|
||||
_allInstances = weakref.WeakValueDictionary()
|
||||
|
||||
def __new__( cls, target, onDelete=None, *arguments,**named ):
|
||||
"""Create new instance or return current instance
|
||||
|
||||
|
@ -83,6 +91,7 @@ class BoundMethodWeakref(object):
|
|||
cls._allInstances[key] = base
|
||||
base.__init__( target, onDelete, *arguments,**named)
|
||||
return base
|
||||
|
||||
def __init__(self, target, onDelete=None):
|
||||
"""Return a weak-reference-like instance for a bound method
|
||||
|
||||
|
@ -122,6 +131,7 @@ class BoundMethodWeakref(object):
|
|||
self.weakFunc = weakref.ref(target.im_func, remove)
|
||||
self.selfName = str(target.im_self)
|
||||
self.funcName = str(target.im_func.__name__)
|
||||
|
||||
def calculateKey( cls, target ):
|
||||
"""Calculate the reference key for this reference
|
||||
|
||||
|
@ -130,6 +140,7 @@ class BoundMethodWeakref(object):
|
|||
"""
|
||||
return (id(target.im_self),id(target.im_func))
|
||||
calculateKey = classmethod( calculateKey )
|
||||
|
||||
def __str__(self):
|
||||
"""Give a friendly representation of the object"""
|
||||
return """%s( %s.%s )"""%(
|
||||
|
@ -137,15 +148,19 @@ class BoundMethodWeakref(object):
|
|||
self.selfName,
|
||||
self.funcName,
|
||||
)
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
def __nonzero__( self ):
|
||||
"""Whether we are still a valid reference"""
|
||||
return self() is not None
|
||||
|
||||
def __cmp__( self, other ):
|
||||
"""Compare with another reference"""
|
||||
if not isinstance (other,self.__class__):
|
||||
return cmp( self.__class__, type(other) )
|
||||
return cmp( self.key, other.key)
|
||||
|
||||
def __call__(self):
|
||||
"""Return a strong reference to the bound method
|
||||
|
||||
|
@ -224,7 +239,6 @@ class BoundNonDescriptorMethodWeakref(BoundMethodWeakref):
|
|||
return getattr(target, function.__name__)
|
||||
return None
|
||||
|
||||
|
||||
def get_bound_method_weakref(target, onDelete):
|
||||
"""Instantiates the appropiate BoundMethodWeakRef, depending on the details of
|
||||
the underlying class method implementation"""
|
||||
|
@ -234,4 +248,3 @@ def get_bound_method_weakref(target, onDelete):
|
|||
else:
|
||||
# no luck, use the alternative implementation:
|
||||
return BoundNonDescriptorMethodWeakref(target=target, onDelete=onDelete)
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ from django.contrib.auth import authenticate, login
|
|||
from django.core.handlers.base import BaseHandler
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
from django.core.signals import got_request_exception
|
||||
from django.dispatch import dispatcher
|
||||
from django.http import SimpleCookie, HttpRequest
|
||||
from django.template import TemplateDoesNotExist
|
||||
from django.test import signals
|
||||
|
@ -59,7 +58,7 @@ class ClientHandler(BaseHandler):
|
|||
if self._request_middleware is None:
|
||||
self.load_middleware()
|
||||
|
||||
dispatcher.send(signal=signals.request_started)
|
||||
signals.request_started.send(sender=self.__class__)
|
||||
try:
|
||||
request = WSGIRequest(environ)
|
||||
response = self.get_response(request)
|
||||
|
@ -69,11 +68,11 @@ class ClientHandler(BaseHandler):
|
|||
response = middleware_method(request, response)
|
||||
response = self.apply_response_fixes(request, response)
|
||||
finally:
|
||||
dispatcher.send(signal=signals.request_finished)
|
||||
signals.request_finished.send(sender=self.__class__)
|
||||
|
||||
return response
|
||||
|
||||
def store_rendered_templates(store, signal, sender, template, context):
|
||||
def store_rendered_templates(store, signal, sender, template, context, **kwargs):
|
||||
"""
|
||||
Stores templates and contexts that are rendered.
|
||||
"""
|
||||
|
@ -160,7 +159,7 @@ class Client:
|
|||
self.cookies = SimpleCookie()
|
||||
self.exc_info = None
|
||||
|
||||
def store_exc_info(self, *args, **kwargs):
|
||||
def store_exc_info(self, **kwargs):
|
||||
"""
|
||||
Stores exceptions when they are generated by a view.
|
||||
"""
|
||||
|
@ -202,10 +201,10 @@ class Client:
|
|||
# callback function.
|
||||
data = {}
|
||||
on_template_render = curry(store_rendered_templates, data)
|
||||
dispatcher.connect(on_template_render, signal=signals.template_rendered)
|
||||
signals.template_rendered.connect(on_template_render)
|
||||
|
||||
# Capture exceptions created by the handler.
|
||||
dispatcher.connect(self.store_exc_info, signal=got_request_exception)
|
||||
got_request_exception.connect(self.store_exc_info)
|
||||
|
||||
try:
|
||||
response = self.handler(environ)
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
template_rendered = object()
|
||||
from django.dispatch import Signal
|
||||
|
||||
template_rendered = Signal(providing_args=["template", "context"])
|
||||
|
|
|
@ -3,7 +3,6 @@ from django.conf import settings
|
|||
from django.db import connection, get_creation_module
|
||||
from django.core import mail
|
||||
from django.core.management import call_command
|
||||
from django.dispatch import dispatcher
|
||||
from django.test import signals
|
||||
from django.template import Template
|
||||
from django.utils.translation import deactivate
|
||||
|
@ -17,7 +16,7 @@ def instrumented_test_render(self, context):
|
|||
An instrumented Template render method, providing a signal
|
||||
that can be intercepted by the test system Client
|
||||
"""
|
||||
dispatcher.send(signal=signals.template_rendered, sender=self, template=self, context=context)
|
||||
signals.template_rendered.send(sender=self, template=self, context=context)
|
||||
return self.nodelist.render(context)
|
||||
|
||||
class TestSMTPConnection(object):
|
||||
|
|
|
@ -3,7 +3,6 @@ Testing signals before/after saving and deleting.
|
|||
"""
|
||||
|
||||
from django.db import models
|
||||
from django.dispatch import dispatcher
|
||||
|
||||
class Person(models.Model):
|
||||
first_name = models.CharField(max_length=20)
|
||||
|
@ -12,19 +11,12 @@ class Person(models.Model):
|
|||
def __unicode__(self):
|
||||
return u"%s %s" % (self.first_name, self.last_name)
|
||||
|
||||
|
||||
def pre_save_nokwargs_test(sender, instance):
|
||||
print 'pre_save_nokwargs signal'
|
||||
|
||||
def post_save_nokwargs_test(sender, instance):
|
||||
print 'post_save_nokwargs signal'
|
||||
|
||||
def pre_save_test(sender, instance, **kwargs):
|
||||
def pre_save_test(signal, sender, instance, **kwargs):
|
||||
print 'pre_save signal,', instance
|
||||
if kwargs.get('raw'):
|
||||
print 'Is raw'
|
||||
|
||||
def post_save_test(sender, instance, **kwargs):
|
||||
def post_save_test(signal, sender, instance, **kwargs):
|
||||
print 'post_save signal,', instance
|
||||
if 'created' in kwargs:
|
||||
if kwargs['created']:
|
||||
|
@ -34,44 +26,36 @@ def post_save_test(sender, instance, **kwargs):
|
|||
if kwargs.get('raw'):
|
||||
print 'Is raw'
|
||||
|
||||
def pre_delete_test(sender, instance, **kwargs):
|
||||
def pre_delete_test(signal, sender, instance, **kwargs):
|
||||
print 'pre_delete signal,', instance
|
||||
print 'instance.id is not None: %s' % (instance.id != None)
|
||||
|
||||
def post_delete_test(sender, instance, **kwargs):
|
||||
def post_delete_test(signal, sender, instance, **kwargs):
|
||||
print 'post_delete signal,', instance
|
||||
print 'instance.id is None: %s' % (instance.id == None)
|
||||
|
||||
__test__ = {'API_TESTS':"""
|
||||
>>> dispatcher.connect(pre_save_nokwargs_test, signal=models.signals.pre_save)
|
||||
>>> dispatcher.connect(post_save_nokwargs_test, signal=models.signals.post_save)
|
||||
>>> dispatcher.connect(pre_save_test, signal=models.signals.pre_save)
|
||||
>>> dispatcher.connect(post_save_test, signal=models.signals.post_save)
|
||||
>>> dispatcher.connect(pre_delete_test, signal=models.signals.pre_delete)
|
||||
>>> dispatcher.connect(post_delete_test, signal=models.signals.post_delete)
|
||||
>>> models.signals.pre_save.connect(pre_save_test)
|
||||
>>> models.signals.post_save.connect(post_save_test)
|
||||
>>> models.signals.pre_delete.connect(pre_delete_test)
|
||||
>>> models.signals.post_delete.connect(post_delete_test)
|
||||
|
||||
>>> p1 = Person(first_name='John', last_name='Smith')
|
||||
>>> p1.save()
|
||||
pre_save_nokwargs signal
|
||||
pre_save signal, John Smith
|
||||
post_save_nokwargs signal
|
||||
post_save signal, John Smith
|
||||
Is created
|
||||
|
||||
>>> p1.first_name = 'Tom'
|
||||
>>> p1.save()
|
||||
pre_save_nokwargs signal
|
||||
pre_save signal, Tom Smith
|
||||
post_save_nokwargs signal
|
||||
post_save signal, Tom Smith
|
||||
Is updated
|
||||
|
||||
# Calling an internal method purely so that we can trigger a "raw" save.
|
||||
>>> p1.save_base(raw=True)
|
||||
pre_save_nokwargs signal
|
||||
pre_save signal, Tom Smith
|
||||
Is raw
|
||||
post_save_nokwargs signal
|
||||
post_save signal, Tom Smith
|
||||
Is updated
|
||||
Is raw
|
||||
|
@ -85,17 +69,13 @@ instance.id is None: False
|
|||
>>> p2 = Person(first_name='James', last_name='Jones')
|
||||
>>> p2.id = 99999
|
||||
>>> p2.save()
|
||||
pre_save_nokwargs signal
|
||||
pre_save signal, James Jones
|
||||
post_save_nokwargs signal
|
||||
post_save signal, James Jones
|
||||
Is created
|
||||
|
||||
>>> p2.id = 99998
|
||||
>>> p2.save()
|
||||
pre_save_nokwargs signal
|
||||
pre_save signal, James Jones
|
||||
post_save_nokwargs signal
|
||||
post_save signal, James Jones
|
||||
Is created
|
||||
|
||||
|
@ -108,10 +88,8 @@ instance.id is None: False
|
|||
>>> Person.objects.all()
|
||||
[<Person: James Jones>]
|
||||
|
||||
>>> dispatcher.disconnect(pre_save_nokwargs_test, signal=models.signals.pre_save)
|
||||
>>> dispatcher.disconnect(post_save_nokwargs_test, signal=models.signals.post_save)
|
||||
>>> dispatcher.disconnect(post_delete_test, signal=models.signals.post_delete)
|
||||
>>> dispatcher.disconnect(pre_delete_test, signal=models.signals.pre_delete)
|
||||
>>> dispatcher.disconnect(post_save_test, signal=models.signals.post_save)
|
||||
>>> dispatcher.disconnect(pre_save_test, signal=models.signals.pre_save)
|
||||
>>> models.signals.post_delete.disconnect(post_delete_test)
|
||||
>>> models.signals.pre_delete.disconnect(pre_delete_test)
|
||||
>>> models.signals.post_save.disconnect(post_save_test)
|
||||
>>> models.signals.pre_save.disconnect(pre_save_test)
|
||||
"""}
|
||||
|
|
|
@ -2,6 +2,5 @@
|
|||
Unit-tests for the dispatch project
|
||||
"""
|
||||
|
||||
from test_dispatcher import *
|
||||
from test_robustapply import *
|
||||
from test_saferef import *
|
||||
from test_dispatcher import *
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from django.dispatch.dispatcher import *
|
||||
from django.dispatch import dispatcher, robust
|
||||
from django.dispatch import Signal
|
||||
import unittest
|
||||
import copy
|
||||
import sys
|
||||
|
@ -15,143 +14,94 @@ else:
|
|||
def garbage_collect():
|
||||
gc.collect()
|
||||
|
||||
def x(a):
|
||||
return a
|
||||
|
||||
class Dummy(object):
|
||||
pass
|
||||
def receiver_1_arg(val, **kwargs):
|
||||
return val
|
||||
|
||||
class Callable(object):
|
||||
def __call__(self, a):
|
||||
return a
|
||||
def __call__(self, val, **kwargs):
|
||||
return val
|
||||
|
||||
def a(self, a):
|
||||
return a
|
||||
def a(self, val, **kwargs):
|
||||
return val
|
||||
|
||||
a_signal = Signal(providing_args=["val"])
|
||||
|
||||
class DispatcherTests(unittest.TestCase):
|
||||
"""Test suite for dispatcher (barely started)"""
|
||||
|
||||
def setUp(self):
|
||||
# track the initial state, since it's possible that others have bleed receivers in
|
||||
garbage_collect()
|
||||
self.sendersBack = copy.copy(dispatcher.sendersBack)
|
||||
self.connections = copy.copy(dispatcher.connections)
|
||||
self.senders = copy.copy(dispatcher.senders)
|
||||
|
||||
def _testIsClean(self):
|
||||
def _testIsClean(self, signal):
|
||||
"""Assert that everything has been cleaned up automatically"""
|
||||
self.assertEqual(dispatcher.sendersBack, self.sendersBack)
|
||||
self.assertEqual(dispatcher.connections, self.connections)
|
||||
self.assertEqual(dispatcher.senders, self.senders)
|
||||
self.assertEqual(signal.receivers, [])
|
||||
|
||||
# force cleanup just in case
|
||||
signal.receivers = []
|
||||
|
||||
def testExact(self):
|
||||
a = Dummy()
|
||||
signal = 'this'
|
||||
connect(x, signal, a)
|
||||
expected = [(x,a)]
|
||||
result = send('this',a, a=a)
|
||||
a_signal.connect(receiver_1_arg, sender=self)
|
||||
expected = [(receiver_1_arg,"test")]
|
||||
result = a_signal.send(sender=self, val="test")
|
||||
self.assertEqual(result, expected)
|
||||
disconnect(x, signal, a)
|
||||
self.assertEqual(list(getAllReceivers(a,signal)), [])
|
||||
self._testIsClean()
|
||||
a_signal.disconnect(receiver_1_arg, sender=self)
|
||||
self._testIsClean(a_signal)
|
||||
|
||||
def testAnonymousSend(self):
|
||||
a = Dummy()
|
||||
signal = 'this'
|
||||
connect(x, signal)
|
||||
expected = [(x,a)]
|
||||
result = send(signal,None, a=a)
|
||||
def testIgnoredSender(self):
|
||||
a_signal.connect(receiver_1_arg)
|
||||
expected = [(receiver_1_arg,"test")]
|
||||
result = a_signal.send(sender=self, val="test")
|
||||
self.assertEqual(result, expected)
|
||||
disconnect(x, signal)
|
||||
self.assertEqual(list(getAllReceivers(None,signal)), [])
|
||||
self._testIsClean()
|
||||
|
||||
def testAnyRegistration(self):
|
||||
a = Dummy()
|
||||
signal = 'this'
|
||||
connect(x, signal, Any)
|
||||
expected = [(x,a)]
|
||||
result = send('this',object(), a=a)
|
||||
self.assertEqual(result, expected)
|
||||
disconnect(x, signal, Any)
|
||||
expected = []
|
||||
result = send('this',object(), a=a)
|
||||
self.assertEqual(result, expected)
|
||||
self.assertEqual(list(getAllReceivers(Any,signal)), [])
|
||||
|
||||
self._testIsClean()
|
||||
|
||||
def testAnyRegistration2(self):
|
||||
a = Dummy()
|
||||
signal = 'this'
|
||||
connect(x, Any, a)
|
||||
expected = [(x,a)]
|
||||
result = send('this',a, a=a)
|
||||
self.assertEqual(result, expected)
|
||||
disconnect(x, Any, a)
|
||||
self.assertEqual(list(getAllReceivers(a,Any)), [])
|
||||
self._testIsClean()
|
||||
a_signal.disconnect(receiver_1_arg)
|
||||
self._testIsClean(a_signal)
|
||||
|
||||
def testGarbageCollected(self):
|
||||
a = Callable()
|
||||
b = Dummy()
|
||||
signal = 'this'
|
||||
connect(a.a, signal, b)
|
||||
a_signal.connect(a.a, sender=self)
|
||||
expected = []
|
||||
del a
|
||||
garbage_collect()
|
||||
result = send('this',b, a=b)
|
||||
result = a_signal.send(sender=self, val="test")
|
||||
self.assertEqual(result, expected)
|
||||
self.assertEqual(list(getAllReceivers(b,signal)), [])
|
||||
self._testIsClean()
|
||||
|
||||
def testGarbageCollectedObj(self):
|
||||
class x:
|
||||
def __call__(self, a):
|
||||
return a
|
||||
a = Callable()
|
||||
b = Dummy()
|
||||
signal = 'this'
|
||||
connect(a, signal, b)
|
||||
expected = []
|
||||
del a
|
||||
garbage_collect()
|
||||
result = send('this',b, a=b)
|
||||
self.assertEqual(result, expected)
|
||||
self.assertEqual(list(getAllReceivers(b,signal)), [])
|
||||
self._testIsClean()
|
||||
|
||||
self._testIsClean(a_signal)
|
||||
|
||||
def testMultipleRegistration(self):
|
||||
a = Callable()
|
||||
b = Dummy()
|
||||
signal = 'this'
|
||||
connect(a, signal, b)
|
||||
connect(a, signal, b)
|
||||
connect(a, signal, b)
|
||||
connect(a, signal, b)
|
||||
connect(a, signal, b)
|
||||
connect(a, signal, b)
|
||||
result = send('this',b, a=b)
|
||||
a_signal.connect(a)
|
||||
a_signal.connect(a)
|
||||
a_signal.connect(a)
|
||||
a_signal.connect(a)
|
||||
a_signal.connect(a)
|
||||
a_signal.connect(a)
|
||||
result = a_signal.send(sender=self, val="test")
|
||||
self.assertEqual(len(result), 1)
|
||||
self.assertEqual(len(list(getAllReceivers(b,signal))), 1)
|
||||
self.assertEqual(len(a_signal.receivers), 1)
|
||||
del a
|
||||
del b
|
||||
del result
|
||||
garbage_collect()
|
||||
self._testIsClean()
|
||||
self._testIsClean(a_signal)
|
||||
|
||||
def testUidRegistration(self):
|
||||
def uid_based_receiver_1(**kwargs):
|
||||
pass
|
||||
|
||||
def uid_based_receiver_2(**kwargs):
|
||||
pass
|
||||
|
||||
a_signal.connect(uid_based_receiver_1, dispatch_uid = "uid")
|
||||
a_signal.connect(uid_based_receiver_2, dispatch_uid = "uid")
|
||||
self.assertEqual(len(a_signal.receivers), 1)
|
||||
a_signal.disconnect(dispatch_uid = "uid")
|
||||
self._testIsClean(a_signal)
|
||||
|
||||
def testRobust(self):
|
||||
"""Test the sendRobust function"""
|
||||
def fails():
|
||||
def fails(val, **kwargs):
|
||||
raise ValueError('this')
|
||||
a = object()
|
||||
signal = 'this'
|
||||
connect(fails, Any, a)
|
||||
result = robust.sendRobust('this',a, a=a)
|
||||
a_signal.connect(fails)
|
||||
result = a_signal.send_robust(sender=self, val="test")
|
||||
err = result[0][1]
|
||||
self.assert_(isinstance(err, ValueError))
|
||||
self.assertEqual(err.args, ('this',))
|
||||
a_signal.disconnect(fails)
|
||||
self._testIsClean(a_signal)
|
||||
|
||||
def getSuite():
|
||||
return unittest.makeSuite(DispatcherTests,'test')
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
from django.dispatch.robustapply import *
|
||||
|
||||
import unittest
|
||||
|
||||
def noArgument():
|
||||
pass
|
||||
|
||||
def oneArgument(blah):
|
||||
pass
|
||||
|
||||
def twoArgument(blah, other):
|
||||
pass
|
||||
|
||||
class TestCases(unittest.TestCase):
|
||||
def test01(self):
|
||||
robustApply(noArgument)
|
||||
|
||||
def test02(self):
|
||||
self.assertRaises(TypeError, robustApply, noArgument, "this")
|
||||
|
||||
def test03(self):
|
||||
self.assertRaises(TypeError, robustApply, oneArgument)
|
||||
|
||||
def test04(self):
|
||||
"""Raise error on duplication of a particular argument"""
|
||||
self.assertRaises(TypeError, robustApply, oneArgument, "this", blah = "that")
|
||||
|
||||
def getSuite():
|
||||
return unittest.makeSuite(TestCases,'test')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
Loading…
Reference in New Issue