mirror of https://github.com/django/django.git
Major refactoring of django.dispatch with an eye towards speed. The net result is that signals are up to 90% faster.
Though some attempts and backwards-compatibility were made, speed trumped compatibility. Thus, as usual, check BackwardsIncompatibleChanges for the complete list of backwards-incompatible changes. Thanks to Jeremy Dunck and Keith Busell for the bulk of the work; some ideas from Brian Herring's previous work (refs #4561) were incorporated. Documentation is, sigh, still forthcoming. Fixes #6814 and #3951 (with the new dispatch_uid argument to connect). git-svn-id: http://code.djangoproject.com/svn/django/trunk@8223 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
d06b474251
commit
34a3bd5225
|
@ -2,7 +2,6 @@
|
||||||
Creates permissions for all installed apps that need permissions.
|
Creates permissions for all installed apps that need permissions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.dispatch import dispatcher
|
|
||||||
from django.db.models import get_models, signals
|
from django.db.models import get_models, signals
|
||||||
from django.contrib.auth import models as auth_app
|
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)))
|
perms.append((_get_permission_codename(action, opts), u'Can %s %s' % (action, opts.verbose_name_raw)))
|
||||||
return perms + list(opts.permissions)
|
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.contenttypes.models import ContentType
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
app_models = get_models(app)
|
app_models = get_models(app)
|
||||||
|
@ -45,7 +44,7 @@ def create_superuser(app, created_models, verbosity, **kwargs):
|
||||||
call_command("createsuperuser", interactive=True)
|
call_command("createsuperuser", interactive=True)
|
||||||
break
|
break
|
||||||
|
|
||||||
if 'create_permissions' not in [i.__name__ for i in dispatcher.getAllReceivers(signal=signals.post_syncdb)]:
|
signals.post_syncdb.connect(create_permissions,
|
||||||
dispatcher.connect(create_permissions, signal=signals.post_syncdb)
|
dispatch_uid = "django.contrib.auth.management.create_permissions")
|
||||||
if 'create_superuser' not in [i.__name__ for i in dispatcher.getAllReceivers(signal=signals.post_syncdb, sender=auth_app)]:
|
signals.post_syncdb.connect(create_superuser,
|
||||||
dispatcher.connect(create_superuser, sender=auth_app, signal=signals.post_syncdb)
|
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 import signals
|
||||||
from django.db.models.fields.related import RelatedField, Field, ManyToManyRel
|
from django.db.models.fields.related import RelatedField, Field, ManyToManyRel
|
||||||
from django.db.models.loading import get_model
|
from django.db.models.loading import get_model
|
||||||
from django.dispatch import dispatcher
|
|
||||||
from django.utils.functional import curry
|
from django.utils.functional import curry
|
||||||
|
|
||||||
class GenericForeignKey(object):
|
class GenericForeignKey(object):
|
||||||
|
@ -29,12 +28,12 @@ class GenericForeignKey(object):
|
||||||
self.cache_attr = "_%s_cache" % name
|
self.cache_attr = "_%s_cache" % name
|
||||||
|
|
||||||
# For some reason I don't totally understand, using weakrefs here doesn't work.
|
# 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
|
# Connect myself as the descriptor for this field
|
||||||
setattr(cls, name, self)
|
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
|
Handles initializing an object with the generic FK instaed of
|
||||||
content-type/object-id fields.
|
content-type/object-id fields.
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.dispatch import dispatcher
|
|
||||||
from django.db.models import get_apps, get_models, signals
|
from django.db.models import get_apps, get_models, signals
|
||||||
from django.utils.encoding import smart_unicode
|
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
|
Creates content types for models in the given app, removing any model
|
||||||
entries that no longer have a matching model class.
|
entries that no longer have a matching model class.
|
||||||
|
@ -37,7 +36,7 @@ def update_all_contenttypes(verbosity=2):
|
||||||
for app in get_apps():
|
for app in get_apps():
|
||||||
update_contenttypes(app, None, verbosity)
|
update_contenttypes(app, None, verbosity)
|
||||||
|
|
||||||
dispatcher.connect(update_contenttypes, signal=signals.post_syncdb)
|
signals.post_syncdb.connect(update_contenttypes)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
update_all_contenttypes()
|
update_all_contenttypes()
|
||||||
|
|
|
@ -2,12 +2,11 @@
|
||||||
Creates the default Site object.
|
Creates the default Site object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.dispatch import dispatcher
|
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.contrib.sites import models as site_app
|
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 Site in created_models:
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
print "Creating example.com Site object"
|
print "Creating example.com Site object"
|
||||||
|
@ -15,4 +14,4 @@ def create_default_site(app, created_models, verbosity):
|
||||||
s.save()
|
s.save()
|
||||||
Site.objects.clear_cache()
|
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 import http
|
||||||
from django.core import signals
|
from django.core import signals
|
||||||
from django.dispatch import dispatcher
|
|
||||||
from django.utils.encoding import force_unicode
|
from django.utils.encoding import force_unicode
|
||||||
|
|
||||||
class BaseHandler(object):
|
class BaseHandler(object):
|
||||||
|
@ -122,7 +121,7 @@ class BaseHandler(object):
|
||||||
except: # Handle everything else, including SuspiciousOperation, etc.
|
except: # Handle everything else, including SuspiciousOperation, etc.
|
||||||
# Get the exception info now, in case another exception is thrown later.
|
# Get the exception info now, in case another exception is thrown later.
|
||||||
exc_info = sys.exc_info()
|
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)
|
return self.handle_uncaught_exception(request, resolver, exc_info)
|
||||||
|
|
||||||
def handle_uncaught_exception(self, 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 import signals
|
||||||
from django.core.handlers.base import BaseHandler
|
from django.core.handlers.base import BaseHandler
|
||||||
from django.core.urlresolvers import set_script_prefix
|
from django.core.urlresolvers import set_script_prefix
|
||||||
from django.dispatch import dispatcher
|
|
||||||
from django.utils import datastructures
|
from django.utils import datastructures
|
||||||
from django.utils.encoding import force_unicode, smart_str
|
from django.utils.encoding import force_unicode, smart_str
|
||||||
|
|
||||||
|
@ -174,7 +173,7 @@ class ModPythonHandler(BaseHandler):
|
||||||
self.load_middleware()
|
self.load_middleware()
|
||||||
|
|
||||||
set_script_prefix(req.get_options().get('django.root', ''))
|
set_script_prefix(req.get_options().get('django.root', ''))
|
||||||
dispatcher.send(signal=signals.request_started)
|
signals.request_started.send(sender=self.__class__)
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
request = self.request_class(req)
|
request = self.request_class(req)
|
||||||
|
@ -188,7 +187,7 @@ class ModPythonHandler(BaseHandler):
|
||||||
response = middleware_method(request, response)
|
response = middleware_method(request, response)
|
||||||
response = self.apply_response_fixes(request, response)
|
response = self.apply_response_fixes(request, response)
|
||||||
finally:
|
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.
|
# Convert our custom HttpResponse object back into the mod_python req.
|
||||||
req.content_type = response['Content-Type']
|
req.content_type = response['Content-Type']
|
||||||
|
|
|
@ -9,7 +9,6 @@ from django import http
|
||||||
from django.core import signals
|
from django.core import signals
|
||||||
from django.core.handlers import base
|
from django.core.handlers import base
|
||||||
from django.core.urlresolvers import set_script_prefix
|
from django.core.urlresolvers import set_script_prefix
|
||||||
from django.dispatch import dispatcher
|
|
||||||
from django.utils import datastructures
|
from django.utils import datastructures
|
||||||
from django.utils.encoding import force_unicode
|
from django.utils.encoding import force_unicode
|
||||||
|
|
||||||
|
@ -207,7 +206,7 @@ class WSGIHandler(base.BaseHandler):
|
||||||
self.initLock.release()
|
self.initLock.release()
|
||||||
|
|
||||||
set_script_prefix(base.get_script_name(environ))
|
set_script_prefix(base.get_script_name(environ))
|
||||||
dispatcher.send(signal=signals.request_started)
|
signals.request_started.send(sender=self.__class__)
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
request = self.request_class(environ)
|
request = self.request_class(environ)
|
||||||
|
@ -221,7 +220,7 @@ class WSGIHandler(base.BaseHandler):
|
||||||
response = middleware_method(request, response)
|
response = middleware_method(request, response)
|
||||||
response = self.apply_response_fixes(request, response)
|
response = self.apply_response_fixes(request, response)
|
||||||
finally:
|
finally:
|
||||||
dispatcher.send(signal=signals.request_finished)
|
signals.request_finished.send(sender=self.__class__)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
status_text = STATUS_CODE_TEXT[response.status_code]
|
status_text = STATUS_CODE_TEXT[response.status_code]
|
||||||
|
|
|
@ -15,7 +15,6 @@ class Command(NoArgsCommand):
|
||||||
def handle_noargs(self, **options):
|
def handle_noargs(self, **options):
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import connection, transaction, models
|
from django.db import connection, transaction, models
|
||||||
from django.dispatch import dispatcher
|
|
||||||
from django.core.management.sql import sql_flush, emit_post_sync_signal
|
from django.core.management.sql import sql_flush, emit_post_sync_signal
|
||||||
|
|
||||||
verbosity = int(options.get('verbosity', 1))
|
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]
|
app_name = app.__name__.split('.')[-2]
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
print "Running post-sync handlers for application", app_name
|
print "Running post-sync handlers for application", app_name
|
||||||
dispatcher.send(signal=models.signals.post_syncdb, sender=app,
|
models.signals.post_syncdb.send(sender=app, app=app,
|
||||||
app=app, created_models=created_models,
|
created_models=created_models, verbosity=verbosity,
|
||||||
verbosity=verbosity, interactive=interactive)
|
interactive=interactive)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
request_started = object()
|
from django.dispatch import Signal
|
||||||
request_finished = object()
|
|
||||||
got_request_exception = object()
|
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.conf import settings
|
||||||
from django.core import signals
|
from django.core import signals
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.dispatch import dispatcher
|
|
||||||
from django.utils.functional import curry
|
from django.utils.functional import curry
|
||||||
|
|
||||||
__all__ = ('backend', 'connection', 'DatabaseError', 'IntegrityError')
|
__all__ = ('backend', 'connection', 'DatabaseError', 'IntegrityError')
|
||||||
|
@ -58,17 +57,19 @@ IntegrityError = backend.IntegrityError
|
||||||
|
|
||||||
# Register an event that closes the database connection
|
# Register an event that closes the database connection
|
||||||
# when a Django request is finished.
|
# 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
|
# Register an event that resets connection.queries
|
||||||
# when a Django request is started.
|
# when a Django request is started.
|
||||||
def reset_queries():
|
def reset_queries(**kwargs):
|
||||||
connection.queries = []
|
connection.queries = []
|
||||||
dispatcher.connect(reset_queries, signal=signals.request_started)
|
signals.request_started.connect(reset_queries)
|
||||||
|
|
||||||
# Register an event that rolls back the connection
|
# Register an event that rolls back the connection
|
||||||
# when a Django request has an exception.
|
# when a Django request has an exception.
|
||||||
def _rollback_on_exception():
|
def _rollback_on_exception(**kwargs):
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
transaction.rollback_unless_managed()
|
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 import connection, transaction
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
from django.db.models.loading import register_models, get_model
|
from django.db.models.loading import register_models, get_model
|
||||||
from django.dispatch import dispatcher
|
|
||||||
from django.utils.functional import curry
|
from django.utils.functional import curry
|
||||||
from django.utils.encoding import smart_str, force_unicode, smart_unicode
|
from django.utils.encoding import smart_str, force_unicode, smart_unicode
|
||||||
from django.core.files.move import file_move_safe
|
from django.core.files.move import file_move_safe
|
||||||
|
@ -161,14 +160,14 @@ class ModelBase(type):
|
||||||
if hasattr(cls, 'get_absolute_url'):
|
if hasattr(cls, 'get_absolute_url'):
|
||||||
cls.get_absolute_url = curry(get_absolute_url, opts, 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):
|
class Model(object):
|
||||||
__metaclass__ = ModelBase
|
__metaclass__ = ModelBase
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
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
|
# 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
|
# overrides it. It should be one or the other; don't duplicate the work
|
||||||
|
@ -239,7 +238,7 @@ class Model(object):
|
||||||
pass
|
pass
|
||||||
if kwargs:
|
if kwargs:
|
||||||
raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]
|
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):
|
def __repr__(self):
|
||||||
return smart_str(u'<%s: %s>' % (self.__class__.__name__, unicode(self)))
|
return smart_str(u'<%s: %s>' % (self.__class__.__name__, unicode(self)))
|
||||||
|
@ -288,8 +287,7 @@ class Model(object):
|
||||||
cls = self.__class__
|
cls = self.__class__
|
||||||
meta = self._meta
|
meta = self._meta
|
||||||
signal = True
|
signal = True
|
||||||
dispatcher.send(signal=signals.pre_save, sender=self.__class__,
|
signals.pre_save.send(sender=self.__class__, instance=self, raw=raw)
|
||||||
instance=self, raw=raw)
|
|
||||||
else:
|
else:
|
||||||
meta = cls._meta
|
meta = cls._meta
|
||||||
signal = False
|
signal = False
|
||||||
|
@ -351,8 +349,8 @@ class Model(object):
|
||||||
transaction.commit_unless_managed()
|
transaction.commit_unless_managed()
|
||||||
|
|
||||||
if signal:
|
if signal:
|
||||||
dispatcher.send(signal=signals.post_save, sender=self.__class__,
|
signals.post_save.send(sender=self.__class__, instance=self,
|
||||||
instance=self, created=(not record_exists), raw=raw)
|
created=(not record_exists), raw=raw)
|
||||||
|
|
||||||
save_base.alters_data = True
|
save_base.alters_data = True
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ except ImportError:
|
||||||
from django.db import connection, get_creation_module
|
from django.db import connection, get_creation_module
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
from django.db.models.query_utils import QueryWrapper
|
from django.db.models.query_utils import QueryWrapper
|
||||||
from django.dispatch import dispatcher
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django import oldforms
|
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_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, '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))
|
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):
|
if getattr(instance, self.attname):
|
||||||
file_name = getattr(instance, 'get_%s_filename' % self.name)()
|
file_name = getattr(instance, 'get_%s_filename' % self.name)()
|
||||||
# If the file exists and no other object of this type references it,
|
# 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.core import validators
|
||||||
from django import oldforms
|
from django import oldforms
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.dispatch import dispatcher
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
set
|
set
|
||||||
|
@ -74,7 +73,7 @@ def add_lazy_relation(cls, field, relation, operation):
|
||||||
value = (cls, field, operation)
|
value = (cls, field, operation)
|
||||||
pending_lookups.setdefault(key, []).append(value)
|
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.
|
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, []):
|
for cls, field, operation in pending_lookups.pop(key, []):
|
||||||
operation(field, sender, cls)
|
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):
|
def manipulator_valid_rel_key(f, self, field_data, all_data):
|
||||||
"Validates that the value is a valid foreign key"
|
"Validates that the value is a valid foreign key"
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from django.db.models.query import QuerySet, EmptyQuerySet, insert_query
|
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 import signals
|
||||||
from django.db.models.fields import FieldDoesNotExist
|
from django.db.models.fields import FieldDoesNotExist
|
||||||
|
|
||||||
def ensure_default_manager(sender):
|
def ensure_default_manager(sender, **kwargs):
|
||||||
cls = sender
|
cls = sender
|
||||||
if not getattr(cls, '_default_manager', None) and not cls._meta.abstract:
|
if not getattr(cls, '_default_manager', None) and not cls._meta.abstract:
|
||||||
# Create the default manager, if needed.
|
# Create the default manager, if needed.
|
||||||
|
@ -16,7 +15,7 @@ def ensure_default_manager(sender):
|
||||||
pass
|
pass
|
||||||
cls.add_to_class('objects', Manager())
|
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):
|
class Manager(object):
|
||||||
# Tracks each time a Manager instance is created. Used to retain order.
|
# 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 import oldforms
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.db.models.fields import FileField, AutoField
|
from django.db.models.fields import FileField, AutoField
|
||||||
from django.dispatch import dispatcher
|
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
from django.utils.functional import curry
|
from django.utils.functional import curry
|
||||||
from django.utils.datastructures import DotExpandedDict
|
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.translation import ugettext as _
|
||||||
from django.utils import datetime_safe
|
from django.utils import datetime_safe
|
||||||
|
|
||||||
def add_manipulators(sender):
|
def add_manipulators(sender, **kwargs):
|
||||||
cls = sender
|
cls = sender
|
||||||
cls.add_to_class('AddManipulator', AutomaticAddManipulator)
|
cls.add_to_class('AddManipulator', AutomaticAddManipulator)
|
||||||
cls.add_to_class('ChangeManipulator', AutomaticChangeManipulator)
|
cls.add_to_class('ChangeManipulator', AutomaticChangeManipulator)
|
||||||
|
|
||||||
dispatcher.connect(add_manipulators, signal=signals.class_prepared)
|
signals.class_prepared.connect(add_manipulators)
|
||||||
|
|
||||||
class ManipulatorDescriptor(object):
|
class ManipulatorDescriptor(object):
|
||||||
# This class provides the functionality that makes the default model
|
# 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.fields import DateField
|
||||||
from django.db.models.query_utils import Q, select_related_descend
|
from django.db.models.query_utils import Q, select_related_descend
|
||||||
from django.db.models import signals, sql
|
from django.db.models import signals, sql
|
||||||
from django.dispatch import dispatcher
|
|
||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
|
|
||||||
|
|
||||||
|
@ -810,8 +809,7 @@ def delete_objects(seen_objs):
|
||||||
|
|
||||||
# Pre-notify all instances to be deleted.
|
# Pre-notify all instances to be deleted.
|
||||||
for pk_val, instance in items:
|
for pk_val, instance in items:
|
||||||
dispatcher.send(signal=signals.pre_delete, sender=cls,
|
signals.pre_delete.send(sender=cls, instance=instance)
|
||||||
instance=instance)
|
|
||||||
|
|
||||||
pk_list = [pk for pk,instance in items]
|
pk_list = [pk for pk,instance in items]
|
||||||
del_query = sql.DeleteQuery(cls, connection)
|
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:
|
if field.rel and field.null and field.rel.to in seen_objs:
|
||||||
setattr(instance, field.attname, None)
|
setattr(instance, field.attname, None)
|
||||||
|
|
||||||
dispatcher.send(signal=signals.post_delete, sender=cls,
|
signals.post_delete.send(sender=cls, instance=instance)
|
||||||
instance=instance)
|
|
||||||
setattr(instance, cls._meta.pk.attname, None)
|
setattr(instance, cls._meta.pk.attname, None)
|
||||||
|
|
||||||
transaction.commit_unless_managed()
|
transaction.commit_unless_managed()
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
class_prepared = object()
|
from django.dispatch import Signal
|
||||||
|
|
||||||
pre_init= object()
|
class_prepared = Signal(providing_args=["class"])
|
||||||
post_init = object()
|
|
||||||
|
|
||||||
pre_save = object()
|
pre_init = Signal(providing_args=["instance", "args", "kwargs"])
|
||||||
post_save = object()
|
post_init = Signal(providing_args=["instance"])
|
||||||
|
|
||||||
pre_delete = object()
|
pre_save = Signal(providing_args=["instance", "raw"])
|
||||||
post_delete = object()
|
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.tree import Node
|
||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
from django.dispatch import dispatcher
|
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
from django.db.models.fields import FieldDoesNotExist
|
from django.db.models.fields import FieldDoesNotExist
|
||||||
|
@ -1670,7 +1669,7 @@ def order_modified_iter(cursor, trim, sentinel):
|
||||||
sentinel):
|
sentinel):
|
||||||
yield [r[:-trim] for r in rows]
|
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
|
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
|
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 = {}
|
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
|
"""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
|
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>"
|
from django.dispatch import saferef
|
||||||
__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()
|
|
||||||
|
|
||||||
WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
|
WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
|
||||||
|
|
||||||
connections = {}
|
def _make_id(target):
|
||||||
senders = {}
|
if hasattr(target, 'im_func'):
|
||||||
sendersBack = {}
|
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
|
"""Connect receiver to sender for signal
|
||||||
|
|
||||||
receiver -- a callable Python object which is to receive
|
receiver -- a function or an instance method which is to
|
||||||
messages/signals/events. Receivers must be hashable
|
receive signals. Receivers must be
|
||||||
objects.
|
hashable objects.
|
||||||
|
|
||||||
if weak is True, then receiver must be weak-referencable
|
if weak is True, then receiver must be weak-referencable
|
||||||
(more precisely saferef.safeRef() must be able to create
|
(more precisely saferef.safeRef() must be able to create
|
||||||
a reference to the receiver).
|
a reference to the receiver).
|
||||||
|
|
||||||
Receivers are fairly flexible in their specification,
|
Receivers must be able to accept keyword arguments.
|
||||||
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 receivers have a dispatch_uid attribute, the receiver will
|
||||||
if receiver is itself a weak reference (a callable),
|
not be added if another receiver already exists with that
|
||||||
it will be de-referenced by the system's machinery,
|
dispatch_uid.
|
||||||
so *generally* weak references are not suitable as
|
|
||||||
receivers, though some use might be found for the
|
|
||||||
facility whereby a higher-level library passes in
|
|
||||||
pre-weakrefed receiver references.
|
|
||||||
|
|
||||||
signal -- the signal to which the receiver should respond
|
|
||||||
|
|
||||||
if Any, receiver will receive any signal from the
|
|
||||||
indicated sender (which might also be Any, but is not
|
|
||||||
necessarily Any).
|
|
||||||
|
|
||||||
Otherwise must be a hashable Python object other than
|
|
||||||
None (DispatcherError raised on None).
|
|
||||||
|
|
||||||
sender -- the sender to which the receiver should respond
|
sender -- the sender to which the receiver should respond
|
||||||
|
Must either be of type Signal, or None to receive events
|
||||||
if Any, receiver will receive the indicated signals
|
|
||||||
from any sender.
|
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
|
weak -- whether to use weak references to the receiver
|
||||||
By default, the module will attempt to use weak
|
By default, the module will attempt to use weak
|
||||||
references to the receiver objects. If this parameter
|
references to the receiver objects. If this parameter
|
||||||
is false, then strong references will be used.
|
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:
|
from django.conf import settings
|
||||||
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, {})
|
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.
|
if dispatch_uid:
|
||||||
# Is Anonymous something we want to clean up?
|
lookup_key = (dispatch_uid, _make_id(sender))
|
||||||
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)
|
|
||||||
else:
|
else:
|
||||||
receivers = signals[signal] = []
|
lookup_key = (_make_id(receiver), _make_id(sender))
|
||||||
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)
|
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(self, receiver=None, sender=None, weak=True, dispatch_uid=None):
|
||||||
def disconnect(receiver, signal=Any, sender=Any, weak=True):
|
|
||||||
"""Disconnect receiver from sender for signal
|
"""Disconnect receiver from sender for signal
|
||||||
|
|
||||||
receiver -- the registered receiver to disconnect
|
receiver -- the registered receiver to disconnect. May be none if
|
||||||
signal -- the registered signal to disconnect
|
dispatch_uid is specified.
|
||||||
sender -- the registered sender to disconnect
|
sender -- the registered sender to disconnect
|
||||||
weak -- the weakref state to disconnect
|
weak -- the weakref state to disconnect
|
||||||
|
dispatch_uid -- the unique identifier of the receiver to disconnect
|
||||||
|
|
||||||
disconnect reverses the process of connect,
|
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:
|
If weak references are used, disconnect need not be called.
|
||||||
Using disconnect is not required to cleanup
|
The receiver will be remove from dispatch automatically.
|
||||||
routing when an object is deleted, the framework
|
|
||||||
will remove routes for deleted objects
|
|
||||||
automatically. It's only necessary to disconnect
|
|
||||||
if you want to stop routing to a live object.
|
|
||||||
|
|
||||||
returns None, may raise DispatcherTypeError or
|
returns None
|
||||||
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):
|
if dispatch_uid:
|
||||||
"""Get list of receivers from global tables
|
lookup_key = (dispatch_uid, _make_id(sender))
|
||||||
|
else:
|
||||||
|
lookup_key = (_make_id(receiver), _make_id(sender))
|
||||||
|
|
||||||
This utility function allows you to retrieve the
|
for idx, (r_key, _) in enumerate(self.receivers):
|
||||||
raw list of receivers from the connections table
|
if r_key == lookup_key:
|
||||||
for the given sender and signal pair.
|
del self.receivers[idx]
|
||||||
|
|
||||||
Note:
|
def send(self, sender, **named):
|
||||||
there is no guarantee that this is the actual list
|
"""Send signal from sender to all connected receivers.
|
||||||
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(...))
|
sender -- the sender of the signal
|
||||||
to retrieve the actual receiver objects as an iterable
|
Either a specific object or None.
|
||||||
object.
|
|
||||||
|
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
|
"""Filter sequence of receivers to get resolved, live receivers
|
||||||
|
|
||||||
This is a generator which will iterate over
|
This checks for weak references
|
||||||
the passed sequence, checking for weak references
|
and resolves them, then returning only live
|
||||||
and resolving them, then returning all live
|
|
||||||
receivers.
|
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):
|
if isinstance(receiver, WEAKREF_TYPES):
|
||||||
# Dereference the weak reference.
|
# Dereference the weak reference.
|
||||||
receiver = receiver()
|
receiver = receiver()
|
||||||
|
@ -266,230 +186,58 @@ def liveReceivers(receivers):
|
||||||
else:
|
else:
|
||||||
yield receiver
|
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):
|
def connect(receiver, signal, sender=None, weak=True):
|
||||||
"""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 = {}
|
For backward compatibility only. See Signal.connect()
|
||||||
# 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.
|
warnings.warn(
|
||||||
# Return a list of tuple pairs [(receiver, response), ... ].
|
category = DeprecationWarning,
|
||||||
responses = []
|
message = "dispatcher.connect() is deprecated; use Signal.connect() instead.",
|
||||||
for receiver in getAllReceivers(sender, signal):
|
stacklevel = 2
|
||||||
response = robustapply.robustApply(
|
|
||||||
receiver,
|
|
||||||
signal=signal,
|
|
||||||
sender=sender,
|
|
||||||
*arguments,
|
|
||||||
**named
|
|
||||||
)
|
)
|
||||||
responses.append((receiver, response))
|
return signal.connect(receiver, sender, weak)
|
||||||
return responses
|
|
||||||
|
|
||||||
|
def disconnect(receiver, signal, sender=None, weak=True):
|
||||||
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 backward compatibility only. See Signal.disconnect()
|
||||||
for receiver in liveReceivers(getReceivers(sender, signal)):
|
"""
|
||||||
response = robustapply.robustApply(
|
warnings.warn(
|
||||||
receiver,
|
category = DeprecationWarning,
|
||||||
signal=signal,
|
message = "dispatcher.disconnect() is deprecated; use Signal.disconnect() instead.",
|
||||||
sender=sender,
|
stacklevel = 2
|
||||||
*arguments,
|
|
||||||
**named
|
|
||||||
)
|
)
|
||||||
responses.append((receiver, response))
|
signal.disconnect(receiver, sender, weak)
|
||||||
return responses
|
|
||||||
|
|
||||||
|
def send(signal, sender=None, **named):
|
||||||
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
|
|
||||||
"""
|
"""
|
||||||
try:
|
For backward compatibility only. See Signal.send()
|
||||||
index = receivers.index(receiver)
|
"""
|
||||||
# need to scan back references here and remove senderkey
|
warnings.warn(
|
||||||
except ValueError:
|
category = DeprecationWarning,
|
||||||
return False
|
message = "dispatcher.send() is deprecated; use Signal.send() instead.",
|
||||||
else:
|
stacklevel = 2
|
||||||
oldReceiver = receivers[index]
|
)
|
||||||
del receivers[index]
|
return signal.send(sender=sender, **named)
|
||||||
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 sendExact(signal, sender, **named ):
|
||||||
def _killBackref(receiver, senderkey):
|
"""
|
||||||
"""Do the actual removal of back reference from receiver to senderkey"""
|
This function is deprecated, as it now has the same meaning as send.
|
||||||
receiverkey = id(receiver)
|
"""
|
||||||
receivers_list = sendersBack.get(receiverkey, ())
|
warnings.warn(
|
||||||
while senderkey in receivers_list:
|
category = DeprecationWarning,
|
||||||
try:
|
message = "dispatcher.sendExact() is deprecated; use Signal.send() instead.",
|
||||||
receivers_list.remove(senderkey)
|
stacklevel = 2
|
||||||
except:
|
)
|
||||||
break
|
return signal.send(sender=sender, **named)
|
||||||
if not receivers_list:
|
|
||||||
try:
|
|
||||||
del sendersBack[ receiverkey ]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
return True
|
|
||||||
|
|
|
@ -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
|
Copyright (c) 2001-2003, Patrick K. O'Brien and Contributors
|
||||||
All rights reserved.
|
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
|
import weakref, traceback
|
||||||
|
|
||||||
def safeRef(target, onDelete = None):
|
def safeRef(target, onDelete = None):
|
||||||
|
@ -60,7 +66,9 @@ class BoundMethodWeakref(object):
|
||||||
same BoundMethodWeakref instance.
|
same BoundMethodWeakref instance.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_allInstances = weakref.WeakValueDictionary()
|
_allInstances = weakref.WeakValueDictionary()
|
||||||
|
|
||||||
def __new__( cls, target, onDelete=None, *arguments,**named ):
|
def __new__( cls, target, onDelete=None, *arguments,**named ):
|
||||||
"""Create new instance or return current instance
|
"""Create new instance or return current instance
|
||||||
|
|
||||||
|
@ -83,6 +91,7 @@ class BoundMethodWeakref(object):
|
||||||
cls._allInstances[key] = base
|
cls._allInstances[key] = base
|
||||||
base.__init__( target, onDelete, *arguments,**named)
|
base.__init__( target, onDelete, *arguments,**named)
|
||||||
return base
|
return base
|
||||||
|
|
||||||
def __init__(self, target, onDelete=None):
|
def __init__(self, target, onDelete=None):
|
||||||
"""Return a weak-reference-like instance for a bound method
|
"""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.weakFunc = weakref.ref(target.im_func, remove)
|
||||||
self.selfName = str(target.im_self)
|
self.selfName = str(target.im_self)
|
||||||
self.funcName = str(target.im_func.__name__)
|
self.funcName = str(target.im_func.__name__)
|
||||||
|
|
||||||
def calculateKey( cls, target ):
|
def calculateKey( cls, target ):
|
||||||
"""Calculate the reference key for this reference
|
"""Calculate the reference key for this reference
|
||||||
|
|
||||||
|
@ -130,6 +140,7 @@ class BoundMethodWeakref(object):
|
||||||
"""
|
"""
|
||||||
return (id(target.im_self),id(target.im_func))
|
return (id(target.im_self),id(target.im_func))
|
||||||
calculateKey = classmethod( calculateKey )
|
calculateKey = classmethod( calculateKey )
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Give a friendly representation of the object"""
|
"""Give a friendly representation of the object"""
|
||||||
return """%s( %s.%s )"""%(
|
return """%s( %s.%s )"""%(
|
||||||
|
@ -137,15 +148,19 @@ class BoundMethodWeakref(object):
|
||||||
self.selfName,
|
self.selfName,
|
||||||
self.funcName,
|
self.funcName,
|
||||||
)
|
)
|
||||||
|
|
||||||
__repr__ = __str__
|
__repr__ = __str__
|
||||||
|
|
||||||
def __nonzero__( self ):
|
def __nonzero__( self ):
|
||||||
"""Whether we are still a valid reference"""
|
"""Whether we are still a valid reference"""
|
||||||
return self() is not None
|
return self() is not None
|
||||||
|
|
||||||
def __cmp__( self, other ):
|
def __cmp__( self, other ):
|
||||||
"""Compare with another reference"""
|
"""Compare with another reference"""
|
||||||
if not isinstance (other,self.__class__):
|
if not isinstance (other,self.__class__):
|
||||||
return cmp( self.__class__, type(other) )
|
return cmp( self.__class__, type(other) )
|
||||||
return cmp( self.key, other.key)
|
return cmp( self.key, other.key)
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
"""Return a strong reference to the bound method
|
"""Return a strong reference to the bound method
|
||||||
|
|
||||||
|
@ -224,7 +239,6 @@ class BoundNonDescriptorMethodWeakref(BoundMethodWeakref):
|
||||||
return getattr(target, function.__name__)
|
return getattr(target, function.__name__)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_bound_method_weakref(target, onDelete):
|
def get_bound_method_weakref(target, onDelete):
|
||||||
"""Instantiates the appropiate BoundMethodWeakRef, depending on the details of
|
"""Instantiates the appropiate BoundMethodWeakRef, depending on the details of
|
||||||
the underlying class method implementation"""
|
the underlying class method implementation"""
|
||||||
|
@ -234,4 +248,3 @@ def get_bound_method_weakref(target, onDelete):
|
||||||
else:
|
else:
|
||||||
# no luck, use the alternative implementation:
|
# no luck, use the alternative implementation:
|
||||||
return BoundNonDescriptorMethodWeakref(target=target, onDelete=onDelete)
|
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.base import BaseHandler
|
||||||
from django.core.handlers.wsgi import WSGIRequest
|
from django.core.handlers.wsgi import WSGIRequest
|
||||||
from django.core.signals import got_request_exception
|
from django.core.signals import got_request_exception
|
||||||
from django.dispatch import dispatcher
|
|
||||||
from django.http import SimpleCookie, HttpRequest
|
from django.http import SimpleCookie, HttpRequest
|
||||||
from django.template import TemplateDoesNotExist
|
from django.template import TemplateDoesNotExist
|
||||||
from django.test import signals
|
from django.test import signals
|
||||||
|
@ -59,7 +58,7 @@ class ClientHandler(BaseHandler):
|
||||||
if self._request_middleware is None:
|
if self._request_middleware is None:
|
||||||
self.load_middleware()
|
self.load_middleware()
|
||||||
|
|
||||||
dispatcher.send(signal=signals.request_started)
|
signals.request_started.send(sender=self.__class__)
|
||||||
try:
|
try:
|
||||||
request = WSGIRequest(environ)
|
request = WSGIRequest(environ)
|
||||||
response = self.get_response(request)
|
response = self.get_response(request)
|
||||||
|
@ -69,11 +68,11 @@ class ClientHandler(BaseHandler):
|
||||||
response = middleware_method(request, response)
|
response = middleware_method(request, response)
|
||||||
response = self.apply_response_fixes(request, response)
|
response = self.apply_response_fixes(request, response)
|
||||||
finally:
|
finally:
|
||||||
dispatcher.send(signal=signals.request_finished)
|
signals.request_finished.send(sender=self.__class__)
|
||||||
|
|
||||||
return response
|
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.
|
Stores templates and contexts that are rendered.
|
||||||
"""
|
"""
|
||||||
|
@ -160,7 +159,7 @@ class Client:
|
||||||
self.cookies = SimpleCookie()
|
self.cookies = SimpleCookie()
|
||||||
self.exc_info = None
|
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.
|
Stores exceptions when they are generated by a view.
|
||||||
"""
|
"""
|
||||||
|
@ -202,10 +201,10 @@ class Client:
|
||||||
# callback function.
|
# callback function.
|
||||||
data = {}
|
data = {}
|
||||||
on_template_render = curry(store_rendered_templates, 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.
|
# 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:
|
try:
|
||||||
response = self.handler(environ)
|
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.db import connection, get_creation_module
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.dispatch import dispatcher
|
|
||||||
from django.test import signals
|
from django.test import signals
|
||||||
from django.template import Template
|
from django.template import Template
|
||||||
from django.utils.translation import deactivate
|
from django.utils.translation import deactivate
|
||||||
|
@ -17,7 +16,7 @@ def instrumented_test_render(self, context):
|
||||||
An instrumented Template render method, providing a signal
|
An instrumented Template render method, providing a signal
|
||||||
that can be intercepted by the test system Client
|
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)
|
return self.nodelist.render(context)
|
||||||
|
|
||||||
class TestSMTPConnection(object):
|
class TestSMTPConnection(object):
|
||||||
|
|
|
@ -3,7 +3,6 @@ Testing signals before/after saving and deleting.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.dispatch import dispatcher
|
|
||||||
|
|
||||||
class Person(models.Model):
|
class Person(models.Model):
|
||||||
first_name = models.CharField(max_length=20)
|
first_name = models.CharField(max_length=20)
|
||||||
|
@ -12,19 +11,12 @@ class Person(models.Model):
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u"%s %s" % (self.first_name, self.last_name)
|
return u"%s %s" % (self.first_name, self.last_name)
|
||||||
|
|
||||||
|
def pre_save_test(signal, sender, instance, **kwargs):
|
||||||
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):
|
|
||||||
print 'pre_save signal,', instance
|
print 'pre_save signal,', instance
|
||||||
if kwargs.get('raw'):
|
if kwargs.get('raw'):
|
||||||
print 'Is raw'
|
print 'Is raw'
|
||||||
|
|
||||||
def post_save_test(sender, instance, **kwargs):
|
def post_save_test(signal, sender, instance, **kwargs):
|
||||||
print 'post_save signal,', instance
|
print 'post_save signal,', instance
|
||||||
if 'created' in kwargs:
|
if 'created' in kwargs:
|
||||||
if kwargs['created']:
|
if kwargs['created']:
|
||||||
|
@ -34,44 +26,36 @@ def post_save_test(sender, instance, **kwargs):
|
||||||
if kwargs.get('raw'):
|
if kwargs.get('raw'):
|
||||||
print 'Is 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 'pre_delete signal,', instance
|
||||||
print 'instance.id is not None: %s' % (instance.id != None)
|
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 'post_delete signal,', instance
|
||||||
print 'instance.id is None: %s' % (instance.id == None)
|
print 'instance.id is None: %s' % (instance.id == None)
|
||||||
|
|
||||||
__test__ = {'API_TESTS':"""
|
__test__ = {'API_TESTS':"""
|
||||||
>>> dispatcher.connect(pre_save_nokwargs_test, signal=models.signals.pre_save)
|
>>> models.signals.pre_save.connect(pre_save_test)
|
||||||
>>> dispatcher.connect(post_save_nokwargs_test, signal=models.signals.post_save)
|
>>> models.signals.post_save.connect(post_save_test)
|
||||||
>>> dispatcher.connect(pre_save_test, signal=models.signals.pre_save)
|
>>> models.signals.pre_delete.connect(pre_delete_test)
|
||||||
>>> dispatcher.connect(post_save_test, signal=models.signals.post_save)
|
>>> models.signals.post_delete.connect(post_delete_test)
|
||||||
>>> dispatcher.connect(pre_delete_test, signal=models.signals.pre_delete)
|
|
||||||
>>> dispatcher.connect(post_delete_test, signal=models.signals.post_delete)
|
|
||||||
|
|
||||||
>>> p1 = Person(first_name='John', last_name='Smith')
|
>>> p1 = Person(first_name='John', last_name='Smith')
|
||||||
>>> p1.save()
|
>>> p1.save()
|
||||||
pre_save_nokwargs signal
|
|
||||||
pre_save signal, John Smith
|
pre_save signal, John Smith
|
||||||
post_save_nokwargs signal
|
|
||||||
post_save signal, John Smith
|
post_save signal, John Smith
|
||||||
Is created
|
Is created
|
||||||
|
|
||||||
>>> p1.first_name = 'Tom'
|
>>> p1.first_name = 'Tom'
|
||||||
>>> p1.save()
|
>>> p1.save()
|
||||||
pre_save_nokwargs signal
|
|
||||||
pre_save signal, Tom Smith
|
pre_save signal, Tom Smith
|
||||||
post_save_nokwargs signal
|
|
||||||
post_save signal, Tom Smith
|
post_save signal, Tom Smith
|
||||||
Is updated
|
Is updated
|
||||||
|
|
||||||
# Calling an internal method purely so that we can trigger a "raw" save.
|
# Calling an internal method purely so that we can trigger a "raw" save.
|
||||||
>>> p1.save_base(raw=True)
|
>>> p1.save_base(raw=True)
|
||||||
pre_save_nokwargs signal
|
|
||||||
pre_save signal, Tom Smith
|
pre_save signal, Tom Smith
|
||||||
Is raw
|
Is raw
|
||||||
post_save_nokwargs signal
|
|
||||||
post_save signal, Tom Smith
|
post_save signal, Tom Smith
|
||||||
Is updated
|
Is updated
|
||||||
Is raw
|
Is raw
|
||||||
|
@ -85,17 +69,13 @@ instance.id is None: False
|
||||||
>>> p2 = Person(first_name='James', last_name='Jones')
|
>>> p2 = Person(first_name='James', last_name='Jones')
|
||||||
>>> p2.id = 99999
|
>>> p2.id = 99999
|
||||||
>>> p2.save()
|
>>> p2.save()
|
||||||
pre_save_nokwargs signal
|
|
||||||
pre_save signal, James Jones
|
pre_save signal, James Jones
|
||||||
post_save_nokwargs signal
|
|
||||||
post_save signal, James Jones
|
post_save signal, James Jones
|
||||||
Is created
|
Is created
|
||||||
|
|
||||||
>>> p2.id = 99998
|
>>> p2.id = 99998
|
||||||
>>> p2.save()
|
>>> p2.save()
|
||||||
pre_save_nokwargs signal
|
|
||||||
pre_save signal, James Jones
|
pre_save signal, James Jones
|
||||||
post_save_nokwargs signal
|
|
||||||
post_save signal, James Jones
|
post_save signal, James Jones
|
||||||
Is created
|
Is created
|
||||||
|
|
||||||
|
@ -108,10 +88,8 @@ instance.id is None: False
|
||||||
>>> Person.objects.all()
|
>>> Person.objects.all()
|
||||||
[<Person: James Jones>]
|
[<Person: James Jones>]
|
||||||
|
|
||||||
>>> dispatcher.disconnect(pre_save_nokwargs_test, signal=models.signals.pre_save)
|
>>> models.signals.post_delete.disconnect(post_delete_test)
|
||||||
>>> dispatcher.disconnect(post_save_nokwargs_test, signal=models.signals.post_save)
|
>>> models.signals.pre_delete.disconnect(pre_delete_test)
|
||||||
>>> dispatcher.disconnect(post_delete_test, signal=models.signals.post_delete)
|
>>> models.signals.post_save.disconnect(post_save_test)
|
||||||
>>> dispatcher.disconnect(pre_delete_test, signal=models.signals.pre_delete)
|
>>> models.signals.pre_save.disconnect(pre_save_test)
|
||||||
>>> dispatcher.disconnect(post_save_test, signal=models.signals.post_save)
|
|
||||||
>>> dispatcher.disconnect(pre_save_test, signal=models.signals.pre_save)
|
|
||||||
"""}
|
"""}
|
||||||
|
|
|
@ -2,6 +2,5 @@
|
||||||
Unit-tests for the dispatch project
|
Unit-tests for the dispatch project
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from test_dispatcher import *
|
|
||||||
from test_robustapply import *
|
|
||||||
from test_saferef import *
|
from test_saferef import *
|
||||||
|
from test_dispatcher import *
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from django.dispatch.dispatcher import *
|
from django.dispatch import Signal
|
||||||
from django.dispatch import dispatcher, robust
|
|
||||||
import unittest
|
import unittest
|
||||||
import copy
|
import copy
|
||||||
import sys
|
import sys
|
||||||
|
@ -15,143 +14,94 @@ else:
|
||||||
def garbage_collect():
|
def garbage_collect():
|
||||||
gc.collect()
|
gc.collect()
|
||||||
|
|
||||||
def x(a):
|
def receiver_1_arg(val, **kwargs):
|
||||||
return a
|
return val
|
||||||
|
|
||||||
class Dummy(object):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Callable(object):
|
class Callable(object):
|
||||||
def __call__(self, a):
|
def __call__(self, val, **kwargs):
|
||||||
return a
|
return val
|
||||||
|
|
||||||
def a(self, a):
|
def a(self, val, **kwargs):
|
||||||
return a
|
return val
|
||||||
|
|
||||||
|
a_signal = Signal(providing_args=["val"])
|
||||||
|
|
||||||
class DispatcherTests(unittest.TestCase):
|
class DispatcherTests(unittest.TestCase):
|
||||||
"""Test suite for dispatcher (barely started)"""
|
"""Test suite for dispatcher (barely started)"""
|
||||||
|
|
||||||
def setUp(self):
|
def _testIsClean(self, signal):
|
||||||
# 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):
|
|
||||||
"""Assert that everything has been cleaned up automatically"""
|
"""Assert that everything has been cleaned up automatically"""
|
||||||
self.assertEqual(dispatcher.sendersBack, self.sendersBack)
|
self.assertEqual(signal.receivers, [])
|
||||||
self.assertEqual(dispatcher.connections, self.connections)
|
|
||||||
self.assertEqual(dispatcher.senders, self.senders)
|
# force cleanup just in case
|
||||||
|
signal.receivers = []
|
||||||
|
|
||||||
def testExact(self):
|
def testExact(self):
|
||||||
a = Dummy()
|
a_signal.connect(receiver_1_arg, sender=self)
|
||||||
signal = 'this'
|
expected = [(receiver_1_arg,"test")]
|
||||||
connect(x, signal, a)
|
result = a_signal.send(sender=self, val="test")
|
||||||
expected = [(x,a)]
|
|
||||||
result = send('this',a, a=a)
|
|
||||||
self.assertEqual(result, expected)
|
self.assertEqual(result, expected)
|
||||||
disconnect(x, signal, a)
|
a_signal.disconnect(receiver_1_arg, sender=self)
|
||||||
self.assertEqual(list(getAllReceivers(a,signal)), [])
|
self._testIsClean(a_signal)
|
||||||
self._testIsClean()
|
|
||||||
|
|
||||||
def testAnonymousSend(self):
|
def testIgnoredSender(self):
|
||||||
a = Dummy()
|
a_signal.connect(receiver_1_arg)
|
||||||
signal = 'this'
|
expected = [(receiver_1_arg,"test")]
|
||||||
connect(x, signal)
|
result = a_signal.send(sender=self, val="test")
|
||||||
expected = [(x,a)]
|
|
||||||
result = send(signal,None, a=a)
|
|
||||||
self.assertEqual(result, expected)
|
self.assertEqual(result, expected)
|
||||||
disconnect(x, signal)
|
a_signal.disconnect(receiver_1_arg)
|
||||||
self.assertEqual(list(getAllReceivers(None,signal)), [])
|
self._testIsClean(a_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()
|
|
||||||
|
|
||||||
def testGarbageCollected(self):
|
def testGarbageCollected(self):
|
||||||
a = Callable()
|
a = Callable()
|
||||||
b = Dummy()
|
a_signal.connect(a.a, sender=self)
|
||||||
signal = 'this'
|
|
||||||
connect(a.a, signal, b)
|
|
||||||
expected = []
|
expected = []
|
||||||
del a
|
del a
|
||||||
garbage_collect()
|
garbage_collect()
|
||||||
result = send('this',b, a=b)
|
result = a_signal.send(sender=self, val="test")
|
||||||
self.assertEqual(result, expected)
|
self.assertEqual(result, expected)
|
||||||
self.assertEqual(list(getAllReceivers(b,signal)), [])
|
self._testIsClean(a_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()
|
|
||||||
|
|
||||||
|
|
||||||
def testMultipleRegistration(self):
|
def testMultipleRegistration(self):
|
||||||
a = Callable()
|
a = Callable()
|
||||||
b = Dummy()
|
a_signal.connect(a)
|
||||||
signal = 'this'
|
a_signal.connect(a)
|
||||||
connect(a, signal, b)
|
a_signal.connect(a)
|
||||||
connect(a, signal, b)
|
a_signal.connect(a)
|
||||||
connect(a, signal, b)
|
a_signal.connect(a)
|
||||||
connect(a, signal, b)
|
a_signal.connect(a)
|
||||||
connect(a, signal, b)
|
result = a_signal.send(sender=self, val="test")
|
||||||
connect(a, signal, b)
|
|
||||||
result = send('this',b, a=b)
|
|
||||||
self.assertEqual(len(result), 1)
|
self.assertEqual(len(result), 1)
|
||||||
self.assertEqual(len(list(getAllReceivers(b,signal))), 1)
|
self.assertEqual(len(a_signal.receivers), 1)
|
||||||
del a
|
del a
|
||||||
del b
|
|
||||||
del result
|
del result
|
||||||
garbage_collect()
|
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):
|
def testRobust(self):
|
||||||
"""Test the sendRobust function"""
|
"""Test the sendRobust function"""
|
||||||
def fails():
|
def fails(val, **kwargs):
|
||||||
raise ValueError('this')
|
raise ValueError('this')
|
||||||
a = object()
|
a_signal.connect(fails)
|
||||||
signal = 'this'
|
result = a_signal.send_robust(sender=self, val="test")
|
||||||
connect(fails, Any, a)
|
|
||||||
result = robust.sendRobust('this',a, a=a)
|
|
||||||
err = result[0][1]
|
err = result[0][1]
|
||||||
self.assert_(isinstance(err, ValueError))
|
self.assert_(isinstance(err, ValueError))
|
||||||
self.assertEqual(err.args, ('this',))
|
self.assertEqual(err.args, ('this',))
|
||||||
|
a_signal.disconnect(fails)
|
||||||
|
self._testIsClean(a_signal)
|
||||||
|
|
||||||
def getSuite():
|
def getSuite():
|
||||||
return unittest.makeSuite(DispatcherTests,'test')
|
return unittest.makeSuite(DispatcherTests,'test')
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
from django.dispatch.robustapply import *
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
def noArgument():
|
|
||||||
pass
|
|
||||||
|
|
||||||
def oneArgument(blah):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def twoArgument(blah, other):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class TestCases(unittest.TestCase):
|
|
||||||
def test01(self):
|
|
||||||
robustApply(noArgument)
|
|
||||||
|
|
||||||
def test02(self):
|
|
||||||
self.assertRaises(TypeError, robustApply, noArgument, "this")
|
|
||||||
|
|
||||||
def test03(self):
|
|
||||||
self.assertRaises(TypeError, robustApply, oneArgument)
|
|
||||||
|
|
||||||
def test04(self):
|
|
||||||
"""Raise error on duplication of a particular argument"""
|
|
||||||
self.assertRaises(TypeError, robustApply, oneArgument, "this", blah = "that")
|
|
||||||
|
|
||||||
def getSuite():
|
|
||||||
return unittest.makeSuite(TestCases,'test')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
||||||
|
|
Loading…
Reference in New Issue