Opened 16 years ago
Closed 13 years ago
#9015 closed New feature (wontfix)
Signal Connection Decorators
Reported by: | zvoase | Owned by: | Brian Rosner |
---|---|---|---|
Component: | Core (Other) | Version: | dev |
Severity: | Normal | Keywords: | signals |
Cc: | semente+djangoproject@…, john+djangoproject@… | Triage Stage: | Design decision needed |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | yes |
Easy pickings: | no | UI/UX: | no |
Description
Usually, signal receivers are defined as functions and then connected to a specific signal via a function call to the signal instance's connect
method. The problem is, this registration of the receiver is located outside the receiver's definition. This can cause clutter, it violates DRY, and it is not very Pythonic in style. Several examples of the current usage pattern are included in the signal docs, so I don't need to go into too much detail.
I propose a change to the connect
method on the django.dispatch.dispatcher.Signal
class, which would allow you to decorate a signal receiver function and therefore skip the explicit call to the method. The usage of the new method would look something like this:
from django.db.models.signals import pre_save # Just an example of a signal. @pre_save.connect(sender=MyModel) # Other keyword arguments may be given. def receiver(sender, instance, *args, **kwargs): pass # Do something here.
I've written a patch which does exactly this, preserving backwards compatibility also (although that's not the biggest issue right now; signals are a very recent addition). It works by moving the current Signal.connect
method to Signal._connect
, replacing it with a small wrapper around the original which, when called, does one of two things:
- If called with positional arguments (and, optionally, keyword arguments), it behaves like the old
Signal.connect
, connecting the given receiver function to the signal. This allows it to be used as both a decorator and a registration function. - If called with keyword arguments but no positional arguments, it returns a wrapper function, allowing it to decorate the receiver function and include the given keyword arguments (thanks to Python's lexical scoping).
This means it will work like so (with the effects easy to determine from the code):
from django.db.models.signals import pre_save # Decorator with no args. @pre_save.connect def recvr1(sender, instance, *args, **kwargs): pass # ... # Registration with only the receiver. pre_save.connect(recvr1) # Decorator with keyword args. @pre_save.connect(sender=MyModel) def recvr2(sender, instance, *args, **kwargs): pass # ... # Registration with both receiver and keyword args. pre_save.connect(recvr2, sender=MyModel)
Attachments (3)
Change History (27)
by , 16 years ago
Attachment: | signal_deco_patch.diff added |
---|
comment:1 by , 16 years ago
Component: | Uncategorized → Core framework |
---|
comment:2 by , 16 years ago
Patch needs improvement: | set |
---|
I've just realised that the patch is not from the top of the source tree. I'm on the verge of kicking myself.
Will submit a better patch promptly.
comment:3 by , 16 years ago
Owner: | changed from | to
---|---|
Patch needs improvement: | unset |
Status: | new → assigned |
Submitted new patch, claimed the ticket, and made the executive decision not to move the 'connect' method around, in the interest of not (potentially) screwing things up.
comment:4 by , 16 years ago
Triage Stage: | Unreviewed → Accepted |
---|
comment:6 by , 15 years ago
Needs tests: | set |
---|
Marking as Needs tests. I'd love to see this get into 1.2.
comment:7 by , 15 years ago
Needs documentation: | set |
---|
Also marking as "Needs documentation", since this affects the public api
comment:8 by , 15 years ago
wouldn't it be nicer to have the decorator like this?
from django.db.models.signals import connect, pre_save # Decorator with no args. @connect(pre_save) def recvr1(sender, instance, *args, **kwargs): pass # ... # Decorator with keyword args. @connect(pre_save, sender=MyModel) def recvr2(sender, instance, *args, **kwargs): pass # ...
comment:9 by , 15 years ago
Untested example:
def connect(signal, **kwargs): def receive(receiver): signal.connect(receiver, **kwargs) return receiver return receive
comment:10 by , 15 years ago
Owner: | changed from | to
---|---|
Status: | assigned → new |
I've made a slightly new patch and started discussion at http://groups.google.com/group/django-developers/msg/eb8cd203df82ad5a
comment:11 by , 15 years ago
Status: | new → assigned |
---|
comment:12 by , 15 years ago
Cc: | added |
---|
comment:13 by , 15 years ago
Cc: | added |
---|
comment:14 by , 15 years ago
I use a slightly more general version of the one posted on 10/04/09 that allows multiple signals at once:
def connect(*signals, **kwds): ... @connect(post_save, post_delete, sender=MyModel) def delete_stale_cache(sender, instance, **kwds): cache.delete('mymodel_%s' % instance.pk)
comment:15 by , 14 years ago
Needs documentation: | unset |
---|---|
Needs tests: | unset |
Triage Stage: | Accepted → Ready for checkin |
comment:17 by , 14 years ago
Triage Stage: | Ready for checkin → Accepted |
---|
Moving to accepted until I can get my patch in.
comment:18 by , 14 years ago
Resolution: | → fixed |
---|---|
Status: | assigned → closed |
follow-up: 20 comment:19 by , 14 years ago
Patch needs improvement: | set |
---|---|
Resolution: | fixed |
Status: | closed → reopened |
Triage Stage: | Accepted → Design decision needed |
Function decorators usually named in the form of a verb - cache_page, require_POST, etc. - so why is this decorator named receiver instead of connect or receive_signal?
comment:20 by , 14 years ago
Replying to anonymous:
Function decorators usually named in the form of a verb - cache_page, require_POST, etc. - so why is this decorator named receiver instead of connect or receive_signal?
I have to say, that's a valid point, e.g. this looks nicer to me, too:
@receive_signal(post_login) def update_last_seen(sender, user, **kwargs): user.last_seen = datetime.now() user.save()
comment:21 by , 14 years ago
Severity: | → Normal |
---|---|
Type: | → New feature |
by , 14 years ago
Attachment: | cc63e2f848524b3e5a994a85891f8212158c13b4.patch added |
---|
Enabling the connect to act as a decorator
comment:22 by , 14 years ago
Easy pickings: | unset |
---|
I've pushed a small update to my branch at https://github.com/jezdez/django/tree/feature%2Fsignal-decorator
comment:23 by , 13 years ago
UI/UX: | unset |
---|
1.3 was released with @receiver
. It doesn't seem worth changing the API now that it has gone public.
comment:24 by , 13 years ago
Resolution: | → wontfix |
---|---|
Status: | reopened → closed |
Patch-v1.diff