django_signal 1.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/django_signal.rb +150 -0
- metadata +3 -2
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'weakref'
|
3
|
+
|
4
|
+
#
|
5
|
+
# DjangoSignal
|
6
|
+
# See https://docs.djangoproject.com/en/dev/topics/signals/#defining-and-sending-signals
|
7
|
+
#
|
8
|
+
# Obviously, I didn't wire this signals to ActiveRecord.
|
9
|
+
#
|
10
|
+
# Also, I took the liberty to make some adaptations. Namely:
|
11
|
+
# * No explicit weakref stuff. Want a weakref? Build if yourself.
|
12
|
+
# Invalid refs are handled gracefuly.
|
13
|
+
# * No providing_args, since they had no functional relevance.
|
14
|
+
#
|
15
|
+
class DjangoSignal
|
16
|
+
#
|
17
|
+
# Create a new signal.
|
18
|
+
#
|
19
|
+
def initialize
|
20
|
+
@receivers = {}
|
21
|
+
@lock = Mutex.new
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# Connect receiver to sender for signal.
|
26
|
+
#
|
27
|
+
# +receiver+:: A callable which is to receive signals.
|
28
|
+
# If dispatch_uid is given, the receiver will not be added if
|
29
|
+
# another receiver already exists with that dispatch_uid.
|
30
|
+
# +sender+:: The sender to which the receiver should respond or nil to
|
31
|
+
# receive events from any sender.
|
32
|
+
# +dispatch_uid+:: An identifier used to uniquely identify a particular
|
33
|
+
# instance of a receiver. This will usually be a symbol,
|
34
|
+
# though it may be anything with an object_id.
|
35
|
+
#
|
36
|
+
def connect(receiver, sender=nil, dispatch_uid=nil)
|
37
|
+
lookup_key = make_key(receiver, sender, dispatch_uid)
|
38
|
+
|
39
|
+
@lock.synchronize {
|
40
|
+
@receivers[lookup_key] ||= receiver
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# Disconnect receiver from sender for signal.
|
46
|
+
#
|
47
|
+
# If weak references are used, disconnect need not be called. The receiver
|
48
|
+
# will be remove from dispatch automatically.
|
49
|
+
#
|
50
|
+
# +receiver+:: The registered receiver to disconnect. May be nil if
|
51
|
+
# dispatch_uid is specified.
|
52
|
+
# +sender+:: The registered sender to disconnect
|
53
|
+
# +dispatch_uid+:: the unique identifier of the receiver to disconnect
|
54
|
+
#
|
55
|
+
def disconnect(receiver, sender=nil, dispatch_uid=nil)
|
56
|
+
lookup_key = make_key(receiver, sender, dispatch_uid)
|
57
|
+
|
58
|
+
@lock.synchronize {
|
59
|
+
@receivers.delete(lookup_key)
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Send signal from sender to all connected receivers.
|
65
|
+
#
|
66
|
+
# If any receiver raises an error, the error propagates back through send,
|
67
|
+
# terminating the dispatch loop, so it is quite possible to not have all
|
68
|
+
# receivers called if a raises an error.
|
69
|
+
#
|
70
|
+
# +sender+:: The sender of the signal. Either a specific object or nil.
|
71
|
+
# +args+:: Args which will be passed to receivers.
|
72
|
+
#
|
73
|
+
# Returns a hash +{receiver => response, ... }+.
|
74
|
+
#
|
75
|
+
def send(sender, *args)
|
76
|
+
self.mapper(self.method(:simple_call), sender, *args)
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# Send signal from sender to all connected receivers catching errors.
|
81
|
+
#
|
82
|
+
# If any receiver raises an error (specifically any subclass of Exception),
|
83
|
+
# the error instance is returned as the result for that receiver.
|
84
|
+
#
|
85
|
+
# +sender+:: The sender of the signal. Either a specific object or nil.
|
86
|
+
# +args+:: Args which will be passed to receivers.
|
87
|
+
#
|
88
|
+
# Returns a hash +{receiver => response, ... }+.
|
89
|
+
#
|
90
|
+
def send_robust(sender, *args)
|
91
|
+
self.mapper(self.method(:robust_call), sender, *args)
|
92
|
+
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
def make_key(receiver, sender, dispatch_uid=nil)
|
96
|
+
[
|
97
|
+
dispatch_uid || receiver.object_id,
|
98
|
+
sender.object_id
|
99
|
+
]
|
100
|
+
end
|
101
|
+
|
102
|
+
def simple_call(receiver, sender, *args)
|
103
|
+
opts = {}
|
104
|
+
if args.last.is_a?(Hash)
|
105
|
+
opts = args.pop
|
106
|
+
end
|
107
|
+
opts.merge!(
|
108
|
+
:signal => self,
|
109
|
+
:sender => sender
|
110
|
+
)
|
111
|
+
args.push(opts)
|
112
|
+
|
113
|
+
receiver.call(*args)
|
114
|
+
end
|
115
|
+
|
116
|
+
def robust_call(receiver, sender, *args)
|
117
|
+
begin
|
118
|
+
simple_call(receiver, sender, *args)
|
119
|
+
rescue WeakRef::RefError
|
120
|
+
raise
|
121
|
+
rescue Exception => e
|
122
|
+
e
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def mapper(kaller, sender, *args)
|
127
|
+
return {} if not @receivers
|
128
|
+
|
129
|
+
this_sender_id = sender.object_id
|
130
|
+
Hash[
|
131
|
+
@receivers.select do |(receiver_id, target_sender_id), receiver|
|
132
|
+
target_sender_id == nil.object_id or target_sender_id == this_sender_id
|
133
|
+
end.map do |_, receiver|
|
134
|
+
begin
|
135
|
+
[
|
136
|
+
receiver,
|
137
|
+
kaller.call(receiver, sender, *args)
|
138
|
+
]
|
139
|
+
rescue WeakRef::RefError
|
140
|
+
@lock.synchronize {
|
141
|
+
while (k = @receivers.key(receiver)) do
|
142
|
+
@receivers.delete(k)
|
143
|
+
end
|
144
|
+
}
|
145
|
+
next
|
146
|
+
end
|
147
|
+
end
|
148
|
+
]
|
149
|
+
end
|
150
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: django_signal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -17,7 +17,8 @@ email:
|
|
17
17
|
executables: []
|
18
18
|
extensions: []
|
19
19
|
extra_rdoc_files: []
|
20
|
-
files:
|
20
|
+
files:
|
21
|
+
- lib/django_signal.rb
|
21
22
|
homepage: https://github.com/pkoch/django_signal
|
22
23
|
licenses: []
|
23
24
|
post_install_message:
|