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