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.
Files changed (2) hide show
  1. data/lib/django_signal.rb +150 -0
  2. 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: '1.0'
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: