Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Major refactoring of django.dispatch with an eye towards speed. The n…

…et 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
  • Loading branch information...
commit 34a3bd52255a2253696b74b2d76133aace839fd2 1 parent d06b474
@jacobian jacobian authored
Showing with 369 additions and 837 deletions.
  1. +5 −6 django/contrib/auth/management/__init__.py
  2. +2 −3 django/contrib/contenttypes/generic.py
  3. +2 −3 django/contrib/contenttypes/management.py
  4. +2 −3 django/contrib/sites/management.py
  5. +1 −2  django/core/handlers/base.py
  6. +2 −3 django/core/handlers/modpython.py
  7. +2 −3 django/core/handlers/wsgi.py
  8. +0 −1  django/core/management/commands/flush.py
  9. +3 −3 django/core/management/sql.py
  10. +5 −3 django/core/signals.py
  11. +7 −6 django/db/__init__.py
  12. +6 −8 django/db/models/base.py
  13. +2 −3 django/db/models/fields/__init__.py
  14. +2 −3 django/db/models/fields/related.py
  15. +2 −3 django/db/models/manager.py
  16. +2 −3 django/db/models/manipulators.py
  17. +2 −5 django/db/models/query.py
  18. +10 −8 django/db/models/signals.py
  19. +2 −3 django/db/models/sql/query.py
  20. +6 −3 django/dispatch/__init__.py
  21. +205 −457 django/dispatch/dispatcher.py
  22. +0 −10 django/dispatch/errors.py
  23. +3 −1 django/dispatch/license.txt
  24. +0 −57 django/dispatch/robust.py
  25. +0 −47 django/dispatch/robustapply.py
  26. +16 −3 django/dispatch/saferef.py
  27. +6 −7 django/test/client.py
  28. +3 −1 django/test/signals.py
  29. +1 −2  django/test/utils.py
  30. +12 −34 tests/modeltests/signals/models.py
  31. +1 −2  tests/regressiontests/dispatch/tests/__init__.py
  32. +57 −107 tests/regressiontests/dispatch/tests/test_dispatcher.py
  33. +0 −34 tests/regressiontests/dispatch/tests/test_robustapply.py
