diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py index 8394bee5cda..01fa52430f5 100644 --- a/django/contrib/auth/management/__init__.py +++ b/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) \ No newline at end of file +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") diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index 2d389362652..3d1980a9829 100644 --- a/django/contrib/contenttypes/generic.py +++ b/django/contrib/contenttypes/generic.py @@ -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. diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py index 49083d6d5c3..736e2136654 100644 --- a/django/contrib/contenttypes/management.py +++ b/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() diff --git a/django/contrib/sites/management.py b/django/contrib/sites/management.py index 5cecfb4ec0e..25c076099a3 100644 --- a/django/contrib/sites/management.py +++ b/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) diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 214032e318c..6a2d0645e0b 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -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): diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py index 58699816fdc..aa3fb23e39c 100644 --- a/django/core/handlers/modpython.py +++ b/django/core/handlers/modpython.py @@ -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'] diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index a1324936cd9..1ec382db149 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -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] diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index 87a99b8bebd..51d5034ff43 100644 --- a/django/core/management/commands/flush.py +++ b/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)) diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 7c178071305..2cca3c34696 100644 --- a/django/core/management/sql.py +++ b/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) diff --git a/django/core/signals.py b/django/core/signals.py index 7a236079a50..a14af009edb 100644 --- a/django/core/signals.py +++ b/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"]) diff --git a/django/db/__init__.py b/django/db/__init__.py index 95dd36822ea..37c8837b8bb 100644 --- a/django/db/__init__.py +++ b/django/db/__init__.py @@ -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) diff --git a/django/db/models/base.py b/django/db/models/base.py index 6b7d4bc32c9..3d7eac92842 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -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 diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 494c42cc543..9930953d667 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -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, diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index ab2b9a6c5ee..e90c54df4e8 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -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" diff --git a/django/db/models/manager.py b/django/db/models/manager.py index 3a9da34a49d..96bd53f240e 100644 --- a/django/db/models/manager.py +++ b/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. diff --git a/django/db/models/manipulators.py b/django/db/models/manipulators.py index 4e6ddca26ef..a3c917a4861 100644 --- a/django/db/models/manipulators.py +++ b/django/db/models/manipulators.py @@ -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 diff --git a/django/db/models/query.py b/django/db/models/query.py index 5b24195a7d8..e97e28b6323 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -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() diff --git a/django/db/models/signals.py b/django/db/models/signals.py index 2171cb1bf31..045533612dc 100644 --- a/django/db/models/signals.py +++ b/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"]) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 18dd8cc3f23..b5e99fb4c07 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -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) diff --git a/django/dispatch/__init__.py b/django/dispatch/__init__.py index bccae2a2daf..0798acc7dbf 100644 --- a/django/dispatch/__init__.py +++ b/django/dispatch/__init__.py @@ -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 \ No newline at end of file diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py index 725544446e0..ad54fa42bb9 100644 --- a/django/dispatch/dispatcher.py +++ b/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 " -__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. - Otherwise can be any python object. + If receivers have a dispatch_uid attribute, the receiver will + not be added if another receiver already exists with that + dispatch_uid. + + 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 - - 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 - -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. + self.receivers.append((lookup_key, receiver)) + + 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 - - -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) diff --git a/django/dispatch/errors.py b/django/dispatch/errors.py deleted file mode 100644 index a4c924dbe91..00000000000 --- a/django/dispatch/errors.py +++ /dev/null @@ -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)""" - diff --git a/django/dispatch/license.txt b/django/dispatch/license.txt index 8ff5d286139..505090dff94 100644 --- a/django/dispatch/license.txt +++ b/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. diff --git a/django/dispatch/robust.py b/django/dispatch/robust.py deleted file mode 100644 index 8b1590de353..00000000000 --- a/django/dispatch/robust.py +++ /dev/null @@ -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 diff --git a/django/dispatch/robustapply.py b/django/dispatch/robustapply.py deleted file mode 100644 index 14ba2b51acc..00000000000 --- a/django/dispatch/robustapply.py +++ /dev/null @@ -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) diff --git a/django/dispatch/saferef.py b/django/dispatch/saferef.py index 2abe3300da4..8bcfd8a1406 100644 --- a/django/dispatch/saferef.py +++ b/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 @@ 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) - diff --git a/django/test/client.py b/django/test/client.py index 7d621449eab..060fabd238b 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -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) diff --git a/django/test/signals.py b/django/test/signals.py index 40748ff4fe1..a328a7782e8 100644 --- a/django/test/signals.py +++ b/django/test/signals.py @@ -1 +1,3 @@ -template_rendered = object() \ No newline at end of file +from django.dispatch import Signal + +template_rendered = Signal(providing_args=["template", "context"]) diff --git a/django/test/utils.py b/django/test/utils.py index 03f40a8df74..733307a1c0b 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -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): diff --git a/tests/modeltests/signals/models.py b/tests/modeltests/signals/models.py index fc58d90a14b..23d1f2d87de 100644 --- a/tests/modeltests/signals/models.py +++ b/tests/modeltests/signals/models.py @@ -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() [] ->>> 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) """} diff --git a/tests/regressiontests/dispatch/tests/__init__.py b/tests/regressiontests/dispatch/tests/__init__.py index 0fdefe48a7b..150bb01dc0a 100644 --- a/tests/regressiontests/dispatch/tests/__init__.py +++ b/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 * diff --git a/tests/regressiontests/dispatch/tests/test_dispatcher.py b/tests/regressiontests/dispatch/tests/test_dispatcher.py index f34173972df..baaae9cd958 100644 --- a/tests/regressiontests/dispatch/tests/test_dispatcher.py +++ b/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 @@ 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() - - def testAnonymousSend(self): - a = Dummy() - signal = 'this' - connect(x, signal) - expected = [(x,a)] - result = send(signal,None, 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, 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') diff --git a/tests/regressiontests/dispatch/tests/test_robustapply.py b/tests/regressiontests/dispatch/tests/test_robustapply.py deleted file mode 100644 index 499450eec48..00000000000 --- a/tests/regressiontests/dispatch/tests/test_robustapply.py +++ /dev/null @@ -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() -