1 """
2 General event handling.
3
4 Basically, everything roughly classified as user interaction should go
5 through this module. gavo.base, on import, creates an instance of
6 EventDispatcher and installs it as base.ui. The rest of the library
7 can then call methods of base.ui.
8
9 Clients can then register observers (probably derived from
10 base.observer.Observer) that subscribe to events and can display or
11 log them in some form appropriate to the client.
12 """
13
14
15
16
17
18
19
20 import contextlib
21 import sys
22
23 from gavo import utils
24
25
26 PDB_ENABLED = False
29 """is a metaclass for dispatching of messages.
30
31 Basically, you define methods called notify<whatever> in your class.
32 For each of them, a subscribe<whatever> method is added.
33
34 Then, when notify<whatever> is called, your defined method is called,
35 and its result is then passed to all callbacks passed in through
36 subscribe<whatever>.
37 """
42
44 cls.eventTypes.append(name)
45 def notify(self, *args, **kwargs):
46 res = callable(self, *args, **kwargs)
47 for callback in self.callbacks[name]:
48 callback(res)
49 return res
50 def subscribe(self, callback):
51 self.subscribe(name, callback)
52 def unsubscribe(self, callback):
53 self.unsubscribe(name, callback)
54 setattr(cls, "notify"+name, notify)
55 setattr(cls, "subscribe"+name, subscribe)
56 setattr(cls, "unsubscribe"+name, unsubscribe)
57
59 for name, val in dict.iteritems():
60 if name.startswith("notify"):
61 cls._makeNotifier(name[6:], val)
62
65 """is the central event dispatcher.
66
67 Events are posted by using notify* methods. Various handlers can
68 then attach to them.
69 """
70 __metaclass__ = DispatcherType
71
73 self.callbacks = dict((name, []) for name in self.eventTypes)
74 self.sourceStack = [None]
75 self.curSource = None
76 self.totalShippedOut = 0
77 self.totalRead = 0
78 self.lastRow = None
79
80 @contextlib.contextmanager
82 """a context manager suspending notification for a specific event.
83
84 This is mainly for use by test code that wants to avoid spilling
85 too much junk into the log.
86
87 One weak point here is that any subscriptions entered while notification
88 is suspended are lost. So: Don't suspend notifications for normal code.
89 """
90 origCallbacks = self.callbacks[evName]
91 self.callbacks[evName] = []
92 try:
93 yield
94 finally:
95 self.callbacks[evName] = origCallbacks
96
98 self.callbacks[evName].append(callback)
99
101 """removes a callback from evName's callback list.
102
103 It is not an error to unsubscribe a callback that's not subscribed.
104 """
105 try:
106 self.callbacks[evName].remove(callback)
107 except ValueError:
108 pass
109
111 """is called when an exception is being handled by raising newExc.
112
113 The callbacks are passed a pair of sys.exc_info() and newExc.
114 """
115 return sys.exc_info(), newExc
116
118 """notifies of and ExceptionMutation and returns newExc.
119
120 This is just a convenience when mutating exceptions.
121 """
122 if PDB_ENABLED:
123
124 raise
125 self.notifyExceptionMutation(newExc)
126 return newExc
127
129 """is called when a new source is being operated on.
130
131 The callbacks are passed some, hopefully useful, token string. For
132 file source, this is the file name, otherwise we try to make up
133 something.
134
135 As side effects, the curSource attribute is set to this value.
136 """
137 sourceName = utils.makeSourceEllipsis(sourceToken)
138 self.curSource = sourceName
139 self.sourceStack.append(sourceName)
140 return sourceName
141
143 """is called when a parse error occurred in a source.
144
145 The callbacks are passed the name of the failing source.
146 """
147 if self.sourceStack:
148 lastSource = self.sourceStack.pop()
149 else:
150 lastSource = "Undefined"
151 try:
152 self.curSource = self.sourceStack[-1]
153 except IndexError:
154 self.curSource = None
155 return lastSource
156
158 """is called when a source file has been processed.
159
160 The curSource attribute is updated, and its old value is propagated
161 to the callbacks.
162 """
163 try:
164 lastSource = self.sourceStack.pop()
165 self.curSource = self.sourceStack[-1]
166 except IndexError:
167
168
169 lastSource = None
170 return lastSource
171
173 """is called when certain table implementations store items.
174
175 The number of items is passed on to the callbacks. As a side effect,
176 the instance variable totalShippedOut is adjusted.
177
178 InMemoryTables don't call this right now and probably never will.
179 """
180 self.totalShippedOut += numItems
181 return numItems
182
184 """is called when certain grammars yield a row to the DC's belly.
185
186 The callbacks receive a reference to the row. As a side effect,
187 the instance variable totalRead is bumped up, and lastRow becomes
188 the row passed in.
189
190 To support this, RowIterators have to call this method in their
191 _iterRows. Most will do, DictlistGrammars, e.g., don't.
192 """
193 self.totalRead += 1
194 self.lastRow = row
195 return row
196
198 """is called when an index on a DB table is created.
199
200 The callbacks receive the index name.
201 """
202 return indexName
203
205 """is called when a script is being started.
206
207 The callback receives a scripting.ScriptRunner instance. You probably
208 want to use the name attribute and not much else.
209 """
210 return script
211
213 """is called when something wants to put out an error message.
214
215 The handlers receive the error message as-is.
216
217 In general, you will be in an exception context when you receive
218 this error, but your handlers should not bomb out when you are not.
219 """
220 return errmsg
221
223 """is called when an unexpected twisted failure is being processed.
224
225 You should not listen on this, since the handler just receives None.
226 Rather, these events are converted to ErrorOccurreds including the
227 failure's traceback.
228 """
229 self.notifyError(failure.getErrorMessage()+" (see info for traceback)")
230 self.notifyInfo("Traceback of failure just logged:\n%s"%
231 failure.getTraceback())
232
234 """is called when something tries to emit communicate non-fatal trouble.
235
236 The handlers receive the message as-is
237 """
238 return message
239
241 """is called when something tries to emit auxiliary information.
242
243 The handlers receive the message as-is
244 """
245 return message
246
248 """is called when something wants to communicate information only
249 useful when trying to figure out a malfunction.
250
251 The handlers receive the message as-is.
252 """
253 return message
254
256 """is called when the webserver is up and running.
257
258 No arguments are transmitted.
259 """
260 return ()
261
263 """is called when an existing database table has been modified.
264
265 The argument is the fully qualified table name.
266 """
267 return fqName
268