View
11 django/contrib/auth/management/__init__.py
@@ -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")
View
5 django/contrib/contenttypes/generic.py
@@ -8,7 +8,6 @@
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 @@ def contribute_to_class(self, cls, name):
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.
View
5 django/contrib/contenttypes/management.py
@@ -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()
View
5 django/contrib/sites/management.py
@@ -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)
View
3  django/core/handlers/base.py
@@ -2,7 +2,6 @@
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 @@ def get_response(self, request):
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):
View
5 django/core/handlers/modpython.py
@@ -5,7 +5,6 @@
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 @@ def __call__(self, req):
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 @@ def __call__(self, req):
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']
View
5 django/core/handlers/wsgi.py
@@ -9,7 +9,6 @@
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 @@ def __call__(self, environ, start_response):
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 @@ def __call__(self, environ, start_response):
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]
View
1  django/core/management/commands/flush.py
@@ -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))
View
6 django/core/management/sql.py
@@ -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)
View
8 django/core/signals.py
@@ -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"])
View
13 django/db/__init__.py
@@ -2,7 +2,6 @@
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 @@ def get_creation_module():
# 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)
View
14 django/db/models/base.py
@@ -19,7 +19,6 @@
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 @@ def _prepare(cls):
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 @@ def __init__(self, *args, **kwargs):
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 @@ def save_base(self, raw=False, cls=None):
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 @@ def save_base(self, raw=False, cls=None):
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
View
5 django/db/models/fields/__init__.py
@@ -10,7 +10,6 @@
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 @@ def contribute_to_class(self, cls, name):
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,
View
5 django/db/models/fields/related.py
@@ -9,7 +9,6 @@
from django.core import validators
from django import oldforms
from django import forms
-from django.dispatch import dispatcher
try:
set
@@ -74,7 +73,7 @@ class MyModel(Model):
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"
View
5 django/db/models/manager.py
@@ -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.
View
5 django/db/models/manipulators.py
@@ -2,7 +2,6 @@
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.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
View
7 django/db/models/query.py
@@ -7,7 +7,6 @@
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()
View
18 django/db/models/signals.py
@@ -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"])
View
5 django/db/models/sql/query.py
@@ -11,7 +11,6 @@
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)
View
9 django/dispatch/__init__.py
@@ -1,6 +1,9 @@
"""Multi-consumer multi-producer dispatching mechanism
+
+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.
"""
-__version__ = "1.0.0"
-__author__ = "Patrick K. O'Brien"
-__license__ = "BSD-style, see license.txt for details"
+from django.dispatch.dispatcher import Signal
View
662 django/dispatch/dispatcher.py
@@ -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)
-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).
+class Signal(object):
+ """Base class for all signals
- 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
+ Internal attributes:
+ receivers -- { receriverkey (id) : weakref(receiver) }
+ """
- 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
+ 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)
+
+ def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
+ """Connect receiver to sender for signal
- if Any, receiver will receive the indicated signals
- from any sender.
+ receiver -- a function or an instance method which is to
+ receive signals. 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).
- if Anonymous, receiver will only receive indicated
- signals from send/sendExact which do not specify a
- sender, or specify Anonymous explicitly as the sender.
+ 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.
- Otherwise can be any python object.
+ sender -- the sender to which the receiver should respond
+ Must either be of type Signal, 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.
- 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.
- returns None, may raise DispatcherTypeError
- """
- 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, {})
-
- # 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
+ returns None
+ """
+ from django.conf import settings
- 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
-
- receivers.append(receiver)
-
-
-
-def disconnect(receiver, signal=Any, sender=Any, weak=True):
- """Disconnect receiver from sender for signal
-
- receiver -- the registered receiver to disconnect
- signal -- the registered signal to disconnect
- sender -- the registered sender to disconnect
- weak -- the weakref state to disconnect
-
- 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).
-
- 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.
+ if settings.DEBUG:
+ import inspect
+ assert inspect.getargspec(receiver)[2] is not None, \
+ "Signal receivers must accept keyword arguments (**kwargs)."
- 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)
-
-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
+ for r_key, _ in self.receivers:
+ if r_key == lookup_key:
+ break
+ else:
+ self.receivers.append((lookup_key, receiver))
- 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.
+ def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None):
+ """Disconnect receiver from sender for signal
- signal -- (hashable) signal value, see connect for details
-
- sender -- the sender of the 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
- if Any, only receivers registered for Any will receive
- the message.
+ disconnect reverses the process of connect.
- if Anonymous, only receivers registered to receive
- messages from Anonymous or Any will receive the message
+ If weak references are used, disconnect need not be called.
+ The receiver will be remove from dispatch automatically.
- Otherwise can be any python object (normally one
- registered with a connect if you actually want
- something to occur).
+ returns None
+ """
- 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.
+ if dispatch_uid:
+ lookup_key = (dispatch_uid, _make_id(sender))
+ else:
+ lookup_key = (_make_id(receiver), _make_id(sender))
- named -- named arguments which will be filtered according
- to the parameters of the receivers to only provide those
- acceptable to the receiver.
+ for idx, (r_key, _) in enumerate(self.receivers):
+ if r_key == lookup_key:
+ del self.receivers[idx]
- Return a list of tuple pairs [(receiver, response), ... ]
+ def send(self, sender, **named):
+ """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 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
+ sender -- the sender of the signal
+ Either a specific object or None.
+ named -- named arguments which will be passed to receivers.
-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
- 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
-
-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.
- try:
- signals = connections[senderkey]
- except KeyError:
- pass
- else:
- del signals[signal]
- if not signals:
- # No more signal connections. Therefore, remove the sender.
- _removeSender(senderkey)
+ 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
-def _removeSender(senderkey):
- """Remove senderkey from connections."""
- _removeBackrefs(senderkey)
+ for receiver in self._live_receivers(_make_id(sender)):
+ response = receiver(signal=self, sender=sender, **named)
+ responses.append((receiver, response))
+ return responses
- connections.pop(senderkey, None)
- senders.pop(senderkey, None)
+ 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).
-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)
+ 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
-def _removeOldBackRefs(senderkey, signal, receiver, receivers):
- """Kill old sendersBack references from receiver
+ if any receiver raises an error (specifically any subclass of Exception),
+ the error instance is returned as the result for that 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.
+ responses = []
+ if not self.receivers:
+ return responses
- Also removes old receiver instance from receivers
+ # 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:
+ response = receiver(signal=self, sender=sender, **named)
+ except Exception, err:
+ responses.append((receiver, err))
+ else:
+ responses.append((receiver, response))
+ return responses
+
+ def _live_receivers(self, senderkey):
+ """Filter sequence of receivers to get resolved, live receivers
+
+ 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 _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 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
-
-
-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
+ 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 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)
View
10 django/dispatch/errors.py
@@ -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)"""
-
View
4 django/dispatch/license.txt
@@ -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.
View
57 django/dispatch/robust.py
@@ -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
View
47 django/dispatch/robustapply.py
@@ -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)
View
19 django/dispatch/saferef.py
@@ -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 @@ def __new__( cls, target, onDelete=None, *arguments,**named ):
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 @@ def remove(weak, self=self):
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 @@ def calculateKey( cls, target ):
"""
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 @@ def __str__(self):
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 @@ def __call__(self):
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)
-
View
13 django/test/client.py
@@ -11,7 +11,6 @@
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 @@ def __call__(self, environ):
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 @@ def __call__(self, environ):
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 @@ def __init__(self, **defaults):
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 @@ def request(self, **request):
# 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)
View
4 django/test/signals.py
@@ -1 +1,3 @@
-template_rendered = object()
+from django.dispatch import Signal
+
+template_rendered = Signal(providing_args=["template", "context"])
View
3  django/test/utils.py
@@ -3,7 +3,6 @@
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):
View
46 tests/modeltests/signals/models.py
@@ -3,7 +3,6 @@
"""
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 @@ def post_delete_test(sender, instance, **kwargs):
>>> 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 @@ def post_delete_test(sender, instance, **kwargs):
>>> 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)
"""}
View
3  tests/regressiontests/dispatch/tests/__init__.py
@@ -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 *
View
164 tests/regressiontests/dispatch/tests/test_dispatcher.py
@@ -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 @@ def garbage_collect():
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)
- self.assertEqual(result, expected)
- disconnect(x, signal, a)
- self.assertEqual(list(getAllReceivers(a,signal)), [])
- self._testIsClean()
-
- def testAnonymousSend(self):
- a = Dummy()
- signal = 'this'
- connect(x, signal)
- expected = [(x,a)]
- result = send(signal,None, a=a)
- 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)
+ 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)
- 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)
+ a_signal.disconnect(receiver_1_arg, sender=self)
+ self._testIsClean(a_signal)
+
+ 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, 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)
- expected = []
- del a
- garbage_collect()
- result = send('this',b, a=b)
- 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)
+ 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()
-
+ 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')
View
34 tests/regressiontests/dispatch/tests/test_robustapply.py
@@ -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()
-
Please sign in to comment.
Something went wrong with that request. Please try again.