django_signal 1.0a

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/django_signal.rb +150 -0
  2. metadata +46 -0
@@ -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 ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: django_signal
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0a
5
+ prerelease: 3
6
+ platform: ruby
7
+ authors:
8
+ - Paulo Köch
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-19 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description:
15
+ email:
16
+ - paulo.koch@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/django_signal.rb
22
+ homepage: https://github.com/pkoch/django_signal
23
+ licenses: []
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ none: false
36
+ requirements:
37
+ - - ! '>'
38
+ - !ruby/object:Gem::Version
39
+ version: 1.3.1
40
+ requirements: []
41
+ rubyforge_project:
42
+ rubygems_version: 1.8.15
43
+ signing_key:
44
+ specification_version: 3
45
+ summary: A port of Django's Signal.
46
+ test_files: []