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