##// END OF EJS Templates
pep8 compliance, first pass
Barry Wark -
Show More
@@ -1,395 +1,415 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: ipython1.frontend.cocoa.tests.test_cocoa_frontend -*-
2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
3
3
4 """PyObjC classes to provide a Cocoa frontend to the ipython1.kernel.engineservice.EngineService.
4 """PyObjC classes to provide a Cocoa frontend to the
5 IPython.kernel.engineservice.EngineService.
5
6
6 The Cocoa frontend is divided into two classes:
7 To add an IPython interpreter to a cocoa app, instantiate an
7 - IPythonCocoaController
8 IPythonCocoaController in a XIB and connect its textView outlet to an
8 - IPythonCLITextViewDelegate
9 NSTextView instance in your UI. That's it.
9
10
10 To add an IPython interpreter to a cocoa app, instantiate both of these classes in an XIB...[FINISH]
11 Author: Barry Wark
11 """
12 """
12
13
13 __docformat__ = "restructuredtext en"
14 __docformat__ = "restructuredtext en"
14
15
15 #-------------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
16 # Copyright (C) 2008 Barry Wark <barrywark@gmail.com>
17 # Copyright (C) 2008 The IPython Development Team
17 #
18 #
18 # Distributed under the terms of the BSD License. The full license is in
19 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
20 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
21
22
22 #-------------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
23 # Imports
24 # Imports
24 #-------------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
25
26
26 import objc
27 import objc
27 import uuid
28 import uuid
28
29
29 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
30 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
30 NSLog, NSNotificationCenter, NSMakeRange,\
31 NSLog, NSNotificationCenter, NSMakeRange,\
31 NSLocalizedString, NSIntersectionRange
32 NSLocalizedString, NSIntersectionRange
32
33
33 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
34 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
34 NSTextView, NSRulerView, NSVerticalRuler
35 NSTextView, NSRulerView, NSVerticalRuler
35
36
36 from pprint import saferepr
37 from pprint import saferepr
37
38
38 import IPython
39 import IPython
39 from IPython.kernel.engineservice import EngineService, ThreadedEngineService
40 from IPython.kernel.engineservice import EngineService, ThreadedEngineService
40 from IPython.frontend.frontendbase import FrontEndBase
41 from IPython.frontend.frontendbase import FrontEndBase
41
42
42 from twisted.internet.threads import blockingCallFromThread
43 from twisted.internet.threads import blockingCallFromThread
43 from twisted.python.failure import Failure
44 from twisted.python.failure import Failure
44
45
45 #-------------------------------------------------------------------------------
46 #------------------------------------------------------------------------------
46 # Classes to implement the Cocoa frontend
47 # Classes to implement the Cocoa frontend
47 #-------------------------------------------------------------------------------
48 #------------------------------------------------------------------------------
48
49
49 # TODO:
50 # TODO:
50 # 1. use MultiEngineClient and out-of-process engine rather than ThreadedEngineService?
51 # 1. use MultiEngineClient and out-of-process engine rather than
52 # ThreadedEngineService?
51 # 2. integrate Xgrid launching of engines
53 # 2. integrate Xgrid launching of engines
52
54
53
55
54
56
55
57
56 class IPythonCocoaController(NSObject, FrontEndBase):
58 class IPythonCocoaController(NSObject, FrontEndBase):
57 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
59 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
58 waitingForEngine = objc.ivar().bool()
60 waitingForEngine = objc.ivar().bool()
59 textView = objc.IBOutlet()
61 textView = objc.IBOutlet()
60
62
61 def init(self):
63 def init(self):
62 self = super(IPythonCocoaController, self).init()
64 self = super(IPythonCocoaController, self).init()
63 FrontEndBase.__init__(self, engine=ThreadedEngineService())
65 FrontEndBase.__init__(self, engine=ThreadedEngineService())
64 if(self != None):
66 if(self != None):
65 self._common_init()
67 self._common_init()
66
68
67 return self
69 return self
68
70
69 def _common_init(self):
71 def _common_init(self):
70 """_common_init"""
72 """_common_init"""
71
73
72 self.userNS = NSMutableDictionary.dictionary()
74 self.userNS = NSMutableDictionary.dictionary()
73 self.waitingForEngine = False
75 self.waitingForEngine = False
74
76
75 self.lines = {}
77 self.lines = {}
76 self.tabSpaces = 4
78 self.tabSpaces = 4
77 self.tabUsesSpaces = True
79 self.tabUsesSpaces = True
78 self.currentBlockID = self.nextBlockID()
80 self.currentBlockID = self.nextBlockID()
79 self.blockRanges = {} # blockID=>NSRange
81 self.blockRanges = {} # blockID=>NSRange
80
82
81
83
82 def awakeFromNib(self):
84 def awakeFromNib(self):
83 """awakeFromNib"""
85 """awakeFromNib"""
84
86
85 self._common_init()
87 self._common_init()
86
88
87 # Start the IPython engine
89 # Start the IPython engine
88 self.engine.startService()
90 self.engine.startService()
89 NSLog('IPython engine started')
91 NSLog('IPython engine started')
90
92
91 # Register for app termination
93 # Register for app termination
92 NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(self,
94 NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
95 self,
93 'appWillTerminate:',
96 'appWillTerminate:',
94 NSApplicationWillTerminateNotification,
97 NSApplicationWillTerminateNotification,
95 None)
98 None)
96
99
97 self.textView.setDelegate_(self)
100 self.textView.setDelegate_(self)
98 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
101 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
99 self.verticalRulerView = NSRulerView.alloc().initWithScrollView_orientation_(
102 self.verticalRulerView = NSRulerView.alloc().initWithScrollView_orientation_(
100 self.textView.enclosingScrollView(),
103 self.textView.enclosingScrollView(),
101 NSVerticalRuler)
104 NSVerticalRuler)
102 self.verticalRulerView.setClientView_(self.textView)
105 self.verticalRulerView.setClientView_(self.textView)
103 self.startCLIForTextView()
106 self.startCLIForTextView()
104
107
105
108
106 def appWillTerminate_(self, notification):
109 def appWillTerminate_(self, notification):
107 """appWillTerminate"""
110 """appWillTerminate"""
108
111
109 self.engine.stopService()
112 self.engine.stopService()
110
113
111
114
112 def complete(self, token):
115 def complete(self, token):
113 """Complete token in engine's user_ns
116 """Complete token in engine's user_ns
114
117
115 Parameters
118 Parameters
116 ----------
119 ----------
117 token : string
120 token : string
118
121
119 Result
122 Result
120 ------
123 ------
121 Deferred result of ipython1.kernel.engineservice.IEngineInteractive.complete
124 Deferred result of ipython1.kernel.engineservice.IEngineInteractive.complete
122 """
125 """
123
126
124 return self.engine.complete(token)
127 return self.engine.complete(token)
125
128
126
129
127 def execute(self, block, blockID=None):
130 def execute(self, block, blockID=None):
128 self.waitingForEngine = True
131 self.waitingForEngine = True
129 self.willChangeValueForKey_('commandHistory')
132 self.willChangeValueForKey_('commandHistory')
130 d = super(IPythonCocoaController, self).execute(block, blockID)
133 d = super(IPythonCocoaController, self).execute(block, blockID)
131 d.addBoth(self._engineDone)
134 d.addBoth(self._engineDone)
132 d.addCallback(self._updateUserNS)
135 d.addCallback(self._updateUserNS)
133
136
134 return d
137 return d
135
138
136
139
137 def _engineDone(self, x):
140 def _engineDone(self, x):
138 self.waitingForEngine = False
141 self.waitingForEngine = False
139 self.didChangeValueForKey_('commandHistory')
142 self.didChangeValueForKey_('commandHistory')
140 return x
143 return x
141
144
142 def _updateUserNS(self, result):
145 def _updateUserNS(self, result):
143 """Update self.userNS from self.engine's namespace"""
146 """Update self.userNS from self.engine's namespace"""
144 d = self.engine.keys()
147 d = self.engine.keys()
145 d.addCallback(self._getEngineNamepsaceValuesForKeys)
148 d.addCallback(self._getEngineNamepsaceValuesForKeys)
146
149
147 return result
150 return result
148
151
149
152
150 def _getEngineNamepsaceValuesForKeys(self, keys):
153 def _getEngineNamepsaceValuesForKeys(self, keys):
151 d = self.engine.pull(keys)
154 d = self.engine.pull(keys)
152 d.addCallback(self._storeEngineNamespaceValues, keys=keys)
155 d.addCallback(self._storeEngineNamespaceValues, keys=keys)
153
156
154
157
155 def _storeEngineNamespaceValues(self, values, keys=[]):
158 def _storeEngineNamespaceValues(self, values, keys=[]):
156 assert(len(values) == len(keys))
159 assert(len(values) == len(keys))
157 self.willChangeValueForKey_('userNS')
160 self.willChangeValueForKey_('userNS')
158 for (k,v) in zip(keys,values):
161 for (k,v) in zip(keys,values):
159 self.userNS[k] = saferepr(v)
162 self.userNS[k] = saferepr(v)
160 self.didChangeValueForKey_('userNS')
163 self.didChangeValueForKey_('userNS')
161
164
162
165
163 def startCLIForTextView(self):
166 def startCLIForTextView(self):
164 """Print banner"""
167 """Print banner"""
165
168
166 banner = """IPython1 %s -- An enhanced Interactive Python.""" % IPython.__version__
169 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
170 IPython.__version__
167
171
168 self.insert_text(banner + '\n\n')
172 self.insert_text(banner + '\n\n')
169
173
170 # NSTextView/IPythonTextView delegate methods
174 # NSTextView/IPythonTextView delegate methods
171 def textView_doCommandBySelector_(self, textView, selector):
175 def textView_doCommandBySelector_(self, textView, selector):
172 assert(textView == self.textView)
176 assert(textView == self.textView)
173 NSLog("textView_doCommandBySelector_: "+selector)
177 NSLog("textView_doCommandBySelector_: "+selector)
174
178
175
179
176 if(selector == 'insertNewline:'):
180 if(selector == 'insertNewline:'):
177 indent = self.currentIndentString()
181 indent = self.currentIndentString()
178 if(indent):
182 if(indent):
179 line = indent + self.currentLine()
183 line = indent + self.currentLine()
180 else:
184 else:
181 line = self.currentLine()
185 line = self.currentLine()
182
186
183 if(self.is_complete(self.currentBlock())):
187 if(self.is_complete(self.currentBlock())):
184 self.execute(self.currentBlock(),
188 self.execute(self.currentBlock(),
185 blockID=self.currentBlockID)
189 blockID=self.currentBlockID)
186 self.startNewBlock()
190 self.startNewBlock()
187
191
188 return True
192 return True
189
193
190 return False
194 return False
191
195
192 elif(selector == 'moveUp:'):
196 elif(selector == 'moveUp:'):
193 prevBlock = self.get_history_previous(self.currentBlock())
197 prevBlock = self.get_history_previous(self.currentBlock())
194 if(prevBlock != None):
198 if(prevBlock != None):
195 self.replaceCurrentBlockWithString(textView, prevBlock)
199 self.replaceCurrentBlockWithString(textView, prevBlock)
196 else:
200 else:
197 NSBeep()
201 NSBeep()
198 return True
202 return True
199
203
200 elif(selector == 'moveDown:'):
204 elif(selector == 'moveDown:'):
201 nextBlock = self.get_history_next()
205 nextBlock = self.get_history_next()
202 if(nextBlock != None):
206 if(nextBlock != None):
203 self.replaceCurrentBlockWithString(textView, nextBlock)
207 self.replaceCurrentBlockWithString(textView, nextBlock)
204 else:
208 else:
205 NSBeep()
209 NSBeep()
206 return True
210 return True
207
211
208 elif(selector == 'moveToBeginningOfParagraph:'):
212 elif(selector == 'moveToBeginningOfParagraph:'):
209 textView.setSelectedRange_(NSMakeRange(self.currentBlockRange().location, 0))
213 textView.setSelectedRange_(NSMakeRange(
214 self.currentBlockRange().location,
215 0))
210 return True
216 return True
211 elif(selector == 'moveToEndOfParagraph:'):
217 elif(selector == 'moveToEndOfParagraph:'):
212 textView.setSelectedRange_(NSMakeRange(self.currentBlockRange().location + \
218 textView.setSelectedRange_(NSMakeRange(
219 self.currentBlockRange().location + \
213 self.currentBlockRange().length, 0))
220 self.currentBlockRange().length, 0))
214 return True
221 return True
215 elif(selector == 'deleteToEndOfParagraph:'):
222 elif(selector == 'deleteToEndOfParagraph:'):
216 if(textView.selectedRange().location <= self.currentBlockRange().location):
223 if(textView.selectedRange().location <= \
224 self.currentBlockRange().location):
217 # Intersect the selected range with the current line range
225 # Intersect the selected range with the current line range
218 if(self.currentBlockRange().length < 0):
226 if(self.currentBlockRange().length < 0):
219 self.blockRanges[self.currentBlockID].length = 0
227 self.blockRanges[self.currentBlockID].length = 0
220
228
221 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
229 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
222 self.currentBlockRange())
230 self.currentBlockRange())
223
231
224 if(r.length > 0): #no intersection
232 if(r.length > 0): #no intersection
225 textView.setSelectedRange_(r)
233 textView.setSelectedRange_(r)
226
234
227 return False # don't actually handle the delete
235 return False # don't actually handle the delete
228
236
229 elif(selector == 'insertTab:'):
237 elif(selector == 'insertTab:'):
230 if(len(self.currentLine().strip()) == 0): #only white space
238 if(len(self.currentLine().strip()) == 0): #only white space
231 return False
239 return False
232 else:
240 else:
233 self.textView.complete_(self)
241 self.textView.complete_(self)
234 return True
242 return True
235
243
236 elif(selector == 'deleteBackward:'):
244 elif(selector == 'deleteBackward:'):
237 #if we're at the beginning of the current block, ignore
245 #if we're at the beginning of the current block, ignore
238 if(textView.selectedRange().location == self.currentBlockRange().location):
246 if(textView.selectedRange().location == \
247 self.currentBlockRange().location):
239 return True
248 return True
240 else:
249 else:
241 self.currentBlockRange().length-=1
250 self.currentBlockRange().length-=1
242 return False
251 return False
243 return False
252 return False
244
253
245
254
246 def textView_shouldChangeTextInRanges_replacementStrings_(self, textView, ranges, replacementStrings):
255 def textView_shouldChangeTextInRanges_replacementStrings_(self,
256 textView, ranges, replacementStrings):
247 """
257 """
248 Delegate method for NSTextView.
258 Delegate method for NSTextView.
249
259
250 Refuse change text in ranges not at end, but make those changes at end.
260 Refuse change text in ranges not at end, but make those changes at
261 end.
251 """
262 """
252
263
253 #print 'textView_shouldChangeTextInRanges_replacementStrings_:',ranges,replacementStrings
254 assert(len(ranges) == len(replacementStrings))
264 assert(len(ranges) == len(replacementStrings))
255 allow = True
265 allow = True
256 for r,s in zip(ranges, replacementStrings):
266 for r,s in zip(ranges, replacementStrings):
257 r = r.rangeValue()
267 r = r.rangeValue()
258 if(textView.textStorage().length() > 0 and
268 if(textView.textStorage().length() > 0 and
259 r.location < self.currentBlockRange().location):
269 r.location < self.currentBlockRange().location):
260 self.insert_text(s)
270 self.insert_text(s)
261 allow = False
271 allow = False
262
272
263
273
264 self.blockRanges.setdefault(self.currentBlockID, self.currentBlockRange()).length += len(s)
274 self.blockRanges.setdefault(self.currentBlockID,
275 self.currentBlockRange()).length +=\
276 len(s)
265
277
266 return allow
278 return allow
267
279
268 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self, textView, words, charRange, index):
280 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
281 textView, words, charRange, index):
269 try:
282 try:
270 token = textView.textStorage().string().substringWithRange_(charRange)
283 ts = textView.textStorage()
284 token = ts.string().substringWithRange_(charRange)
271 completions = blockingCallFromThread(self.complete, token)
285 completions = blockingCallFromThread(self.complete, token)
272 except:
286 except:
273 completions = objc.nil
287 completions = objc.nil
274 NSBeep()
288 NSBeep()
275
289
276 return (completions,0)
290 return (completions,0)
277
291
278
292
279 def startNewBlock(self):
293 def startNewBlock(self):
280 """"""
294 """"""
281
295
282 self.currentBlockID = self.nextBlockID()
296 self.currentBlockID = self.nextBlockID()
283
297
284
298
285
299
286 def nextBlockID(self):
300 def nextBlockID(self):
287
301
288 return uuid.uuid4()
302 return uuid.uuid4()
289
303
290 def currentBlockRange(self):
304 def currentBlockRange(self):
291 return self.blockRanges.get(self.currentBlockID, NSMakeRange(self.textView.textStorage().length(), 0))
305 return self.blockRanges.get(self.currentBlockID,
306 NSMakeRange(self.textView.textStorage().length(),
307 0))
292
308
293 def currentBlock(self):
309 def currentBlock(self):
294 """The current block's text"""
310 """The current block's text"""
295
311
296 return self.textForRange(self.currentBlockRange())
312 return self.textForRange(self.currentBlockRange())
297
313
298 def textForRange(self, textRange):
314 def textForRange(self, textRange):
299 """textForRange"""
315 """textForRange"""
300
316
301 return self.textView.textStorage().string().substringWithRange_(textRange)
317 ts = self.textView.textStorage()
318 return ts.string().substringWithRange_(textRange)
302
319
303 def currentLine(self):
320 def currentLine(self):
304 block = self.textForRange(self.currentBlockRange())
321 block = self.textForRange(self.currentBlockRange())
305 block = block.split('\n')
322 block = block.split('\n')
306 return block[-1]
323 return block[-1]
307
324
308 def update_cell_prompt(self, result):
325 def update_cell_prompt(self, result):
309 if(isinstance(result, Failure)):
326 if(isinstance(result, Failure)):
310 blockID = result.blockID
327 blockID = result.blockID
311 else:
328 else:
312 blockID = result['blockID']
329 blockID = result['blockID']
313
330
314
331
315 self.insert_text(self.input_prompt(result=result),
332 self.insert_text(self.input_prompt(result=result),
316 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
333 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
317 scrollToVisible=False
334 scrollToVisible=False
318 )
335 )
319
336
320 return result
337 return result
321
338
322
339
323 def render_result(self, result):
340 def render_result(self, result):
324 blockID = result['blockID']
341 blockID = result['blockID']
325 inputRange = self.blockRanges[blockID]
342 inputRange = self.blockRanges[blockID]
326 del self.blockRanges[blockID]
343 del self.blockRanges[blockID]
327
344
328 #print inputRange,self.currentBlockRange()
345 #print inputRange,self.currentBlockRange()
329 self.insert_text('\n' +
346 self.insert_text('\n' +
330 self.output_prompt(result) +
347 self.output_prompt(result) +
331 result.get('display',{}).get('pprint','') +
348 result.get('display',{}).get('pprint','') +
332 '\n\n',
349 '\n\n',
333 textRange=NSMakeRange(inputRange.location+inputRange.length, 0))
350 textRange=NSMakeRange(inputRange.location+inputRange.length,
351 0))
334 return result
352 return result
335
353
336
354
337 def render_error(self, failure):
355 def render_error(self, failure):
338 self.insert_text('\n\n'+str(failure)+'\n\n')
356 self.insert_text('\n\n'+str(failure)+'\n\n')
339 self.startNewBlock()
357 self.startNewBlock()
340 return failure
358 return failure
341
359
342
360
343 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
361 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
344 """Insert text into textView at textRange, updating blockRanges as necessary"""
362 """Insert text into textView at textRange, updating blockRanges
363 as necessary
364 """
345
365
346 if(textRange == None):
366 if(textRange == None):
347 textRange = NSMakeRange(self.textView.textStorage().length(), 0) #range for end of text
367 textRange = NSMakeRange(self.textView.textStorage().length(), 0) #range for end of text
348
368
349 for r in self.blockRanges.itervalues():
369 for r in self.blockRanges.itervalues():
350 intersection = NSIntersectionRange(r,textRange)
370 intersection = NSIntersectionRange(r,textRange)
351 if(intersection.length == 0): #ranges don't intersect
371 if(intersection.length == 0): #ranges don't intersect
352 if r.location >= textRange.location:
372 if r.location >= textRange.location:
353 r.location += len(string)
373 r.location += len(string)
354 else: #ranges intersect
374 else: #ranges intersect
355 if(r.location <= textRange.location):
375 if(r.location <= textRange.location):
356 assert(intersection.length == textRange.length)
376 assert(intersection.length == textRange.length)
357 r.length += textRange.length
377 r.length += textRange.length
358 else:
378 else:
359 r.location += intersection.length
379 r.location += intersection.length
360
380
361 self.textView.replaceCharactersInRange_withString_(textRange, string) #textStorage().string()
381 self.textView.replaceCharactersInRange_withString_(textRange, string) #textStorage().string()
362 self.textView.setSelectedRange_(NSMakeRange(textRange.location+len(string), 0))
382 self.textView.setSelectedRange_(NSMakeRange(textRange.location+len(string), 0))
363 if(scrollToVisible):
383 if(scrollToVisible):
364 self.textView.scrollRangeToVisible_(textRange)
384 self.textView.scrollRangeToVisible_(textRange)
365
385
366
386
367
387
368 def replaceCurrentBlockWithString(self, textView, string):
388 def replaceCurrentBlockWithString(self, textView, string):
369 textView.replaceCharactersInRange_withString_(self.currentBlockRange(),
389 textView.replaceCharactersInRange_withString_(self.currentBlockRange(),
370 string)
390 string)
371 self.currentBlockRange().length = len(string)
391 self.currentBlockRange().length = len(string)
372 r = NSMakeRange(textView.textStorage().length(), 0)
392 r = NSMakeRange(textView.textStorage().length(), 0)
373 textView.scrollRangeToVisible_(r)
393 textView.scrollRangeToVisible_(r)
374 textView.setSelectedRange_(r)
394 textView.setSelectedRange_(r)
375
395
376
396
377 def currentIndentString(self):
397 def currentIndentString(self):
378 """returns string for indent or None if no indent"""
398 """returns string for indent or None if no indent"""
379
399
380 if(len(self.currentBlock()) > 0):
400 if(len(self.currentBlock()) > 0):
381 lines = self.currentBlock().split('\n')
401 lines = self.currentBlock().split('\n')
382 currentIndent = len(lines[-1]) - len(lines[-1])
402 currentIndent = len(lines[-1]) - len(lines[-1])
383 if(currentIndent == 0):
403 if(currentIndent == 0):
384 currentIndent = self.tabSpaces
404 currentIndent = self.tabSpaces
385
405
386 if(self.tabUsesSpaces):
406 if(self.tabUsesSpaces):
387 result = ' ' * currentIndent
407 result = ' ' * currentIndent
388 else:
408 else:
389 result = '\t' * (currentIndent/self.tabSpaces)
409 result = '\t' * (currentIndent/self.tabSpaces)
390 else:
410 else:
391 result = None
411 result = None
392
412
393 return result
413 return result
394
414
395
415
@@ -1,77 +1,72 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """This file contains unittests for the ipython1.frontend.cocoa.cocoa_frontend module.
2 """This file contains unittests for the
3
3 IPython.frontend.cocoa.cocoa_frontend module.
4 Things that should be tested:
5
6 - IPythonCocoaController instantiates an IEngineInteractive
7 - IPythonCocoaController executes code on the engine
8 - IPythonCocoaController mirrors engine's user_ns
9 """
4 """
10 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
11
6
12 #-------------------------------------------------------------------------------
7 #---------------------------------------------------------------------------
13 # Copyright (C) 2005 Fernando Perez <fperez@colorado.edu>
8 # Copyright (C) 2005 The IPython Development Team
14 # Brian E Granger <ellisonbg@gmail.com>
15 # Benjamin Ragan-Kelley <benjaminrk@gmail.com>
16 #
9 #
17 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
19 #-------------------------------------------------------------------------------
12 #---------------------------------------------------------------------------
20
13
21 #-------------------------------------------------------------------------------
14 #---------------------------------------------------------------------------
22 # Imports
15 # Imports
23 #-------------------------------------------------------------------------------
16 #---------------------------------------------------------------------------
24 from IPython.kernel.core.interpreter import Interpreter
17 from IPython.kernel.core.interpreter import Interpreter
25 import IPython.kernel.engineservice as es
18 import IPython.kernel.engineservice as es
26 from IPython.testing.util import DeferredTestCase
19 from IPython.testing.util import DeferredTestCase
27 from twisted.internet.defer import succeed
20 from twisted.internet.defer import succeed
28 from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController
21 from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController
29
22
30 from Foundation import NSMakeRect
23 from Foundation import NSMakeRect
31 from AppKit import NSTextView, NSScrollView
24 from AppKit import NSTextView, NSScrollView
32
25
33 class TestIPythonCocoaControler(DeferredTestCase):
26 class TestIPythonCocoaControler(DeferredTestCase):
34 """Tests for IPythonCocoaController"""
27 """Tests for IPythonCocoaController"""
35
28
36 def setUp(self):
29 def setUp(self):
37 self.controller = IPythonCocoaController.alloc().init()
30 self.controller = IPythonCocoaController.alloc().init()
38 self.engine = es.EngineService()
31 self.engine = es.EngineService()
39 self.engine.startService()
32 self.engine.startService()
40
33
41
34
42 def tearDown(self):
35 def tearDown(self):
43 self.controller = None
36 self.controller = None
44 self.engine.stopService()
37 self.engine.stopService()
45
38
46 def testControllerExecutesCode(self):
39 def testControllerExecutesCode(self):
47 code ="""5+5"""
40 code ="""5+5"""
48 expected = Interpreter().execute(code)
41 expected = Interpreter().execute(code)
49 del expected['number']
42 del expected['number']
50 def removeNumberAndID(result):
43 def removeNumberAndID(result):
51 del result['number']
44 del result['number']
52 del result['id']
45 del result['id']
53 return result
46 return result
54 self.assertDeferredEquals(self.controller.execute(code).addCallback(removeNumberAndID), expected)
47 self.assertDeferredEquals(
48 self.controller.execute(code).addCallback(removeNumberAndID),
49 expected)
55
50
56 def testControllerMirrorsUserNSWithValuesAsStrings(self):
51 def testControllerMirrorsUserNSWithValuesAsStrings(self):
57 code = """userns1=1;userns2=2"""
52 code = """userns1=1;userns2=2"""
58 def testControllerUserNS(result):
53 def testControllerUserNS(result):
59 self.assertEquals(self.controller.userNS['userns1'], 1)
54 self.assertEquals(self.controller.userNS['userns1'], 1)
60 self.assertEquals(self.controller.userNS['userns2'], 2)
55 self.assertEquals(self.controller.userNS['userns2'], 2)
61
56
62 self.controller.execute(code).addCallback(testControllerUserNS)
57 self.controller.execute(code).addCallback(testControllerUserNS)
63
58
64
59
65 def testControllerInstantiatesIEngine(self):
60 def testControllerInstantiatesIEngine(self):
66 self.assert_(es.IEngineBase.providedBy(self.controller.engine))
61 self.assert_(es.IEngineBase.providedBy(self.controller.engine))
67
62
68 def testControllerCompletesToken(self):
63 def testControllerCompletesToken(self):
69 code = """longNameVariable=10"""
64 code = """longNameVariable=10"""
70 def testCompletes(result):
65 def testCompletes(result):
71 self.assert_("longNameVariable" in result)
66 self.assert_("longNameVariable" in result)
72
67
73 def testCompleteToken(result):
68 def testCompleteToken(result):
74 self.controller.complete("longNa").addCallback(testCompletes)
69 self.controller.complete("longNa").addCallback(testCompletes)
75
70
76 self.controller.execute(code).addCallback(testCompletes)
71 self.controller.execute(code).addCallback(testCompletes)
77
72
@@ -1,326 +1,344 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
2 """
3 """
3 frontendbase provides an interface and base class for GUI frontends for IPython.kernel/IPython.kernel.core.
4 frontendbase provides an interface and base class for GUI frontends for
5 IPython.kernel/IPython.kernel.core.
4
6
5 Frontend implementations will likely want to subclass FrontEndBase.
7 Frontend implementations will likely want to subclass FrontEndBase.
6
8
7 Author: Barry Wark
9 Author: Barry Wark
8 """
10 """
9 __docformat__ = "restructuredtext en"
11 __docformat__ = "restructuredtext en"
10
12
11 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
14 # Copyright (C) 2008 The IPython Development Team
13 #
15 #
14 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
17
19
18 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
19 # Imports
21 # Imports
20 #-------------------------------------------------------------------------------
22 #-------------------------------------------------------------------------------
21 import string
23 import string
22 import uuid
24 import uuid
23 import _ast
25 import _ast
24
26
25 import zope.interface as zi
27 import zope.interface as zi
26
28
27 from IPython.kernel.core.history import FrontEndHistory
29 from IPython.kernel.core.history import FrontEndHistory
28 from IPython.kernel.core.util import Bunch
30 from IPython.kernel.core.util import Bunch
29 from IPython.kernel.engineservice import IEngineCore
31 from IPython.kernel.engineservice import IEngineCore
30
32
31 from twisted.python.failure import Failure
33 from twisted.python.failure import Failure
32
34
33 ##############################################################################
35 ##############################################################################
34 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
36 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
35 # not
37 # not
36
38
37 rc = Bunch()
39 rc = Bunch()
38 rc.prompt_in1 = r'In [$number]: '
40 rc.prompt_in1 = r'In [$number]: '
39 rc.prompt_in2 = r'...'
41 rc.prompt_in2 = r'...'
40 rc.prompt_out = r'Out [$number]: '
42 rc.prompt_out = r'Out [$number]: '
41
43
42 ##############################################################################
44 ##############################################################################
43
45
44 class IFrontEndFactory(zi.Interface):
46 class IFrontEndFactory(zi.Interface):
45 """Factory interface for frontends."""
47 """Factory interface for frontends."""
46
48
47 def __call__(engine=None, history=None):
49 def __call__(engine=None, history=None):
48 """
50 """
49 Parameters:
51 Parameters:
50 interpreter : IPython.kernel.engineservice.IEngineCore
52 interpreter : IPython.kernel.engineservice.IEngineCore
51 """
53 """
52
54
53 pass
55 pass
54
56
55
57
56
58
57 class IFrontEnd(zi.Interface):
59 class IFrontEnd(zi.Interface):
58 """Interface for frontends. All methods return t.i.d.Deferred"""
60 """Interface for frontends. All methods return t.i.d.Deferred"""
59
61
60 zi.Attribute("input_prompt_template", "string.Template instance substituteable with execute result.")
62 zi.Attribute("input_prompt_template", "string.Template instance\
61 zi.Attribute("output_prompt_template", "string.Template instance substituteable with execute result.")
63 substituteable with execute result.")
62 zi.Attribute("continuation_prompt_template", "string.Template instance substituteable with execute result.")
64 zi.Attribute("output_prompt_template", "string.Template instance\
65 substituteable with execute result.")
66 zi.Attribute("continuation_prompt_template", "string.Template instance\
67 substituteable with execute result.")
63
68
64 def update_cell_prompt(self, result):
69 def update_cell_prompt(self, result):
65 """Subclass may override to update the input prompt for a block.
70 """Subclass may override to update the input prompt for a block.
66 Since this method will be called as a twisted.internet.defer.Deferred's callback,
71 Since this method will be called as a
72 twisted.internet.defer.Deferred's callback,
67 implementations should return result when finished."""
73 implementations should return result when finished."""
68
74
69 pass
75 pass
70
76
71 def render_result(self, result):
77 def render_result(self, result):
72 """Render the result of an execute call. Implementors may choose the method of rendering.
78 """Render the result of an execute call. Implementors may choose the
73 For example, a notebook-style frontend might render a Chaco plot inline.
79 method of rendering.
80 For example, a notebook-style frontend might render a Chaco plot
81 inline.
74
82
75 Parameters:
83 Parameters:
76 result : dict (result of IEngineBase.execute )
84 result : dict (result of IEngineBase.execute )
77
85
78 Result:
86 Result:
79 Output of frontend rendering
87 Output of frontend rendering
80 """
88 """
81
89
82 pass
90 pass
83
91
84 def render_error(self, failure):
92 def render_error(self, failure):
85 """Subclasses must override to render the failure. Since this method will be called as a
93 """Subclasses must override to render the failure. Since this method
86 twisted.internet.defer.Deferred's callback, implementations should return result
94 ill be called as a twisted.internet.defer.Deferred's callback,
87 when finished."""
95 implementations should return result when finished.
96 """
88
97
89 pass
98 pass
90
99
91
100
92 def input_prompt(result={}):
101 def input_prompt(result={}):
93 """Returns the input prompt by subsituting into self.input_prompt_template"""
102 """Returns the input prompt by subsituting into
103 self.input_prompt_template
104 """
94 pass
105 pass
95
106
96 def output_prompt(result):
107 def output_prompt(result):
97 """Returns the output prompt by subsituting into self.output_prompt_template"""
108 """Returns the output prompt by subsituting into
109 self.output_prompt_template
110 """
98
111
99 pass
112 pass
100
113
101 def continuation_prompt():
114 def continuation_prompt():
102 """Returns the continuation prompt by subsituting into self.continuation_prompt_template"""
115 """Returns the continuation prompt by subsituting into
116 self.continuation_prompt_template
117 """
103
118
104 pass
119 pass
105
120
106 def is_complete(block):
121 def is_complete(block):
107 """Returns True if block is complete, False otherwise."""
122 """Returns True if block is complete, False otherwise."""
108
123
109 pass
124 pass
110
125
111 def compile_ast(block):
126 def compile_ast(block):
112 """Compiles block to an _ast.AST"""
127 """Compiles block to an _ast.AST"""
113
128
114 pass
129 pass
115
130
116
131
117 def get_history_previous(currentBlock):
132 def get_history_previous(currentBlock):
118 """Returns the block previous in the history. Saves currentBlock if
133 """Returns the block previous in the history. Saves currentBlock if
119 the history_cursor is currently at the end of the input history"""
134 the history_cursor is currently at the end of the input history"""
120 pass
135 pass
121
136
122 def get_history_next():
137 def get_history_next():
123 """Returns the next block in the history."""
138 """Returns the next block in the history."""
124
139
125 pass
140 pass
126
141
127
142
128 class FrontEndBase(object):
143 class FrontEndBase(object):
129 """
144 """
130 FrontEndBase manages the state tasks for a CLI frontend:
145 FrontEndBase manages the state tasks for a CLI frontend:
131 - Input and output history management
146 - Input and output history management
132 - Input/continuation and output prompt generation
147 - Input/continuation and output prompt generation
133
148
134 Some issues (due to possibly unavailable engine):
149 Some issues (due to possibly unavailable engine):
135 - How do we get the current cell number for the engine?
150 - How do we get the current cell number for the engine?
136 - How do we handle completions?
151 - How do we handle completions?
137 """
152 """
138
153
139 zi.implements(IFrontEnd)
154 zi.implements(IFrontEnd)
140 zi.classProvides(IFrontEndFactory)
155 zi.classProvides(IFrontEndFactory)
141
156
142 history_cursor = 0
157 history_cursor = 0
143
158
144 current_indent_level = 0
159 current_indent_level = 0
145
160
146
161
147 input_prompt_template = string.Template(rc.prompt_in1)
162 input_prompt_template = string.Template(rc.prompt_in1)
148 output_prompt_template = string.Template(rc.prompt_out)
163 output_prompt_template = string.Template(rc.prompt_out)
149 continuation_prompt_template = string.Template(rc.prompt_in2)
164 continuation_prompt_template = string.Template(rc.prompt_in2)
150
165
151 def __init__(self, engine=None, history=None):
166 def __init__(self, engine=None, history=None):
152 assert(engine==None or IEngineCore.providedBy(engine))
167 assert(engine==None or IEngineCore.providedBy(engine))
153 self.engine = IEngineCore(engine)
168 self.engine = IEngineCore(engine)
154 if history is None:
169 if history is None:
155 self.history = FrontEndHistory(input_cache=[''])
170 self.history = FrontEndHistory(input_cache=[''])
156 else:
171 else:
157 self.history = history
172 self.history = history
158
173
159
174
160 def input_prompt(self, result={}):
175 def input_prompt(self, result={}):
161 """Returns the current input prompt
176 """Returns the current input prompt
162
177
163 It would be great to use ipython1.core.prompts.Prompt1 here
178 It would be great to use ipython1.core.prompts.Prompt1 here
164 """
179 """
165
180
166 result.setdefault('number','')
181 result.setdefault('number','')
167
182
168 return self.input_prompt_template.safe_substitute(result)
183 return self.input_prompt_template.safe_substitute(result)
169
184
170
185
171 def continuation_prompt(self):
186 def continuation_prompt(self):
172 """Returns the current continuation prompt"""
187 """Returns the current continuation prompt"""
173
188
174 return self.continuation_prompt_template.safe_substitute()
189 return self.continuation_prompt_template.safe_substitute()
175
190
176 def output_prompt(self, result):
191 def output_prompt(self, result):
177 """Returns the output prompt for result"""
192 """Returns the output prompt for result"""
178
193
179 return self.output_prompt_template.safe_substitute(result)
194 return self.output_prompt_template.safe_substitute(result)
180
195
181
196
182 def is_complete(self, block):
197 def is_complete(self, block):
183 """Determine if block is complete.
198 """Determine if block is complete.
184
199
185 Parameters
200 Parameters
186 block : string
201 block : string
187
202
188 Result
203 Result
189 True if block can be sent to the engine without compile errors.
204 True if block can be sent to the engine without compile errors.
190 False otherwise.
205 False otherwise.
191 """
206 """
192
207
193 try:
208 try:
194 ast = self.compile_ast(block)
209 ast = self.compile_ast(block)
195 except:
210 except:
196 return False
211 return False
197
212
198 lines = block.split('\n')
213 lines = block.split('\n')
199 return (len(lines)==1 or str(lines[-1])=='')
214 return (len(lines)==1 or str(lines[-1])=='')
200
215
201
216
202 def compile_ast(self, block):
217 def compile_ast(self, block):
203 """Compile block to an AST
218 """Compile block to an AST
204
219
205 Parameters:
220 Parameters:
206 block : str
221 block : str
207
222
208 Result:
223 Result:
209 AST
224 AST
210
225
211 Throws:
226 Throws:
212 Exception if block cannot be compiled
227 Exception if block cannot be compiled
213 """
228 """
214
229
215 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
230 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
216
231
217
232
218 def execute(self, block, blockID=None):
233 def execute(self, block, blockID=None):
219 """Execute the block and return result.
234 """Execute the block and return result.
220
235
221 Parameters:
236 Parameters:
222 block : {str, AST}
237 block : {str, AST}
223 blockID : any
238 blockID : any
224 Caller may provide an ID to identify this block. result['blockID'] := blockID
239 Caller may provide an ID to identify this block.
240 result['blockID'] := blockID
225
241
226 Result:
242 Result:
227 Deferred result of self.interpreter.execute
243 Deferred result of self.interpreter.execute
228 """
244 """
229
245
230 if(not self.is_complete(block)):
246 if(not self.is_complete(block)):
231 return Failure(Exception("Block is not compilable"))
247 return Failure(Exception("Block is not compilable"))
232
248
233 if(blockID == None):
249 if(blockID == None):
234 blockID = uuid.uuid4() #random UUID
250 blockID = uuid.uuid4() #random UUID
235
251
236 d = self.engine.execute(block)
252 d = self.engine.execute(block)
237 d.addCallback(self._add_history, block=block)
253 d.addCallback(self._add_history, block=block)
238 d.addBoth(self._add_block_id, blockID)
254 d.addBoth(self._add_block_id, blockID)
239 d.addBoth(self.update_cell_prompt)
255 d.addBoth(self.update_cell_prompt)
240 d.addCallbacks(self.render_result, errback=self.render_error)
256 d.addCallbacks(self.render_result, errback=self.render_error)
241
257
242 return d
258 return d
243
259
244
260
245 def _add_block_id(self, result, blockID):
261 def _add_block_id(self, result, blockID):
246 """Add the blockID to result or failure. Unfortunatley, we have to treat failures
262 """Add the blockID to result or failure. Unfortunatley, we have to
247 differently than result dicts
263 treat failures differently than result dicts.
248 """
264 """
249
265
250 if(isinstance(result, Failure)):
266 if(isinstance(result, Failure)):
251 result.blockID = blockID
267 result.blockID = blockID
252 else:
268 else:
253 result['blockID'] = blockID
269 result['blockID'] = blockID
254
270
255 return result
271 return result
256
272
257 def _add_history(self, result, block=None):
273 def _add_history(self, result, block=None):
258 """Add block to the history"""
274 """Add block to the history"""
259
275
260 assert(block != None)
276 assert(block != None)
261 self.history.add_items([block])
277 self.history.add_items([block])
262 self.history_cursor += 1
278 self.history_cursor += 1
263
279
264 return result
280 return result
265
281
266
282
267 def get_history_previous(self, currentBlock):
283 def get_history_previous(self, currentBlock):
268 """ Returns previous history string and decrement history cursor.
284 """ Returns previous history string and decrement history cursor.
269 """
285 """
270 command = self.history.get_history_item(self.history_cursor - 1)
286 command = self.history.get_history_item(self.history_cursor - 1)
271
287
272 if command is not None:
288 if command is not None:
273 if(self.history_cursor == len(self.history.input_cache)):
289 if(self.history_cursor == len(self.history.input_cache)):
274 self.history.input_cache[self.history_cursor] = currentBlock
290 self.history.input_cache[self.history_cursor] = currentBlock
275 self.history_cursor -= 1
291 self.history_cursor -= 1
276 return command
292 return command
277
293
278
294
279 def get_history_next(self):
295 def get_history_next(self):
280 """ Returns next history string and increment history cursor.
296 """ Returns next history string and increment history cursor.
281 """
297 """
282 command = self.history.get_history_item(self.history_cursor+1)
298 command = self.history.get_history_item(self.history_cursor+1)
283
299
284 if command is not None:
300 if command is not None:
285 self.history_cursor += 1
301 self.history_cursor += 1
286 return command
302 return command
287
303
288 ###
304 ###
289 # Subclasses probably want to override these methods...
305 # Subclasses probably want to override these methods...
290 ###
306 ###
291
307
292 def update_cell_prompt(self, result):
308 def update_cell_prompt(self, result):
293 """Subclass may override to update the input prompt for a block.
309 """Subclass may override to update the input prompt for a block.
294 Since this method will be called as a twisted.internet.defer.Deferred's callback,
310 Since this method will be called as a
295 implementations should return result when finished.
311 twisted.internet.defer.Deferred's callback, implementations should
312 return result when finished.
296
313
297 NP: result is a failure if the execute returned a failre. To get the blockID, you should
314 NP: result is a failure if the execute returned a failre.
298 do something like::
315 To get the blockID, you should do something like::
299 if(isinstance(result, twisted.python.failure.Failure)):
316 if(isinstance(result, twisted.python.failure.Failure)):
300 blockID = result.blockID
317 blockID = result.blockID
301 else:
318 else:
302 blockID = result['blockID']
319 blockID = result['blockID']
303
320
304
321
305 """
322 """
306
323
307 return result
324 return result
308
325
309
326
310 def render_result(self, result):
327 def render_result(self, result):
311 """Subclasses must override to render result. Since this method will be called as a
328 """Subclasses must override to render result. Since this method will
312 twisted.internet.defer.Deferred's callback, implementations should return result
329 be called as a twisted.internet.defer.Deferred's callback,
313 when finished."""
330 implementations should return result when finished.
331 """
314
332
315 return result
333 return result
316
334
317
335
318 def render_error(self, failure):
336 def render_error(self, failure):
319 """Subclasses must override to render the failure. Since this method will be called as a
337 """Subclasses must override to render the failure. Since this method
320 twisted.internet.defer.Deferred's callback, implementations should return result
338 will be called as a twisted.internet.defer.Deferred's callback,
321 when finished."""
339 implementations should return result when finished."""
322
340
323 return failure
341 return failure
324
342
325
343
326
344
@@ -1,149 +1,151 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """This file contains unittests for the frontendbase module."""
3 """This file contains unittests for the frontendbase module."""
4
4
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
7 #-------------------------------------------------------------------------------
7 #---------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
8 # Copyright (C) 2008 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
12 #---------------------------------------------------------------------------
13
13
14 #-------------------------------------------------------------------------------
14 #---------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-------------------------------------------------------------------------------
16 #---------------------------------------------------------------------------
17
17
18 import unittest
18 import unittest
19 from IPython.frontend import frontendbase
19 from IPython.frontend import frontendbase
20 from IPython.kernel.engineservice import EngineService
20 from IPython.kernel.engineservice import EngineService
21
21
22 class FrontEndCallbackChecker(frontendbase.FrontEndBase):
22 class FrontEndCallbackChecker(frontendbase.FrontEndBase):
23 """FrontEndBase subclass for checking callbacks"""
23 """FrontEndBase subclass for checking callbacks"""
24 def __init__(self, engine=None, history=None):
24 def __init__(self, engine=None, history=None):
25 super(FrontEndCallbackChecker, self).__init__(engine=engine, history=history)
25 super(FrontEndCallbackChecker, self).__init__(engine=engine,
26 history=history)
26 self.updateCalled = False
27 self.updateCalled = False
27 self.renderResultCalled = False
28 self.renderResultCalled = False
28 self.renderErrorCalled = False
29 self.renderErrorCalled = False
29
30
30 def update_cell_prompt(self, result):
31 def update_cell_prompt(self, result):
31 self.updateCalled = True
32 self.updateCalled = True
32 return result
33 return result
33
34
34 def render_result(self, result):
35 def render_result(self, result):
35 self.renderResultCalled = True
36 self.renderResultCalled = True
36 return result
37 return result
37
38
38
39
39 def render_error(self, failure):
40 def render_error(self, failure):
40 self.renderErrorCalled = True
41 self.renderErrorCalled = True
41 return failure
42 return failure
42
43
43
44
44
45
45
46
46 class TestFrontendBase(unittest.TestCase):
47 class TestFrontendBase(unittest.TestCase):
47 def setUp(self):
48 def setUp(self):
48 """Setup the EngineService and FrontEndBase"""
49 """Setup the EngineService and FrontEndBase"""
49
50
50 self.fb = FrontEndCallbackChecker(engine=EngineService())
51 self.fb = FrontEndCallbackChecker(engine=EngineService())
51
52
52
53
53 def test_implements_IFrontEnd(self):
54 def test_implements_IFrontEnd(self):
54 assert(frontendbase.IFrontEnd.implementedBy(frontendbase.FrontEndBase))
55 assert(frontendbase.IFrontEnd.implementedBy(
56 frontendbase.FrontEndBase))
55
57
56
58
57 def test_is_complete_returns_False_for_incomplete_block(self):
59 def test_is_complete_returns_False_for_incomplete_block(self):
58 """"""
60 """"""
59
61
60 block = """def test(a):"""
62 block = """def test(a):"""
61
63
62 assert(self.fb.is_complete(block) == False)
64 assert(self.fb.is_complete(block) == False)
63
65
64 def test_is_complete_returns_True_for_complete_block(self):
66 def test_is_complete_returns_True_for_complete_block(self):
65 """"""
67 """"""
66
68
67 block = """def test(a): pass"""
69 block = """def test(a): pass"""
68
70
69 assert(self.fb.is_complete(block))
71 assert(self.fb.is_complete(block))
70
72
71 block = """a=3"""
73 block = """a=3"""
72
74
73 assert(self.fb.is_complete(block))
75 assert(self.fb.is_complete(block))
74
76
75
77
76 def test_blockID_added_to_result(self):
78 def test_blockID_added_to_result(self):
77 block = """3+3"""
79 block = """3+3"""
78
80
79 d = self.fb.execute(block, blockID='TEST_ID')
81 d = self.fb.execute(block, blockID='TEST_ID')
80
82
81 d.addCallback(self.checkBlockID, expected='TEST_ID')
83 d.addCallback(self.checkBlockID, expected='TEST_ID')
82
84
83 def test_blockID_added_to_failure(self):
85 def test_blockID_added_to_failure(self):
84 block = "raise Exception()"
86 block = "raise Exception()"
85
87
86 d = self.fb.execute(block,blockID='TEST_ID')
88 d = self.fb.execute(block,blockID='TEST_ID')
87 d.addErrback(self.checkFailureID, expected='TEST_ID')
89 d.addErrback(self.checkFailureID, expected='TEST_ID')
88
90
89 def checkBlockID(self, result, expected=""):
91 def checkBlockID(self, result, expected=""):
90 assert(result['blockID'] == expected)
92 assert(result['blockID'] == expected)
91
93
92
94
93 def checkFailureID(self, failure, expected=""):
95 def checkFailureID(self, failure, expected=""):
94 assert(failure.blockID == expected)
96 assert(failure.blockID == expected)
95
97
96
98
97 def test_callbacks_added_to_execute(self):
99 def test_callbacks_added_to_execute(self):
98 """test that
100 """test that
99 update_cell_prompt
101 update_cell_prompt
100 render_result
102 render_result
101
103
102 are added to execute request
104 are added to execute request
103 """
105 """
104
106
105 d = self.fb.execute("10+10")
107 d = self.fb.execute("10+10")
106 d.addCallback(self.checkCallbacks)
108 d.addCallback(self.checkCallbacks)
107
109
108
110
109 def checkCallbacks(self, result):
111 def checkCallbacks(self, result):
110 assert(self.fb.updateCalled)
112 assert(self.fb.updateCalled)
111 assert(self.fb.renderResultCalled)
113 assert(self.fb.renderResultCalled)
112
114
113
115
114 def test_error_callback_added_to_execute(self):
116 def test_error_callback_added_to_execute(self):
115 """test that render_error called on execution error"""
117 """test that render_error called on execution error"""
116
118
117 d = self.fb.execute("raise Exception()")
119 d = self.fb.execute("raise Exception()")
118 d.addCallback(self.checkRenderError)
120 d.addCallback(self.checkRenderError)
119
121
120 def checkRenderError(self, result):
122 def checkRenderError(self, result):
121 assert(self.fb.renderErrorCalled)
123 assert(self.fb.renderErrorCalled)
122
124
123 def test_history_returns_expected_block(self):
125 def test_history_returns_expected_block(self):
124 """Make sure history browsing doesn't fail"""
126 """Make sure history browsing doesn't fail"""
125
127
126 blocks = ["a=1","a=2","a=3"]
128 blocks = ["a=1","a=2","a=3"]
127 for b in blocks:
129 for b in blocks:
128 d = self.fb.execute(b)
130 d = self.fb.execute(b)
129
131
130 # d is now the deferred for the last executed block
132 # d is now the deferred for the last executed block
131 d.addCallback(self.historyTests, blocks)
133 d.addCallback(self.historyTests, blocks)
132
134
133
135
134 def historyTests(self, result, blocks):
136 def historyTests(self, result, blocks):
135 """historyTests"""
137 """historyTests"""
136
138
137 assert(len(blocks) >= 3)
139 assert(len(blocks) >= 3)
138 assert(self.fb.get_history_previous("") == blocks[-2])
140 assert(self.fb.get_history_previous("") == blocks[-2])
139 assert(self.fb.get_history_previous("") == blocks[-3])
141 assert(self.fb.get_history_previous("") == blocks[-3])
140 assert(self.fb.get_history_next() == blocks[-2])
142 assert(self.fb.get_history_next() == blocks[-2])
141
143
142
144
143 def test_history_returns_none_at_startup(self):
145 def test_history_returns_none_at_startup(self):
144 """test_history_returns_none_at_startup"""
146 """test_history_returns_none_at_startup"""
145
147
146 assert(self.fb.get_history_previous("")==None)
148 assert(self.fb.get_history_previous("")==None)
147 assert(self.fb.get_history_next()==None)
149 assert(self.fb.get_history_next()==None)
148
150
149
151
@@ -1,874 +1,876 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.tests.test_engineservice -*-
2 # -*- test-case-name: IPython.kernel.tests.test_engineservice -*-
3
3
4 """A Twisted Service Representation of the IPython core.
4 """A Twisted Service Representation of the IPython core.
5
5
6 The IPython Core exposed to the network is called the Engine. Its
6 The IPython Core exposed to the network is called the Engine. Its
7 representation in Twisted in the EngineService. Interfaces and adapters
7 representation in Twisted in the EngineService. Interfaces and adapters
8 are used to abstract out the details of the actual network protocol used.
8 are used to abstract out the details of the actual network protocol used.
9 The EngineService is an Engine that knows nothing about the actual protocol
9 The EngineService is an Engine that knows nothing about the actual protocol
10 used.
10 used.
11
11
12 The EngineService is exposed with various network protocols in modules like:
12 The EngineService is exposed with various network protocols in modules like:
13
13
14 enginepb.py
14 enginepb.py
15 enginevanilla.py
15 enginevanilla.py
16
16
17 As of 12/12/06 the classes in this module have been simplified greatly. It was
17 As of 12/12/06 the classes in this module have been simplified greatly. It was
18 felt that we had over-engineered things. To improve the maintainability of the
18 felt that we had over-engineered things. To improve the maintainability of the
19 code we have taken out the ICompleteEngine interface and the completeEngine
19 code we have taken out the ICompleteEngine interface and the completeEngine
20 method that automatically added methods to engines.
20 method that automatically added methods to engines.
21
21
22 """
22 """
23
23
24 __docformat__ = "restructuredtext en"
24 __docformat__ = "restructuredtext en"
25
25
26 #-------------------------------------------------------------------------------
26 #-------------------------------------------------------------------------------
27 # Copyright (C) 2008 The IPython Development Team
27 # Copyright (C) 2008 The IPython Development Team
28 #
28 #
29 # Distributed under the terms of the BSD License. The full license is in
29 # Distributed under the terms of the BSD License. The full license is in
30 # the file COPYING, distributed as part of this software.
30 # the file COPYING, distributed as part of this software.
31 #-------------------------------------------------------------------------------
31 #-------------------------------------------------------------------------------
32
32
33 #-------------------------------------------------------------------------------
33 #-------------------------------------------------------------------------------
34 # Imports
34 # Imports
35 #-------------------------------------------------------------------------------
35 #-------------------------------------------------------------------------------
36
36
37 import os, sys, copy
37 import os, sys, copy
38 import cPickle as pickle
38 import cPickle as pickle
39 from new import instancemethod
39 from new import instancemethod
40
40
41 from twisted.application import service
41 from twisted.application import service
42 from twisted.internet import defer, reactor
42 from twisted.internet import defer, reactor
43 from twisted.python import log, failure, components
43 from twisted.python import log, failure, components
44 import zope.interface as zi
44 import zope.interface as zi
45
45
46 from IPython.kernel.core.interpreter import Interpreter
46 from IPython.kernel.core.interpreter import Interpreter
47 from IPython.kernel import newserialized, error, util
47 from IPython.kernel import newserialized, error, util
48 from IPython.kernel.util import printer
48 from IPython.kernel.util import printer
49 from IPython.kernel.twistedutil import gatherBoth, DeferredList
49 from IPython.kernel.twistedutil import gatherBoth, DeferredList
50 from IPython.kernel import codeutil
50 from IPython.kernel import codeutil
51
51
52
52
53 #-------------------------------------------------------------------------------
53 #-------------------------------------------------------------------------------
54 # Interface specification for the Engine
54 # Interface specification for the Engine
55 #-------------------------------------------------------------------------------
55 #-------------------------------------------------------------------------------
56
56
57 class IEngineCore(zi.Interface):
57 class IEngineCore(zi.Interface):
58 """The minimal required interface for the IPython Engine.
58 """The minimal required interface for the IPython Engine.
59
59
60 This interface provides a formal specification of the IPython core.
60 This interface provides a formal specification of the IPython core.
61 All these methods should return deferreds regardless of what side of a
61 All these methods should return deferreds regardless of what side of a
62 network connection they are on.
62 network connection they are on.
63
63
64 In general, this class simply wraps a shell class and wraps its return
64 In general, this class simply wraps a shell class and wraps its return
65 values as Deferred objects. If the underlying shell class method raises
65 values as Deferred objects. If the underlying shell class method raises
66 an exception, this class should convert it to a twisted.failure.Failure
66 an exception, this class should convert it to a twisted.failure.Failure
67 that will be propagated along the Deferred's errback chain.
67 that will be propagated along the Deferred's errback chain.
68
68
69 In addition, Failures are aggressive. By this, we mean that if a method
69 In addition, Failures are aggressive. By this, we mean that if a method
70 is performing multiple actions (like pulling multiple object) if any
70 is performing multiple actions (like pulling multiple object) if any
71 single one fails, the entire method will fail with that Failure. It is
71 single one fails, the entire method will fail with that Failure. It is
72 all or nothing.
72 all or nothing.
73 """
73 """
74
74
75 id = zi.interface.Attribute("the id of the Engine object")
75 id = zi.interface.Attribute("the id of the Engine object")
76 properties = zi.interface.Attribute("A dict of properties of the Engine")
76 properties = zi.interface.Attribute("A dict of properties of the Engine")
77
77
78 def execute(lines):
78 def execute(lines):
79 """Execute lines of Python code.
79 """Execute lines of Python code.
80
80
81 Returns a dictionary with keys (id, number, stdin, stdout, stderr)
81 Returns a dictionary with keys (id, number, stdin, stdout, stderr)
82 upon success.
82 upon success.
83
83
84 Returns a failure object if the execution of lines raises an exception.
84 Returns a failure object if the execution of lines raises an exception.
85 """
85 """
86
86
87 def push(namespace):
87 def push(namespace):
88 """Push dict namespace into the user's namespace.
88 """Push dict namespace into the user's namespace.
89
89
90 Returns a deferred to None or a failure.
90 Returns a deferred to None or a failure.
91 """
91 """
92
92
93 def pull(keys):
93 def pull(keys):
94 """Pulls values out of the user's namespace by keys.
94 """Pulls values out of the user's namespace by keys.
95
95
96 Returns a deferred to a tuple objects or a single object.
96 Returns a deferred to a tuple objects or a single object.
97
97
98 Raises NameError if any one of objects doess not exist.
98 Raises NameError if any one of objects doess not exist.
99 """
99 """
100
100
101 def push_function(namespace):
101 def push_function(namespace):
102 """Push a dict of key, function pairs into the user's namespace.
102 """Push a dict of key, function pairs into the user's namespace.
103
103
104 Returns a deferred to None or a failure."""
104 Returns a deferred to None or a failure."""
105
105
106 def pull_function(keys):
106 def pull_function(keys):
107 """Pulls functions out of the user's namespace by keys.
107 """Pulls functions out of the user's namespace by keys.
108
108
109 Returns a deferred to a tuple of functions or a single function.
109 Returns a deferred to a tuple of functions or a single function.
110
110
111 Raises NameError if any one of the functions does not exist.
111 Raises NameError if any one of the functions does not exist.
112 """
112 """
113
113
114 def get_result(i=None):
114 def get_result(i=None):
115 """Get the stdin/stdout/stderr of command i.
115 """Get the stdin/stdout/stderr of command i.
116
116
117 Returns a deferred to a dict with keys
117 Returns a deferred to a dict with keys
118 (id, number, stdin, stdout, stderr).
118 (id, number, stdin, stdout, stderr).
119
119
120 Raises IndexError if command i does not exist.
120 Raises IndexError if command i does not exist.
121 Raises TypeError if i in not an int.
121 Raises TypeError if i in not an int.
122 """
122 """
123
123
124 def reset():
124 def reset():
125 """Reset the shell.
125 """Reset the shell.
126
126
127 This clears the users namespace. Won't cause modules to be
127 This clears the users namespace. Won't cause modules to be
128 reloaded. Should also re-initialize certain variables like id.
128 reloaded. Should also re-initialize certain variables like id.
129 """
129 """
130
130
131 def kill():
131 def kill():
132 """Kill the engine by stopping the reactor."""
132 """Kill the engine by stopping the reactor."""
133
133
134 def keys():
134 def keys():
135 """Return the top level variables in the users namspace.
135 """Return the top level variables in the users namspace.
136
136
137 Returns a deferred to a dict."""
137 Returns a deferred to a dict."""
138
138
139
139
140 class IEngineSerialized(zi.Interface):
140 class IEngineSerialized(zi.Interface):
141 """Push/Pull methods that take Serialized objects.
141 """Push/Pull methods that take Serialized objects.
142
142
143 All methods should return deferreds.
143 All methods should return deferreds.
144 """
144 """
145
145
146 def push_serialized(namespace):
146 def push_serialized(namespace):
147 """Push a dict of keys and Serialized objects into the user's namespace."""
147 """Push a dict of keys and Serialized objects into the user's namespace."""
148
148
149 def pull_serialized(keys):
149 def pull_serialized(keys):
150 """Pull objects by key from the user's namespace as Serialized.
150 """Pull objects by key from the user's namespace as Serialized.
151
151
152 Returns a list of or one Serialized.
152 Returns a list of or one Serialized.
153
153
154 Raises NameError is any one of the objects does not exist.
154 Raises NameError is any one of the objects does not exist.
155 """
155 """
156
156
157
157
158 class IEngineProperties(zi.Interface):
158 class IEngineProperties(zi.Interface):
159 """Methods for access to the properties object of an Engine"""
159 """Methods for access to the properties object of an Engine"""
160
160
161 properties = zi.Attribute("A StrictDict object, containing the properties")
161 properties = zi.Attribute("A StrictDict object, containing the properties")
162
162
163 def set_properties(properties):
163 def set_properties(properties):
164 """set properties by key and value"""
164 """set properties by key and value"""
165
165
166 def get_properties(keys=None):
166 def get_properties(keys=None):
167 """get a list of properties by `keys`, if no keys specified, get all"""
167 """get a list of properties by `keys`, if no keys specified, get all"""
168
168
169 def del_properties(keys):
169 def del_properties(keys):
170 """delete properties by `keys`"""
170 """delete properties by `keys`"""
171
171
172 def has_properties(keys):
172 def has_properties(keys):
173 """get a list of bool values for whether `properties` has `keys`"""
173 """get a list of bool values for whether `properties` has `keys`"""
174
174
175 def clear_properties():
175 def clear_properties():
176 """clear the properties dict"""
176 """clear the properties dict"""
177
177
178 class IEngineBase(IEngineCore, IEngineSerialized, IEngineProperties):
178 class IEngineBase(IEngineCore, IEngineSerialized, IEngineProperties):
179 """The basic engine interface that EngineService will implement.
179 """The basic engine interface that EngineService will implement.
180
180
181 This exists so it is easy to specify adapters that adapt to and from the
181 This exists so it is easy to specify adapters that adapt to and from the
182 API that the basic EngineService implements.
182 API that the basic EngineService implements.
183 """
183 """
184 pass
184 pass
185
185
186 class IEngineQueued(IEngineBase):
186 class IEngineQueued(IEngineBase):
187 """Interface for adding a queue to an IEngineBase.
187 """Interface for adding a queue to an IEngineBase.
188
188
189 This interface extends the IEngineBase interface to add methods for managing
189 This interface extends the IEngineBase interface to add methods for managing
190 the engine's queue. The implicit details of this interface are that the
190 the engine's queue. The implicit details of this interface are that the
191 execution of all methods declared in IEngineBase should appropriately be
191 execution of all methods declared in IEngineBase should appropriately be
192 put through a queue before execution.
192 put through a queue before execution.
193
193
194 All methods should return deferreds.
194 All methods should return deferreds.
195 """
195 """
196
196
197 def clear_queue():
197 def clear_queue():
198 """Clear the queue."""
198 """Clear the queue."""
199
199
200 def queue_status():
200 def queue_status():
201 """Get the queued and pending commands in the queue."""
201 """Get the queued and pending commands in the queue."""
202
202
203 def register_failure_observer(obs):
203 def register_failure_observer(obs):
204 """Register an observer of pending Failures.
204 """Register an observer of pending Failures.
205
205
206 The observer must implement IFailureObserver.
206 The observer must implement IFailureObserver.
207 """
207 """
208
208
209 def unregister_failure_observer(obs):
209 def unregister_failure_observer(obs):
210 """Unregister an observer of pending Failures."""
210 """Unregister an observer of pending Failures."""
211
211
212
212
213 class IEngineThreaded(zi.Interface):
213 class IEngineThreaded(zi.Interface):
214 """A place holder for threaded commands.
214 """A place holder for threaded commands.
215
215
216 All methods should return deferreds.
216 All methods should return deferreds.
217 """
217 """
218 pass
218 pass
219
219
220
220
221 #-------------------------------------------------------------------------------
221 #-------------------------------------------------------------------------------
222 # Functions and classes to implement the EngineService
222 # Functions and classes to implement the EngineService
223 #-------------------------------------------------------------------------------
223 #-------------------------------------------------------------------------------
224
224
225
225
226 class StrictDict(dict):
226 class StrictDict(dict):
227 """This is a strict copying dictionary for use as the interface to the
227 """This is a strict copying dictionary for use as the interface to the
228 properties of an Engine.
228 properties of an Engine.
229 :IMPORTANT:
229 :IMPORTANT:
230 This object copies the values you set to it, and returns copies to you
230 This object copies the values you set to it, and returns copies to you
231 when you request them. The only way to change properties os explicitly
231 when you request them. The only way to change properties os explicitly
232 through the setitem and getitem of the dictionary interface.
232 through the setitem and getitem of the dictionary interface.
233 Example:
233 Example:
234 >>> e = kernel.get_engine(id)
234 >>> e = kernel.get_engine(id)
235 >>> L = someList
235 >>> L = someList
236 >>> e.properties['L'] = L
236 >>> e.properties['L'] = L
237 >>> L == e.properties['L']
237 >>> L == e.properties['L']
238 ... True
238 ... True
239 >>> L.append(something Else)
239 >>> L.append(something Else)
240 >>> L == e.properties['L']
240 >>> L == e.properties['L']
241 ... False
241 ... False
242
242
243 getitem copies, so calls to methods of objects do not affect the
243 getitem copies, so calls to methods of objects do not affect the
244 properties, as in the following example:
244 properties, as in the following example:
245 >>> e.properties[1] = range(2)
245 >>> e.properties[1] = range(2)
246 >>> print e.properties[1]
246 >>> print e.properties[1]
247 ... [0, 1]
247 ... [0, 1]
248 >>> e.properties[1].append(2)
248 >>> e.properties[1].append(2)
249 >>> print e.properties[1]
249 >>> print e.properties[1]
250 ... [0, 1]
250 ... [0, 1]
251
251
252 """
252 """
253 def __init__(self, *args, **kwargs):
253 def __init__(self, *args, **kwargs):
254 dict.__init__(self, *args, **kwargs)
254 dict.__init__(self, *args, **kwargs)
255 self.modified = True
255 self.modified = True
256
256
257 def __getitem__(self, key):
257 def __getitem__(self, key):
258 return copy.deepcopy(dict.__getitem__(self, key))
258 return copy.deepcopy(dict.__getitem__(self, key))
259
259
260 def __setitem__(self, key, value):
260 def __setitem__(self, key, value):
261 # check if this entry is valid for transport around the network
261 # check if this entry is valid for transport around the network
262 # and copying
262 # and copying
263 try:
263 try:
264 pickle.dumps(key, 2)
264 pickle.dumps(key, 2)
265 pickle.dumps(value, 2)
265 pickle.dumps(value, 2)
266 newvalue = copy.deepcopy(value)
266 newvalue = copy.deepcopy(value)
267 except:
267 except:
268 raise error.InvalidProperty(value)
268 raise error.InvalidProperty(value)
269 dict.__setitem__(self, key, newvalue)
269 dict.__setitem__(self, key, newvalue)
270 self.modified = True
270 self.modified = True
271
271
272 def __delitem__(self, key):
272 def __delitem__(self, key):
273 dict.__delitem__(self, key)
273 dict.__delitem__(self, key)
274 self.modified = True
274 self.modified = True
275
275
276 def update(self, dikt):
276 def update(self, dikt):
277 for k,v in dikt.iteritems():
277 for k,v in dikt.iteritems():
278 self[k] = v
278 self[k] = v
279
279
280 def pop(self, key):
280 def pop(self, key):
281 self.modified = True
281 self.modified = True
282 return dict.pop(self, key)
282 return dict.pop(self, key)
283
283
284 def popitem(self):
284 def popitem(self):
285 self.modified = True
285 self.modified = True
286 return dict.popitem(self)
286 return dict.popitem(self)
287
287
288 def clear(self):
288 def clear(self):
289 self.modified = True
289 self.modified = True
290 dict.clear(self)
290 dict.clear(self)
291
291
292 def subDict(self, *keys):
292 def subDict(self, *keys):
293 d = {}
293 d = {}
294 for key in keys:
294 for key in keys:
295 d[key] = self[key]
295 d[key] = self[key]
296 return d
296 return d
297
297
298
298
299
299
300 class EngineAPI(object):
300 class EngineAPI(object):
301 """This is the object through which the user can edit the `properties`
301 """This is the object through which the user can edit the `properties`
302 attribute of an Engine.
302 attribute of an Engine.
303 The Engine Properties object copies all object in and out of itself.
303 The Engine Properties object copies all object in and out of itself.
304 See the EngineProperties object for details.
304 See the EngineProperties object for details.
305 """
305 """
306 _fix=False
306 _fix=False
307 def __init__(self, id):
307 def __init__(self, id):
308 self.id = id
308 self.id = id
309 self.properties = StrictDict()
309 self.properties = StrictDict()
310 self._fix=True
310 self._fix=True
311
311
312 def __setattr__(self, k,v):
312 def __setattr__(self, k,v):
313 if self._fix:
313 if self._fix:
314 raise error.KernelError("I am protected!")
314 raise error.KernelError("I am protected!")
315 else:
315 else:
316 object.__setattr__(self, k, v)
316 object.__setattr__(self, k, v)
317
317
318 def __delattr__(self, key):
318 def __delattr__(self, key):
319 raise error.KernelError("I am protected!")
319 raise error.KernelError("I am protected!")
320
320
321
321
322 _apiDict = {}
322 _apiDict = {}
323
323
324 def get_engine(id):
324 def get_engine(id):
325 """Get the Engine API object, whcih currently just provides the properties
325 """Get the Engine API object, whcih currently just provides the properties
326 object, by ID"""
326 object, by ID"""
327 global _apiDict
327 global _apiDict
328 if not _apiDict.get(id):
328 if not _apiDict.get(id):
329 _apiDict[id] = EngineAPI(id)
329 _apiDict[id] = EngineAPI(id)
330 return _apiDict[id]
330 return _apiDict[id]
331
331
332 def drop_engine(id):
332 def drop_engine(id):
333 """remove an engine"""
333 """remove an engine"""
334 global _apiDict
334 global _apiDict
335 if _apiDict.has_key(id):
335 if _apiDict.has_key(id):
336 del _apiDict[id]
336 del _apiDict[id]
337
337
338 class EngineService(object, service.Service):
338 class EngineService(object, service.Service):
339 """Adapt a IPython shell into a IEngine implementing Twisted Service."""
339 """Adapt a IPython shell into a IEngine implementing Twisted Service."""
340
340
341 zi.implements(IEngineBase)
341 zi.implements(IEngineBase)
342 name = 'EngineService'
342 name = 'EngineService'
343
343
344 def __init__(self, shellClass=Interpreter, mpi=None):
344 def __init__(self, shellClass=Interpreter, mpi=None):
345 """Create an EngineService.
345 """Create an EngineService.
346
346
347 shellClass: something that implements IInterpreter or core1
347 shellClass: something that implements IInterpreter or core1
348 mpi: an mpi module that has rank and size attributes
348 mpi: an mpi module that has rank and size attributes
349 """
349 """
350 self.shellClass = shellClass
350 self.shellClass = shellClass
351 self.shell = self.shellClass()
351 self.shell = self.shellClass()
352 self.mpi = mpi
352 self.mpi = mpi
353 self.id = None
353 self.id = None
354 self.properties = get_engine(self.id).properties
354 self.properties = get_engine(self.id).properties
355 if self.mpi is not None:
355 if self.mpi is not None:
356 log.msg("MPI started with rank = %i and size = %i" %
356 log.msg("MPI started with rank = %i and size = %i" %
357 (self.mpi.rank, self.mpi.size))
357 (self.mpi.rank, self.mpi.size))
358 self.id = self.mpi.rank
358 self.id = self.mpi.rank
359 self._seedNamespace()
359 self._seedNamespace()
360
360
361 # Make id a property so that the shell can get the updated id
361 # Make id a property so that the shell can get the updated id
362
362
363 def _setID(self, id):
363 def _setID(self, id):
364 self._id = id
364 self._id = id
365 self.properties = get_engine(id).properties
365 self.properties = get_engine(id).properties
366 self.shell.push({'id': id})
366 self.shell.push({'id': id})
367
367
368 def _getID(self):
368 def _getID(self):
369 return self._id
369 return self._id
370
370
371 id = property(_getID, _setID)
371 id = property(_getID, _setID)
372
372
373 def _seedNamespace(self):
373 def _seedNamespace(self):
374 self.shell.push({'mpi': self.mpi, 'id' : self.id})
374 self.shell.push({'mpi': self.mpi, 'id' : self.id})
375
375
376 def executeAndRaise(self, msg, callable, *args, **kwargs):
376 def executeAndRaise(self, msg, callable, *args, **kwargs):
377 """Call a method of self.shell and wrap any exception."""
377 """Call a method of self.shell and wrap any exception."""
378 d = defer.Deferred()
378 d = defer.Deferred()
379 try:
379 try:
380 result = callable(*args, **kwargs)
380 result = callable(*args, **kwargs)
381 except:
381 except:
382 # This gives the following:
382 # This gives the following:
383 # et=exception class
383 # et=exception class
384 # ev=exception class instance
384 # ev=exception class instance
385 # tb=traceback object
385 # tb=traceback object
386 et,ev,tb = sys.exc_info()
386 et,ev,tb = sys.exc_info()
387 # This call adds attributes to the exception value
387 # This call adds attributes to the exception value
388 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
388 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
389 # Add another attribute
389 # Add another attribute
390 ev._ipython_engine_info = msg
390 ev._ipython_engine_info = msg
391 f = failure.Failure(ev,et,None)
391 f = failure.Failure(ev,et,None)
392 d.errback(f)
392 d.errback(f)
393 else:
393 else:
394 d.callback(result)
394 d.callback(result)
395
395
396 return d
396 return d
397
397
398 # The IEngine methods. See the interface for documentation.
398 # The IEngine methods. See the interface for documentation.
399
399
400 def execute(self, lines):
400 def execute(self, lines):
401 msg = {'engineid':self.id,
401 msg = {'engineid':self.id,
402 'method':'execute',
402 'method':'execute',
403 'args':[lines]}
403 'args':[lines]}
404 d = self.executeAndRaise(msg, self.shell.execute, lines)
404 d = self.executeAndRaise(msg, self.shell.execute, lines)
405 d.addCallback(self.addIDToResult)
405 d.addCallback(self.addIDToResult)
406 return d
406 return d
407
407
408 def addIDToResult(self, result):
408 def addIDToResult(self, result):
409 result['id'] = self.id
409 result['id'] = self.id
410 return result
410 return result
411
411
412 def push(self, namespace):
412 def push(self, namespace):
413 msg = {'engineid':self.id,
413 msg = {'engineid':self.id,
414 'method':'push',
414 'method':'push',
415 'args':[repr(namespace.keys())]}
415 'args':[repr(namespace.keys())]}
416 d = self.executeAndRaise(msg, self.shell.push, namespace)
416 d = self.executeAndRaise(msg, self.shell.push, namespace)
417 return d
417 return d
418
418
419 def pull(self, keys):
419 def pull(self, keys):
420 msg = {'engineid':self.id,
420 msg = {'engineid':self.id,
421 'method':'pull',
421 'method':'pull',
422 'args':[repr(keys)]}
422 'args':[repr(keys)]}
423 d = self.executeAndRaise(msg, self.shell.pull, keys)
423 d = self.executeAndRaise(msg, self.shell.pull, keys)
424 return d
424 return d
425
425
426 def push_function(self, namespace):
426 def push_function(self, namespace):
427 msg = {'engineid':self.id,
427 msg = {'engineid':self.id,
428 'method':'push_function',
428 'method':'push_function',
429 'args':[repr(namespace.keys())]}
429 'args':[repr(namespace.keys())]}
430 d = self.executeAndRaise(msg, self.shell.push_function, namespace)
430 d = self.executeAndRaise(msg, self.shell.push_function, namespace)
431 return d
431 return d
432
432
433 def pull_function(self, keys):
433 def pull_function(self, keys):
434 msg = {'engineid':self.id,
434 msg = {'engineid':self.id,
435 'method':'pull_function',
435 'method':'pull_function',
436 'args':[repr(keys)]}
436 'args':[repr(keys)]}
437 d = self.executeAndRaise(msg, self.shell.pull_function, keys)
437 d = self.executeAndRaise(msg, self.shell.pull_function, keys)
438 return d
438 return d
439
439
440 def get_result(self, i=None):
440 def get_result(self, i=None):
441 msg = {'engineid':self.id,
441 msg = {'engineid':self.id,
442 'method':'get_result',
442 'method':'get_result',
443 'args':[repr(i)]}
443 'args':[repr(i)]}
444 d = self.executeAndRaise(msg, self.shell.getCommand, i)
444 d = self.executeAndRaise(msg, self.shell.getCommand, i)
445 d.addCallback(self.addIDToResult)
445 d.addCallback(self.addIDToResult)
446 return d
446 return d
447
447
448 def reset(self):
448 def reset(self):
449 msg = {'engineid':self.id,
449 msg = {'engineid':self.id,
450 'method':'reset',
450 'method':'reset',
451 'args':[]}
451 'args':[]}
452 del self.shell
452 del self.shell
453 self.shell = self.shellClass()
453 self.shell = self.shellClass()
454 self.properties.clear()
454 self.properties.clear()
455 d = self.executeAndRaise(msg, self._seedNamespace)
455 d = self.executeAndRaise(msg, self._seedNamespace)
456 return d
456 return d
457
457
458 def kill(self):
458 def kill(self):
459 drop_engine(self.id)
459 drop_engine(self.id)
460 try:
460 try:
461 reactor.stop()
461 reactor.stop()
462 except RuntimeError:
462 except RuntimeError:
463 log.msg('The reactor was not running apparently.')
463 log.msg('The reactor was not running apparently.')
464 return defer.fail()
464 return defer.fail()
465 else:
465 else:
466 return defer.succeed(None)
466 return defer.succeed(None)
467
467
468 def keys(self):
468 def keys(self):
469 """Return a list of variables names in the users top level namespace.
469 """Return a list of variables names in the users top level namespace.
470
470
471 This used to return a dict of all the keys/repr(values) in the
471 This used to return a dict of all the keys/repr(values) in the
472 user's namespace. This was too much info for the ControllerService
472 user's namespace. This was too much info for the ControllerService
473 to handle so it is now just a list of keys.
473 to handle so it is now just a list of keys.
474 """
474 """
475
475
476 remotes = []
476 remotes = []
477 for k in self.shell.user_ns.iterkeys():
477 for k in self.shell.user_ns.iterkeys():
478 if k not in ['__name__', '_ih', '_oh', '__builtins__',
478 if k not in ['__name__', '_ih', '_oh', '__builtins__',
479 'In', 'Out', '_', '__', '___', '__IP', 'input', 'raw_input']:
479 'In', 'Out', '_', '__', '___', '__IP', 'input', 'raw_input']:
480 remotes.append(k)
480 remotes.append(k)
481 return defer.succeed(remotes)
481 return defer.succeed(remotes)
482
482
483 def set_properties(self, properties):
483 def set_properties(self, properties):
484 msg = {'engineid':self.id,
484 msg = {'engineid':self.id,
485 'method':'set_properties',
485 'method':'set_properties',
486 'args':[repr(properties.keys())]}
486 'args':[repr(properties.keys())]}
487 return self.executeAndRaise(msg, self.properties.update, properties)
487 return self.executeAndRaise(msg, self.properties.update, properties)
488
488
489 def get_properties(self, keys=None):
489 def get_properties(self, keys=None):
490 msg = {'engineid':self.id,
490 msg = {'engineid':self.id,
491 'method':'get_properties',
491 'method':'get_properties',
492 'args':[repr(keys)]}
492 'args':[repr(keys)]}
493 if keys is None:
493 if keys is None:
494 keys = self.properties.keys()
494 keys = self.properties.keys()
495 return self.executeAndRaise(msg, self.properties.subDict, *keys)
495 return self.executeAndRaise(msg, self.properties.subDict, *keys)
496
496
497 def _doDel(self, keys):
497 def _doDel(self, keys):
498 for key in keys:
498 for key in keys:
499 del self.properties[key]
499 del self.properties[key]
500
500
501 def del_properties(self, keys):
501 def del_properties(self, keys):
502 msg = {'engineid':self.id,
502 msg = {'engineid':self.id,
503 'method':'del_properties',
503 'method':'del_properties',
504 'args':[repr(keys)]}
504 'args':[repr(keys)]}
505 return self.executeAndRaise(msg, self._doDel, keys)
505 return self.executeAndRaise(msg, self._doDel, keys)
506
506
507 def _doHas(self, keys):
507 def _doHas(self, keys):
508 return [self.properties.has_key(key) for key in keys]
508 return [self.properties.has_key(key) for key in keys]
509
509
510 def has_properties(self, keys):
510 def has_properties(self, keys):
511 msg = {'engineid':self.id,
511 msg = {'engineid':self.id,
512 'method':'has_properties',
512 'method':'has_properties',
513 'args':[repr(keys)]}
513 'args':[repr(keys)]}
514 return self.executeAndRaise(msg, self._doHas, keys)
514 return self.executeAndRaise(msg, self._doHas, keys)
515
515
516 def clear_properties(self):
516 def clear_properties(self):
517 msg = {'engineid':self.id,
517 msg = {'engineid':self.id,
518 'method':'clear_properties',
518 'method':'clear_properties',
519 'args':[]}
519 'args':[]}
520 return self.executeAndRaise(msg, self.properties.clear)
520 return self.executeAndRaise(msg, self.properties.clear)
521
521
522 def push_serialized(self, sNamespace):
522 def push_serialized(self, sNamespace):
523 msg = {'engineid':self.id,
523 msg = {'engineid':self.id,
524 'method':'push_serialized',
524 'method':'push_serialized',
525 'args':[repr(sNamespace.keys())]}
525 'args':[repr(sNamespace.keys())]}
526 ns = {}
526 ns = {}
527 for k,v in sNamespace.iteritems():
527 for k,v in sNamespace.iteritems():
528 try:
528 try:
529 unserialized = newserialized.IUnSerialized(v)
529 unserialized = newserialized.IUnSerialized(v)
530 ns[k] = unserialized.getObject()
530 ns[k] = unserialized.getObject()
531 except:
531 except:
532 return defer.fail()
532 return defer.fail()
533 return self.executeAndRaise(msg, self.shell.push, ns)
533 return self.executeAndRaise(msg, self.shell.push, ns)
534
534
535 def pull_serialized(self, keys):
535 def pull_serialized(self, keys):
536 msg = {'engineid':self.id,
536 msg = {'engineid':self.id,
537 'method':'pull_serialized',
537 'method':'pull_serialized',
538 'args':[repr(keys)]}
538 'args':[repr(keys)]}
539 if isinstance(keys, str):
539 if isinstance(keys, str):
540 keys = [keys]
540 keys = [keys]
541 if len(keys)==1:
541 if len(keys)==1:
542 d = self.executeAndRaise(msg, self.shell.pull, keys)
542 d = self.executeAndRaise(msg, self.shell.pull, keys)
543 d.addCallback(newserialized.serialize)
543 d.addCallback(newserialized.serialize)
544 return d
544 return d
545 elif len(keys)>1:
545 elif len(keys)>1:
546 d = self.executeAndRaise(msg, self.shell.pull, keys)
546 d = self.executeAndRaise(msg, self.shell.pull, keys)
547 @d.addCallback
547 @d.addCallback
548 def packThemUp(values):
548 def packThemUp(values):
549 serials = []
549 serials = []
550 for v in values:
550 for v in values:
551 try:
551 try:
552 serials.append(newserialized.serialize(v))
552 serials.append(newserialized.serialize(v))
553 except:
553 except:
554 return defer.fail(failure.Failure())
554 return defer.fail(failure.Failure())
555 return serials
555 return serials
556 return packThemUp
556 return packThemUp
557
557
558
558
559 def queue(methodToQueue):
559 def queue(methodToQueue):
560 def queuedMethod(this, *args, **kwargs):
560 def queuedMethod(this, *args, **kwargs):
561 name = methodToQueue.__name__
561 name = methodToQueue.__name__
562 return this.submitCommand(Command(name, *args, **kwargs))
562 return this.submitCommand(Command(name, *args, **kwargs))
563 return queuedMethod
563 return queuedMethod
564
564
565 class QueuedEngine(object):
565 class QueuedEngine(object):
566 """Adapt an IEngineBase to an IEngineQueued by wrapping it.
566 """Adapt an IEngineBase to an IEngineQueued by wrapping it.
567
567
568 The resulting object will implement IEngineQueued which extends
568 The resulting object will implement IEngineQueued which extends
569 IEngineCore which extends (IEngineBase, IEngineSerialized).
569 IEngineCore which extends (IEngineBase, IEngineSerialized).
570
570
571 This seems like the best way of handling it, but I am not sure. The
571 This seems like the best way of handling it, but I am not sure. The
572 other option is to have the various base interfaces be used like
572 other option is to have the various base interfaces be used like
573 mix-in intefaces. The problem I have with this is adpatation is
573 mix-in intefaces. The problem I have with this is adpatation is
574 more difficult and complicated because there can be can multiple
574 more difficult and complicated because there can be can multiple
575 original and final Interfaces.
575 original and final Interfaces.
576 """
576 """
577
577
578 zi.implements(IEngineQueued)
578 zi.implements(IEngineQueued)
579
579
580 def __init__(self, engine):
580 def __init__(self, engine):
581 """Create a QueuedEngine object from an engine
581 """Create a QueuedEngine object from an engine
582
582
583 engine: An implementor of IEngineCore and IEngineSerialized
583 engine: An implementor of IEngineCore and IEngineSerialized
584 keepUpToDate: whether to update the remote status when the
584 keepUpToDate: whether to update the remote status when the
585 queue is empty. Defaults to False.
585 queue is empty. Defaults to False.
586 """
586 """
587
587
588 # This is the right way to do these tests rather than
588 # This is the right way to do these tests rather than
589 # IEngineCore in list(zi.providedBy(engine)) which will only
589 # IEngineCore in list(zi.providedBy(engine)) which will only
590 # picks of the interfaces that are directly declared by engine.
590 # picks of the interfaces that are directly declared by engine.
591 assert IEngineBase.providedBy(engine), \
591 assert IEngineBase.providedBy(engine), \
592 "engine passed to QueuedEngine doesn't provide IEngineBase"
592 "engine passed to QueuedEngine doesn't provide IEngineBase"
593
593
594 self.engine = engine
594 self.engine = engine
595 self.id = engine.id
595 self.id = engine.id
596 self.queued = []
596 self.queued = []
597 self.history = {}
597 self.history = {}
598 self.engineStatus = {}
598 self.engineStatus = {}
599 self.currentCommand = None
599 self.currentCommand = None
600 self.failureObservers = []
600 self.failureObservers = []
601
601
602 def _get_properties(self):
602 def _get_properties(self):
603 return self.engine.properties
603 return self.engine.properties
604
604
605 properties = property(_get_properties, lambda self, _: None)
605 properties = property(_get_properties, lambda self, _: None)
606 # Queue management methods. You should not call these directly
606 # Queue management methods. You should not call these directly
607
607
608 def submitCommand(self, cmd):
608 def submitCommand(self, cmd):
609 """Submit command to queue."""
609 """Submit command to queue."""
610
610
611 d = defer.Deferred()
611 d = defer.Deferred()
612 cmd.setDeferred(d)
612 cmd.setDeferred(d)
613 if self.currentCommand is not None:
613 if self.currentCommand is not None:
614 if self.currentCommand.finished:
614 if self.currentCommand.finished:
615 # log.msg("Running command immediately: %r" % cmd)
615 # log.msg("Running command immediately: %r" % cmd)
616 self.currentCommand = cmd
616 self.currentCommand = cmd
617 self.runCurrentCommand()
617 self.runCurrentCommand()
618 else: # command is still running
618 else: # command is still running
619 # log.msg("Command is running: %r" % self.currentCommand)
619 # log.msg("Command is running: %r" % self.currentCommand)
620 # log.msg("Queueing: %r" % cmd)
620 # log.msg("Queueing: %r" % cmd)
621 self.queued.append(cmd)
621 self.queued.append(cmd)
622 else:
622 else:
623 # log.msg("No current commands, running: %r" % cmd)
623 # log.msg("No current commands, running: %r" % cmd)
624 self.currentCommand = cmd
624 self.currentCommand = cmd
625 self.runCurrentCommand()
625 self.runCurrentCommand()
626 return d
626 return d
627
627
628 def runCurrentCommand(self):
628 def runCurrentCommand(self):
629 """Run current command."""
629 """Run current command."""
630
630
631 cmd = self.currentCommand
631 cmd = self.currentCommand
632 f = getattr(self.engine, cmd.remoteMethod, None)
632 f = getattr(self.engine, cmd.remoteMethod, None)
633 if f:
633 if f:
634 d = f(*cmd.args, **cmd.kwargs)
634 d = f(*cmd.args, **cmd.kwargs)
635 if cmd.remoteMethod is 'execute':
635 if cmd.remoteMethod is 'execute':
636 d.addCallback(self.saveResult)
636 d.addCallback(self.saveResult)
637 d.addCallback(self.finishCommand)
637 d.addCallback(self.finishCommand)
638 d.addErrback(self.abortCommand)
638 d.addErrback(self.abortCommand)
639 else:
639 else:
640 return defer.fail(AttributeError(cmd.remoteMethod))
640 return defer.fail(AttributeError(cmd.remoteMethod))
641
641
642 def _flushQueue(self):
642 def _flushQueue(self):
643 """Pop next command in queue and run it."""
643 """Pop next command in queue and run it."""
644
644
645 if len(self.queued) > 0:
645 if len(self.queued) > 0:
646 self.currentCommand = self.queued.pop(0)
646 self.currentCommand = self.queued.pop(0)
647 self.runCurrentCommand()
647 self.runCurrentCommand()
648
648
649 def saveResult(self, result):
649 def saveResult(self, result):
650 """Put the result in the history."""
650 """Put the result in the history."""
651 self.history[result['number']] = result
651 self.history[result['number']] = result
652 return result
652 return result
653
653
654 def finishCommand(self, result):
654 def finishCommand(self, result):
655 """Finish currrent command."""
655 """Finish currrent command."""
656
656
657 # The order of these commands is absolutely critical.
657 # The order of these commands is absolutely critical.
658 self.currentCommand.handleResult(result)
658 self.currentCommand.handleResult(result)
659 self.currentCommand.finished = True
659 self.currentCommand.finished = True
660 self._flushQueue()
660 self._flushQueue()
661 return result
661 return result
662
662
663 def abortCommand(self, reason):
663 def abortCommand(self, reason):
664 """Abort current command.
664 """Abort current command.
665
665
666 This eats the Failure but first passes it onto the Deferred that the
666 This eats the Failure but first passes it onto the Deferred that the
667 user has.
667 user has.
668
668
669 It also clear out the queue so subsequence commands don't run.
669 It also clear out the queue so subsequence commands don't run.
670 """
670 """
671
671
672 # The order of these 3 commands is absolutely critical. The currentCommand
672 # The order of these 3 commands is absolutely critical. The currentCommand
673 # must first be marked as finished BEFORE the queue is cleared and before
673 # must first be marked as finished BEFORE the queue is cleared and before
674 # the current command is sent the failure.
674 # the current command is sent the failure.
675 # Also, the queue must be cleared BEFORE the current command is sent the Failure
675 # Also, the queue must be cleared BEFORE the current command is sent the Failure
676 # otherwise the errback chain could trigger new commands to be added to the
676 # otherwise the errback chain could trigger new commands to be added to the
677 # queue before we clear it. We should clear ONLY the commands that were in
677 # queue before we clear it. We should clear ONLY the commands that were in
678 # the queue when the error occured.
678 # the queue when the error occured.
679 self.currentCommand.finished = True
679 self.currentCommand.finished = True
680 s = "%r %r %r" % (self.currentCommand.remoteMethod, self.currentCommand.args, self.currentCommand.kwargs)
680 s = "%r %r %r" % (self.currentCommand.remoteMethod, self.currentCommand.args, self.currentCommand.kwargs)
681 self.clear_queue(msg=s)
681 self.clear_queue(msg=s)
682 self.currentCommand.handleError(reason)
682 self.currentCommand.handleError(reason)
683
683
684 return None
684 return None
685
685
686 #---------------------------------------------------------------------------
686 #---------------------------------------------------------------------------
687 # IEngineCore methods
687 # IEngineCore methods
688 #---------------------------------------------------------------------------
688 #---------------------------------------------------------------------------
689
689
690 @queue
690 @queue
691 def execute(self, lines):
691 def execute(self, lines):
692 pass
692 pass
693
693
694 @queue
694 @queue
695 def push(self, namespace):
695 def push(self, namespace):
696 pass
696 pass
697
697
698 @queue
698 @queue
699 def pull(self, keys):
699 def pull(self, keys):
700 pass
700 pass
701
701
702 @queue
702 @queue
703 def push_function(self, namespace):
703 def push_function(self, namespace):
704 pass
704 pass
705
705
706 @queue
706 @queue
707 def pull_function(self, keys):
707 def pull_function(self, keys):
708 pass
708 pass
709
709
710 def get_result(self, i=None):
710 def get_result(self, i=None):
711 if i is None:
711 if i is None:
712 i = max(self.history.keys()+[None])
712 i = max(self.history.keys()+[None])
713
713
714 cmd = self.history.get(i, None)
714 cmd = self.history.get(i, None)
715 # Uncomment this line to disable chaching of results
715 # Uncomment this line to disable chaching of results
716 #cmd = None
716 #cmd = None
717 if cmd is None:
717 if cmd is None:
718 return self.submitCommand(Command('get_result', i))
718 return self.submitCommand(Command('get_result', i))
719 else:
719 else:
720 return defer.succeed(cmd)
720 return defer.succeed(cmd)
721
721
722 def reset(self):
722 def reset(self):
723 self.clear_queue()
723 self.clear_queue()
724 self.history = {} # reset the cache - I am not sure we should do this
724 self.history = {} # reset the cache - I am not sure we should do this
725 return self.submitCommand(Command('reset'))
725 return self.submitCommand(Command('reset'))
726
726
727 def kill(self):
727 def kill(self):
728 self.clear_queue()
728 self.clear_queue()
729 return self.submitCommand(Command('kill'))
729 return self.submitCommand(Command('kill'))
730
730
731 @queue
731 @queue
732 def keys(self):
732 def keys(self):
733 pass
733 pass
734
734
735 #---------------------------------------------------------------------------
735 #---------------------------------------------------------------------------
736 # IEngineSerialized methods
736 # IEngineSerialized methods
737 #---------------------------------------------------------------------------
737 #---------------------------------------------------------------------------
738
738
739 @queue
739 @queue
740 def push_serialized(self, namespace):
740 def push_serialized(self, namespace):
741 pass
741 pass
742
742
743 @queue
743 @queue
744 def pull_serialized(self, keys):
744 def pull_serialized(self, keys):
745 pass
745 pass
746
746
747 #---------------------------------------------------------------------------
747 #---------------------------------------------------------------------------
748 # IEngineProperties methods
748 # IEngineProperties methods
749 #---------------------------------------------------------------------------
749 #---------------------------------------------------------------------------
750
750
751 @queue
751 @queue
752 def set_properties(self, namespace):
752 def set_properties(self, namespace):
753 pass
753 pass
754
754
755 @queue
755 @queue
756 def get_properties(self, keys=None):
756 def get_properties(self, keys=None):
757 pass
757 pass
758
758
759 @queue
759 @queue
760 def del_properties(self, keys):
760 def del_properties(self, keys):
761 pass
761 pass
762
762
763 @queue
763 @queue
764 def has_properties(self, keys):
764 def has_properties(self, keys):
765 pass
765 pass
766
766
767 @queue
767 @queue
768 def clear_properties(self):
768 def clear_properties(self):
769 pass
769 pass
770
770
771 #---------------------------------------------------------------------------
771 #---------------------------------------------------------------------------
772 # IQueuedEngine methods
772 # IQueuedEngine methods
773 #---------------------------------------------------------------------------
773 #---------------------------------------------------------------------------
774
774
775 def clear_queue(self, msg=''):
775 def clear_queue(self, msg=''):
776 """Clear the queue, but doesn't cancel the currently running commmand."""
776 """Clear the queue, but doesn't cancel the currently running commmand."""
777
777
778 for cmd in self.queued:
778 for cmd in self.queued:
779 cmd.deferred.errback(failure.Failure(error.QueueCleared(msg)))
779 cmd.deferred.errback(failure.Failure(error.QueueCleared(msg)))
780 self.queued = []
780 self.queued = []
781 return defer.succeed(None)
781 return defer.succeed(None)
782
782
783 def queue_status(self):
783 def queue_status(self):
784 if self.currentCommand is not None:
784 if self.currentCommand is not None:
785 if self.currentCommand.finished:
785 if self.currentCommand.finished:
786 pending = repr(None)
786 pending = repr(None)
787 else:
787 else:
788 pending = repr(self.currentCommand)
788 pending = repr(self.currentCommand)
789 else:
789 else:
790 pending = repr(None)
790 pending = repr(None)
791 dikt = {'queue':map(repr,self.queued), 'pending':pending}
791 dikt = {'queue':map(repr,self.queued), 'pending':pending}
792 return defer.succeed(dikt)
792 return defer.succeed(dikt)
793
793
794 def register_failure_observer(self, obs):
794 def register_failure_observer(self, obs):
795 self.failureObservers.append(obs)
795 self.failureObservers.append(obs)
796
796
797 def unregister_failure_observer(self, obs):
797 def unregister_failure_observer(self, obs):
798 self.failureObservers.remove(obs)
798 self.failureObservers.remove(obs)
799
799
800
800
801 # Now register QueuedEngine as an adpater class that makes an IEngineBase into a
801 # Now register QueuedEngine as an adpater class that makes an IEngineBase into a
802 # IEngineQueued.
802 # IEngineQueued.
803 components.registerAdapter(QueuedEngine, IEngineBase, IEngineQueued)
803 components.registerAdapter(QueuedEngine, IEngineBase, IEngineQueued)
804
804
805
805
806 class Command(object):
806 class Command(object):
807 """A command object that encapslates queued commands.
807 """A command object that encapslates queued commands.
808
808
809 This class basically keeps track of a command that has been queued
809 This class basically keeps track of a command that has been queued
810 in a QueuedEngine. It manages the deferreds and hold the method to be called
810 in a QueuedEngine. It manages the deferreds and hold the method to be called
811 and the arguments to that method.
811 and the arguments to that method.
812 """
812 """
813
813
814
814
815 def __init__(self, remoteMethod, *args, **kwargs):
815 def __init__(self, remoteMethod, *args, **kwargs):
816 """Build a new Command object."""
816 """Build a new Command object."""
817
817
818 self.remoteMethod = remoteMethod
818 self.remoteMethod = remoteMethod
819 self.args = args
819 self.args = args
820 self.kwargs = kwargs
820 self.kwargs = kwargs
821 self.finished = False
821 self.finished = False
822
822
823 def setDeferred(self, d):
823 def setDeferred(self, d):
824 """Sets the deferred attribute of the Command."""
824 """Sets the deferred attribute of the Command."""
825
825
826 self.deferred = d
826 self.deferred = d
827
827
828 def __repr__(self):
828 def __repr__(self):
829 if not self.args:
829 if not self.args:
830 args = ''
830 args = ''
831 else:
831 else:
832 args = str(self.args)[1:-2] #cut off (...,)
832 args = str(self.args)[1:-2] #cut off (...,)
833 for k,v in self.kwargs.iteritems():
833 for k,v in self.kwargs.iteritems():
834 if args:
834 if args:
835 args += ', '
835 args += ', '
836 args += '%s=%r' %(k,v)
836 args += '%s=%r' %(k,v)
837 return "%s(%s)" %(self.remoteMethod, args)
837 return "%s(%s)" %(self.remoteMethod, args)
838
838
839 def handleResult(self, result):
839 def handleResult(self, result):
840 """When the result is ready, relay it to self.deferred."""
840 """When the result is ready, relay it to self.deferred."""
841
841
842 self.deferred.callback(result)
842 self.deferred.callback(result)
843
843
844 def handleError(self, reason):
844 def handleError(self, reason):
845 """When an error has occured, relay it to self.deferred."""
845 """When an error has occured, relay it to self.deferred."""
846
846
847 self.deferred.errback(reason)
847 self.deferred.errback(reason)
848
848
849 class ThreadedEngineService(EngineService):
849 class ThreadedEngineService(EngineService):
850 """An EngineService subclass that defers execute commands to a separate thread.
850 """An EngineService subclass that defers execute commands to a separate
851 thread.
851
852
852 ThreadedEngineService uses twisted.internet.threads.deferToThread to defer execute
853 ThreadedEngineService uses twisted.internet.threads.deferToThread to
853 requests to a separate thread. GUI frontends may want to use ThreadedEngineService as
854 defer execute requests to a separate thread. GUI frontends may want to
854 the engine in an IPython.frontend.frontendbase.FrontEndBase subclass to prevent
855 use ThreadedEngineService as the engine in an
856 IPython.frontend.frontendbase.FrontEndBase subclass to prevent
855 block execution from blocking the GUI thread.
857 block execution from blocking the GUI thread.
856 """
858 """
857
859
858 zi.implements(IEngineBase)
860 zi.implements(IEngineBase)
859
861
860 def __init__(self, shellClass=Interpreter, mpi=None):
862 def __init__(self, shellClass=Interpreter, mpi=None):
861 EngineService.__init__(self, shellClass, mpi)
863 EngineService.__init__(self, shellClass, mpi)
862
864
863
865
864 def execute(self, lines):
866 def execute(self, lines):
865 # Only import this if we are going to use this class
867 # Only import this if we are going to use this class
866 from twisted.internet import threads
868 from twisted.internet import threads
867
869
868 msg = {'engineid':self.id,
870 msg = {'engineid':self.id,
869 'method':'execute',
871 'method':'execute',
870 'args':[lines]}
872 'args':[lines]}
871
873
872 d = threads.deferToThread(self.shell.execute, lines)
874 d = threads.deferToThread(self.shell.execute, lines)
873 d.addCallback(self.addIDToResult)
875 d.addCallback(self.addIDToResult)
874 return d
876 return d
General Comments 0
You need to be logged in to leave comments. Login now