##// END OF EJS Templates
pep8, third pass
Barry Wark -
Show More
@@ -1,425 +1,425 b''
1 1 # encoding: utf-8
2 2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
3 3
4 4 """PyObjC classes to provide a Cocoa frontend to the
5 5 IPython.kernel.engineservice.IEngineBase.
6 6
7 7 To add an IPython interpreter to a cocoa app, instantiate an
8 8 IPythonCocoaController in a XIB and connect its textView outlet to an
9 9 NSTextView instance in your UI. That's it.
10 10
11 11 Author: Barry Wark
12 12 """
13 13
14 14 __docformat__ = "restructuredtext en"
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Copyright (C) 2008 The IPython Development Team
18 18 #
19 19 # Distributed under the terms of the BSD License. The full license is in
20 20 # the file COPYING, distributed as part of this software.
21 21 #-----------------------------------------------------------------------------
22 22
23 23 #-----------------------------------------------------------------------------
24 24 # Imports
25 25 #-----------------------------------------------------------------------------
26 26
27 27 import objc
28 28 import uuid
29 29
30 30 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
31 31 NSLog, NSNotificationCenter, NSMakeRange,\
32 32 NSLocalizedString, NSIntersectionRange
33 33
34 34 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
35 35 NSTextView, NSRulerView, NSVerticalRuler
36 36
37 37 from pprint import saferepr
38 38
39 39 import IPython
40 40 from IPython.kernel.engineservice import ThreadedEngineService
41 41 from IPython.frontend.frontendbase import FrontEndBase
42 42
43 43 from twisted.internet.threads import blockingCallFromThread
44 44 from twisted.python.failure import Failure
45 45
46 46 #------------------------------------------------------------------------------
47 47 # Classes to implement the Cocoa frontend
48 48 #------------------------------------------------------------------------------
49 49
50 50 # TODO:
51 51 # 1. use MultiEngineClient and out-of-process engine rather than
52 52 # ThreadedEngineService?
53 53 # 2. integrate Xgrid launching of engines
54 54
55 55
56 56
57 57
58 58 class IPythonCocoaController(NSObject, FrontEndBase):
59 59 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
60 60 waitingForEngine = objc.ivar().bool()
61 61 textView = objc.IBOutlet()
62 62
63 63 def init(self):
64 64 self = super(IPythonCocoaController, self).init()
65 65 FrontEndBase.__init__(self, engine=ThreadedEngineService())
66 66 if(self != None):
67 67 self._common_init()
68 68
69 69 return self
70 70
71 71 def _common_init(self):
72 72 """_common_init"""
73 73
74 74 self.userNS = NSMutableDictionary.dictionary()
75 75 self.waitingForEngine = False
76 76
77 77 self.lines = {}
78 78 self.tabSpaces = 4
79 79 self.tabUsesSpaces = True
80 80 self.currentBlockID = self.next_block_ID()
81 81 self.blockRanges = {} # blockID=>NSRange
82 82
83 83
84 84 def awakeFromNib(self):
85 85 """awakeFromNib"""
86 86
87 87 self._common_init()
88 88
89 89 # Start the IPython engine
90 90 self.engine.startService()
91 91 NSLog('IPython engine started')
92 92
93 93 # Register for app termination
94 94 nc = NSNotificationCenter.defaultCenter()
95 95 nc.addObserver_selector_name_object_(
96 96 self,
97 97 'appWillTerminate:',
98 98 NSApplicationWillTerminateNotification,
99 99 None)
100 100
101 101 self.textView.setDelegate_(self)
102 102 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
103 103 r = NSRulerView.alloc().initWithScrollView_orientation_(
104 104 self.textView.enclosingScrollView(),
105 105 NSVerticalRuler)
106 106 self.verticalRulerView = r
107 107 self.verticalRulerView.setClientView_(self.textView)
108 108 self._start_cli_banner()
109 109
110 110
111 111 def appWillTerminate_(self, notification):
112 112 """appWillTerminate"""
113 113
114 114 self.engine.stopService()
115 115
116 116
117 117 def complete(self, token):
118 118 """Complete token in engine's user_ns
119 119
120 120 Parameters
121 121 ----------
122 122 token : string
123 123
124 124 Result
125 125 ------
126 126 Deferred result of
127 127 IPython.kernel.engineservice.IEngineBase.complete
128 128 """
129 129
130 130 return self.engine.complete(token)
131 131
132 132
133 133 def execute(self, block, blockID=None):
134 134 self.waitingForEngine = True
135 135 self.willChangeValueForKey_('commandHistory')
136 136 d = super(IPythonCocoaController, self).execute(block, blockID)
137 137 d.addBoth(self._engine_done)
138 138 d.addCallback(self._update_user_ns)
139 139
140 140 return d
141 141
142 142
143 143 def _engine_done(self, x):
144 144 self.waitingForEngine = False
145 145 self.didChangeValueForKey_('commandHistory')
146 146 return x
147 147
148 148 def _update_user_ns(self, result):
149 149 """Update self.userNS from self.engine's namespace"""
150 150 d = self.engine.keys()
151 151 d.addCallback(self._get_engine_namespace_values_for_keys)
152 152
153 153 return result
154 154
155 155
156 156 def _get_engine_namespace_values_for_keys(self, keys):
157 157 d = self.engine.pull(keys)
158 158 d.addCallback(self._store_engine_namespace_values, keys=keys)
159 159
160 160
161 161 def _store_engine_namespace_values(self, values, keys=[]):
162 162 assert(len(values) == len(keys))
163 163 self.willChangeValueForKey_('userNS')
164 164 for (k,v) in zip(keys,values):
165 165 self.userNS[k] = saferepr(v)
166 166 self.didChangeValueForKey_('userNS')
167 167
168 168
169 169 def update_cell_prompt(self, result):
170 170 if(isinstance(result, Failure)):
171 171 blockID = result.blockID
172 172 else:
173 173 blockID = result['blockID']
174 174
175 175
176 176 self.insert_text(self.input_prompt(result=result),
177 177 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
178 178 scrollToVisible=False
179 179 )
180 180
181 181 return result
182 182
183 183
184 184 def render_result(self, result):
185 185 blockID = result['blockID']
186 186 inputRange = self.blockRanges[blockID]
187 187 del self.blockRanges[blockID]
188 188
189 189 #print inputRange,self.current_block_range()
190 190 self.insert_text('\n' +
191 191 self.output_prompt(result) +
192 192 result.get('display',{}).get('pprint','') +
193 193 '\n\n',
194 194 textRange=NSMakeRange(inputRange.location+inputRange.length,
195 195 0))
196 196 return result
197 197
198 198
199 199 def render_error(self, failure):
200 200 self.insert_text('\n\n'+str(failure)+'\n\n')
201 201 self.start_new_block()
202 202 return failure
203 203
204 204
205 205 def _start_cli_banner(self):
206 206 """Print banner"""
207 207
208 208 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
209 209 IPython.__version__
210 210
211 211 self.insert_text(banner + '\n\n')
212 212
213 213
214 214 def start_new_block(self):
215 215 """"""
216 216
217 217 self.currentBlockID = self.next_block_ID()
218 218
219 219
220 220
221 221 def next_block_ID(self):
222 222
223 223 return uuid.uuid4()
224 224
225 225 def current_block_range(self):
226 226 return self.blockRanges.get(self.currentBlockID,
227 227 NSMakeRange(self.textView.textStorage().length(),
228 228 0))
229 229
230 def currentBlock(self):
230 def current_block(self):
231 231 """The current block's text"""
232 232
233 return self.textForRange(self.current_block_range())
233 return self.text_for_range(self.current_block_range())
234 234
235 def textForRange(self, textRange):
236 """textForRange"""
235 def text_for_range(self, textRange):
236 """text_for_range"""
237 237
238 238 ts = self.textView.textStorage()
239 239 return ts.string().substringWithRange_(textRange)
240 240
241 def currentLine(self):
242 block = self.textForRange(self.current_block_range())
241 def current_line(self):
242 block = self.text_for_range(self.current_block_range())
243 243 block = block.split('\n')
244 244 return block[-1]
245 245
246 246
247 247 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
248 248 """Insert text into textView at textRange, updating blockRanges
249 249 as necessary
250 250 """
251 251
252 252 if(textRange == None):
253 253 #range for end of text
254 254 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
255 255
256 256 for r in self.blockRanges.itervalues():
257 257 intersection = NSIntersectionRange(r,textRange)
258 258 if(intersection.length == 0): #ranges don't intersect
259 259 if r.location >= textRange.location:
260 260 r.location += len(string)
261 261 else: #ranges intersect
262 262 if(r.location <= textRange.location):
263 263 assert(intersection.length == textRange.length)
264 264 r.length += textRange.length
265 265 else:
266 266 r.location += intersection.length
267 267
268 268 self.textView.replaceCharactersInRange_withString_(
269 269 textRange, string)
270 270 self.textView.setSelectedRange_(
271 271 NSMakeRange(textRange.location+len(string), 0))
272 272 if(scrollToVisible):
273 273 self.textView.scrollRangeToVisible_(textRange)
274 274
275 275
276 276
277 277
278 278 def replace_current_block_with_string(self, textView, string):
279 279 textView.replaceCharactersInRange_withString_(
280 280 self.current_block_range(),
281 281 string)
282 282 self.current_block_range().length = len(string)
283 283 r = NSMakeRange(textView.textStorage().length(), 0)
284 284 textView.scrollRangeToVisible_(r)
285 285 textView.setSelectedRange_(r)
286 286
287 287
288 288 def current_indent_string(self):
289 289 """returns string for indent or None if no indent"""
290 290
291 if(len(self.currentBlock()) > 0):
292 lines = self.currentBlock().split('\n')
291 if(len(self.current_block()) > 0):
292 lines = self.current_block().split('\n')
293 293 currentIndent = len(lines[-1]) - len(lines[-1])
294 294 if(currentIndent == 0):
295 295 currentIndent = self.tabSpaces
296 296
297 297 if(self.tabUsesSpaces):
298 298 result = ' ' * currentIndent
299 299 else:
300 300 result = '\t' * (currentIndent/self.tabSpaces)
301 301 else:
302 302 result = None
303 303
304 304 return result
305 305
306 306
307 307 # NSTextView delegate methods...
308 308 def textView_doCommandBySelector_(self, textView, selector):
309 309 assert(textView == self.textView)
310 310 NSLog("textView_doCommandBySelector_: "+selector)
311 311
312 312
313 313 if(selector == 'insertNewline:'):
314 314 indent = self.current_indent_string()
315 315 if(indent):
316 line = indent + self.currentLine()
316 line = indent + self.current_line()
317 317 else:
318 line = self.currentLine()
318 line = self.current_line()
319 319
320 if(self.is_complete(self.currentBlock())):
321 self.execute(self.currentBlock(),
320 if(self.is_complete(self.current_block())):
321 self.execute(self.current_block(),
322 322 blockID=self.currentBlockID)
323 323 self.start_new_block()
324 324
325 325 return True
326 326
327 327 return False
328 328
329 329 elif(selector == 'moveUp:'):
330 prevBlock = self.get_history_previous(self.currentBlock())
330 prevBlock = self.get_history_previous(self.current_block())
331 331 if(prevBlock != None):
332 332 self.replace_current_block_with_string(textView, prevBlock)
333 333 else:
334 334 NSBeep()
335 335 return True
336 336
337 337 elif(selector == 'moveDown:'):
338 338 nextBlock = self.get_history_next()
339 339 if(nextBlock != None):
340 340 self.replace_current_block_with_string(textView, nextBlock)
341 341 else:
342 342 NSBeep()
343 343 return True
344 344
345 345 elif(selector == 'moveToBeginningOfParagraph:'):
346 346 textView.setSelectedRange_(NSMakeRange(
347 347 self.current_block_range().location,
348 348 0))
349 349 return True
350 350 elif(selector == 'moveToEndOfParagraph:'):
351 351 textView.setSelectedRange_(NSMakeRange(
352 352 self.current_block_range().location + \
353 353 self.current_block_range().length, 0))
354 354 return True
355 355 elif(selector == 'deleteToEndOfParagraph:'):
356 356 if(textView.selectedRange().location <= \
357 357 self.current_block_range().location):
358 358 # Intersect the selected range with the current line range
359 359 if(self.current_block_range().length < 0):
360 360 self.blockRanges[self.currentBlockID].length = 0
361 361
362 362 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
363 363 self.current_block_range())
364 364
365 365 if(r.length > 0): #no intersection
366 366 textView.setSelectedRange_(r)
367 367
368 368 return False # don't actually handle the delete
369 369
370 370 elif(selector == 'insertTab:'):
371 if(len(self.currentLine().strip()) == 0): #only white space
371 if(len(self.current_line().strip()) == 0): #only white space
372 372 return False
373 373 else:
374 374 self.textView.complete_(self)
375 375 return True
376 376
377 377 elif(selector == 'deleteBackward:'):
378 378 #if we're at the beginning of the current block, ignore
379 379 if(textView.selectedRange().location == \
380 380 self.current_block_range().location):
381 381 return True
382 382 else:
383 383 self.current_block_range().length-=1
384 384 return False
385 385 return False
386 386
387 387
388 388 def textView_shouldChangeTextInRanges_replacementStrings_(self,
389 389 textView, ranges, replacementStrings):
390 390 """
391 391 Delegate method for NSTextView.
392 392
393 393 Refuse change text in ranges not at end, but make those changes at
394 394 end.
395 395 """
396 396
397 397 assert(len(ranges) == len(replacementStrings))
398 398 allow = True
399 399 for r,s in zip(ranges, replacementStrings):
400 400 r = r.rangeValue()
401 401 if(textView.textStorage().length() > 0 and
402 402 r.location < self.current_block_range().location):
403 403 self.insert_text(s)
404 404 allow = False
405 405
406 406
407 407 self.blockRanges.setdefault(self.currentBlockID,
408 408 self.current_block_range()).length +=\
409 409 len(s)
410 410
411 411 return allow
412 412
413 413 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
414 414 textView, words, charRange, index):
415 415 try:
416 416 ts = textView.textStorage()
417 417 token = ts.string().substringWithRange_(charRange)
418 418 completions = blockingCallFromThread(self.complete, token)
419 419 except:
420 420 completions = objc.nil
421 421 NSBeep()
422 422
423 423 return (completions,0)
424 424
425 425
@@ -1,344 +1,352 b''
1 1 # encoding: utf-8
2 2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
3 3 """
4 4 frontendbase provides an interface and base class for GUI frontends for
5 5 IPython.kernel/IPython.kernel.core.
6 6
7 7 Frontend implementations will likely want to subclass FrontEndBase.
8 8
9 9 Author: Barry Wark
10 10 """
11 11 __docformat__ = "restructuredtext en"
12 12
13 13 #-------------------------------------------------------------------------------
14 14 # Copyright (C) 2008 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-------------------------------------------------------------------------------
19 19
20 20 #-------------------------------------------------------------------------------
21 21 # Imports
22 22 #-------------------------------------------------------------------------------
23 23 import string
24 24 import uuid
25 25 import _ast
26 26
27 27 import zope.interface as zi
28 28
29 29 from IPython.kernel.core.history import FrontEndHistory
30 30 from IPython.kernel.core.util import Bunch
31 31 from IPython.kernel.engineservice import IEngineCore
32 32
33 33 from twisted.python.failure import Failure
34 34
35 35 ##############################################################################
36 36 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
37 37 # not
38 38
39 39 rc = Bunch()
40 40 rc.prompt_in1 = r'In [$number]: '
41 41 rc.prompt_in2 = r'...'
42 42 rc.prompt_out = r'Out [$number]: '
43 43
44 44 ##############################################################################
45 45
46 46 class IFrontEndFactory(zi.Interface):
47 47 """Factory interface for frontends."""
48 48
49 49 def __call__(engine=None, history=None):
50 50 """
51 51 Parameters:
52 52 interpreter : IPython.kernel.engineservice.IEngineCore
53 53 """
54 54
55 55 pass
56 56
57 57
58 58
59 59 class IFrontEnd(zi.Interface):
60 60 """Interface for frontends. All methods return t.i.d.Deferred"""
61 61
62 62 zi.Attribute("input_prompt_template", "string.Template instance\
63 63 substituteable with execute result.")
64 64 zi.Attribute("output_prompt_template", "string.Template instance\
65 65 substituteable with execute result.")
66 66 zi.Attribute("continuation_prompt_template", "string.Template instance\
67 67 substituteable with execute result.")
68 68
69 69 def update_cell_prompt(self, result):
70 70 """Subclass may override to update the input prompt for a block.
71 71 Since this method will be called as a
72 72 twisted.internet.defer.Deferred's callback,
73 implementations should return result when finished."""
73 implementations should return result when finished.
74
75 NB: result is a failure if the execute returned a failre.
76 To get the blockID, you should do something like::
77 if(isinstance(result, twisted.python.failure.Failure)):
78 blockID = result.blockID
79 else:
80 blockID = result['blockID']
81 """
74 82
75 83 pass
76 84
77 85 def render_result(self, result):
78 86 """Render the result of an execute call. Implementors may choose the
79 87 method of rendering.
80 88 For example, a notebook-style frontend might render a Chaco plot
81 89 inline.
82 90
83 91 Parameters:
84 92 result : dict (result of IEngineBase.execute )
85 93
86 94 Result:
87 95 Output of frontend rendering
88 96 """
89 97
90 98 pass
91 99
92 100 def render_error(self, failure):
93 101 """Subclasses must override to render the failure. Since this method
94 102 ill be called as a twisted.internet.defer.Deferred's callback,
95 103 implementations should return result when finished.
96 104 """
97 105
98 106 pass
99 107
100 108
101 109 def input_prompt(result={}):
102 110 """Returns the input prompt by subsituting into
103 111 self.input_prompt_template
104 112 """
105 113 pass
106 114
107 115 def output_prompt(result):
108 116 """Returns the output prompt by subsituting into
109 117 self.output_prompt_template
110 118 """
111 119
112 120 pass
113 121
114 122 def continuation_prompt():
115 123 """Returns the continuation prompt by subsituting into
116 124 self.continuation_prompt_template
117 125 """
118 126
119 127 pass
120 128
121 129 def is_complete(block):
122 130 """Returns True if block is complete, False otherwise."""
123 131
124 132 pass
125 133
126 134 def compile_ast(block):
127 135 """Compiles block to an _ast.AST"""
128 136
129 137 pass
130 138
131 139
132 140 def get_history_previous(currentBlock):
133 141 """Returns the block previous in the history. Saves currentBlock if
134 142 the history_cursor is currently at the end of the input history"""
135 143 pass
136 144
137 145 def get_history_next():
138 146 """Returns the next block in the history."""
139 147
140 148 pass
141 149
142 150
143 151 class FrontEndBase(object):
144 152 """
145 153 FrontEndBase manages the state tasks for a CLI frontend:
146 154 - Input and output history management
147 155 - Input/continuation and output prompt generation
148 156
149 157 Some issues (due to possibly unavailable engine):
150 158 - How do we get the current cell number for the engine?
151 159 - How do we handle completions?
152 160 """
153 161
154 162 zi.implements(IFrontEnd)
155 163 zi.classProvides(IFrontEndFactory)
156 164
157 165 history_cursor = 0
158 166
159 167 current_indent_level = 0
160 168
161 169
162 170 input_prompt_template = string.Template(rc.prompt_in1)
163 171 output_prompt_template = string.Template(rc.prompt_out)
164 172 continuation_prompt_template = string.Template(rc.prompt_in2)
165 173
166 174 def __init__(self, engine=None, history=None):
167 175 assert(engine==None or IEngineCore.providedBy(engine))
168 176 self.engine = IEngineCore(engine)
169 177 if history is None:
170 178 self.history = FrontEndHistory(input_cache=[''])
171 179 else:
172 180 self.history = history
173 181
174 182
175 183 def input_prompt(self, result={}):
176 184 """Returns the current input prompt
177 185
178 186 It would be great to use ipython1.core.prompts.Prompt1 here
179 187 """
180 188
181 189 result.setdefault('number','')
182 190
183 191 return self.input_prompt_template.safe_substitute(result)
184 192
185 193
186 194 def continuation_prompt(self):
187 195 """Returns the current continuation prompt"""
188 196
189 197 return self.continuation_prompt_template.safe_substitute()
190 198
191 199 def output_prompt(self, result):
192 200 """Returns the output prompt for result"""
193 201
194 202 return self.output_prompt_template.safe_substitute(result)
195 203
196 204
197 205 def is_complete(self, block):
198 206 """Determine if block is complete.
199 207
200 208 Parameters
201 209 block : string
202 210
203 211 Result
204 212 True if block can be sent to the engine without compile errors.
205 213 False otherwise.
206 214 """
207 215
208 216 try:
209 217 ast = self.compile_ast(block)
210 218 except:
211 219 return False
212 220
213 221 lines = block.split('\n')
214 222 return (len(lines)==1 or str(lines[-1])=='')
215 223
216 224
217 225 def compile_ast(self, block):
218 226 """Compile block to an AST
219 227
220 228 Parameters:
221 229 block : str
222 230
223 231 Result:
224 232 AST
225 233
226 234 Throws:
227 235 Exception if block cannot be compiled
228 236 """
229 237
230 238 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
231 239
232 240
233 241 def execute(self, block, blockID=None):
234 242 """Execute the block and return result.
235 243
236 244 Parameters:
237 245 block : {str, AST}
238 246 blockID : any
239 247 Caller may provide an ID to identify this block.
240 248 result['blockID'] := blockID
241 249
242 250 Result:
243 251 Deferred result of self.interpreter.execute
244 252 """
245 253
246 254 if(not self.is_complete(block)):
247 255 return Failure(Exception("Block is not compilable"))
248 256
249 257 if(blockID == None):
250 258 blockID = uuid.uuid4() #random UUID
251 259
252 260 d = self.engine.execute(block)
253 261 d.addCallback(self._add_history, block=block)
254 262 d.addBoth(self._add_block_id, blockID)
255 263 d.addBoth(self.update_cell_prompt)
256 264 d.addCallbacks(self.render_result, errback=self.render_error)
257 265
258 266 return d
259 267
260 268
261 269 def _add_block_id(self, result, blockID):
262 270 """Add the blockID to result or failure. Unfortunatley, we have to
263 271 treat failures differently than result dicts.
264 272 """
265 273
266 274 if(isinstance(result, Failure)):
267 275 result.blockID = blockID
268 276 else:
269 277 result['blockID'] = blockID
270 278
271 279 return result
272 280
273 281 def _add_history(self, result, block=None):
274 282 """Add block to the history"""
275 283
276 284 assert(block != None)
277 285 self.history.add_items([block])
278 286 self.history_cursor += 1
279 287
280 288 return result
281 289
282 290
283 291 def get_history_previous(self, currentBlock):
284 292 """ Returns previous history string and decrement history cursor.
285 293 """
286 294 command = self.history.get_history_item(self.history_cursor - 1)
287 295
288 296 if command is not None:
289 297 if(self.history_cursor == len(self.history.input_cache)):
290 298 self.history.input_cache[self.history_cursor] = currentBlock
291 299 self.history_cursor -= 1
292 300 return command
293 301
294 302
295 303 def get_history_next(self):
296 304 """ Returns next history string and increment history cursor.
297 305 """
298 306 command = self.history.get_history_item(self.history_cursor+1)
299 307
300 308 if command is not None:
301 309 self.history_cursor += 1
302 310 return command
303 311
304 312 ###
305 313 # Subclasses probably want to override these methods...
306 314 ###
307 315
308 316 def update_cell_prompt(self, result):
309 317 """Subclass may override to update the input prompt for a block.
310 318 Since this method will be called as a
311 319 twisted.internet.defer.Deferred's callback, implementations should
312 320 return result when finished.
313 321
314 NP: result is a failure if the execute returned a failre.
322 NB: result is a failure if the execute returned a failre.
315 323 To get the blockID, you should do something like::
316 324 if(isinstance(result, twisted.python.failure.Failure)):
317 325 blockID = result.blockID
318 326 else:
319 327 blockID = result['blockID']
320 328
321 329
322 330 """
323 331
324 332 return result
325 333
326 334
327 335 def render_result(self, result):
328 336 """Subclasses must override to render result. Since this method will
329 337 be called as a twisted.internet.defer.Deferred's callback,
330 338 implementations should return result when finished.
331 339 """
332 340
333 341 return result
334 342
335 343
336 344 def render_error(self, failure):
337 345 """Subclasses must override to render the failure. Since this method
338 346 will be called as a twisted.internet.defer.Deferred's callback,
339 347 implementations should return result when finished."""
340 348
341 349 return failure
342 350
343 351
344 352
General Comments 0
You need to be logged in to leave comments. Login now