##// END OF EJS Templates
FrontEndBase is now synchronous.
Barry Wark -
Show More
@@ -1,371 +1,396 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
3 """
3 """
4 frontendbase provides an interface and base class for GUI frontends for
4 frontendbase provides an interface and base class for GUI frontends for
5 IPython.kernel/IPython.kernel.core.
5 IPython.kernel/IPython.kernel.core.
6
6
7 Frontend implementations will likely want to subclass FrontEndBase.
7 Frontend implementations will likely want to subclass FrontEndBase.
8
8
9 Author: Barry Wark
9 Author: Barry Wark
10 """
10 """
11 __docformat__ = "restructuredtext en"
11 __docformat__ = "restructuredtext en"
12
12
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14 # Copyright (C) 2008 The IPython Development Team
14 # Copyright (C) 2008 The IPython Development Team
15 #
15 #
16 # 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
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19
19
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-------------------------------------------------------------------------------
22 #-------------------------------------------------------------------------------
23 import string
23 import string
24 import uuid
24 import uuid
25 import _ast
25 import _ast
26
26
27 import zope.interface as zi
27 import zope.interface as zi
28
28
29 from IPython.kernel.core.history import FrontEndHistory
29 from IPython.kernel.core.history import FrontEndHistory
30 from IPython.kernel.core.util import Bunch
30 from IPython.kernel.core.util import Bunch
31 from IPython.kernel.engineservice import IEngineCore
31 from IPython.kernel.engineservice import IEngineCore
32
32
33 from twisted.python.failure import Failure
33 from twisted.python.failure import Failure
34
34
35 ##############################################################################
35 ##############################################################################
36 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
36 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
37 # not
37 # not
38
38
39 rc = Bunch()
39 rc = Bunch()
40 rc.prompt_in1 = r'In [$number]: '
40 rc.prompt_in1 = r'In [$number]: '
41 rc.prompt_in2 = r'...'
41 rc.prompt_in2 = r'...'
42 rc.prompt_out = r'Out [$number]: '
42 rc.prompt_out = r'Out [$number]: '
43
43
44 ##############################################################################
44 ##############################################################################
45
45
46 class IFrontEndFactory(zi.Interface):
46 class IFrontEndFactory(zi.Interface):
47 """Factory interface for frontends."""
47 """Factory interface for frontends."""
48
48
49 def __call__(engine=None, history=None):
49 def __call__(engine=None, history=None):
50 """
50 """
51 Parameters:
51 Parameters:
52 interpreter : IPython.kernel.engineservice.IEngineCore
52 interpreter : IPython.kernel.engineservice.IEngineCore
53 """
53 """
54
54
55 pass
55 pass
56
56
57
57
58
58
59 class IFrontEnd(zi.Interface):
59 class IFrontEnd(zi.Interface):
60 """Interface for frontends. All methods return t.i.d.Deferred"""
60 """Interface for frontends. All methods return t.i.d.Deferred"""
61
61
62 zi.Attribute("input_prompt_template", "string.Template instance\
62 zi.Attribute("input_prompt_template", "string.Template instance\
63 substituteable with execute result.")
63 substituteable with execute result.")
64 zi.Attribute("output_prompt_template", "string.Template instance\
64 zi.Attribute("output_prompt_template", "string.Template instance\
65 substituteable with execute result.")
65 substituteable with execute result.")
66 zi.Attribute("continuation_prompt_template", "string.Template instance\
66 zi.Attribute("continuation_prompt_template", "string.Template instance\
67 substituteable with execute result.")
67 substituteable with execute result.")
68
68
69 def update_cell_prompt(result, blockID=None):
69 def update_cell_prompt(result, blockID=None):
70 """Subclass may override to update the input prompt for a block.
70 """Subclass may override to update the input prompt for a block.
71 Since this method will be called as a
71 Since this method will be called as a
72 twisted.internet.defer.Deferred's callback/errback,
72 twisted.internet.defer.Deferred's callback/errback,
73 implementations should return result when finished.
73 implementations should return result when finished.
74
74
75 Result is a result dict in case of success, and a
75 Result is a result dict in case of success, and a
76 twisted.python.util.failure.Failure in case of an error
76 twisted.python.util.failure.Failure in case of an error
77 """
77 """
78
78
79 pass
79 pass
80
80
81
81
82 def render_result(result):
82 def render_result(result):
83 """Render the result of an execute call. Implementors may choose the
83 """Render the result of an execute call. Implementors may choose the
84 method of rendering.
84 method of rendering.
85 For example, a notebook-style frontend might render a Chaco plot
85 For example, a notebook-style frontend might render a Chaco plot
86 inline.
86 inline.
87
87
88 Parameters:
88 Parameters:
89 result : dict (result of IEngineBase.execute )
89 result : dict (result of IEngineBase.execute )
90 blockID = result['blockID']
90 blockID = result['blockID']
91
91
92 Result:
92 Result:
93 Output of frontend rendering
93 Output of frontend rendering
94 """
94 """
95
95
96 pass
96 pass
97
97
98 def render_error(failure):
98 def render_error(failure):
99 """Subclasses must override to render the failure. Since this method
99 """Subclasses must override to render the failure. Since this method
100 will be called as a twisted.internet.defer.Deferred's callback,
100 will be called as a twisted.internet.defer.Deferred's callback,
101 implementations should return result when finished.
101 implementations should return result when finished.
102
102
103 blockID = failure.blockID
103 blockID = failure.blockID
104 """
104 """
105
105
106 pass
106 pass
107
107
108
108
109 def input_prompt(number=''):
109 def input_prompt(number=''):
110 """Returns the input prompt by subsituting into
110 """Returns the input prompt by subsituting into
111 self.input_prompt_template
111 self.input_prompt_template
112 """
112 """
113 pass
113 pass
114
114
115 def output_prompt(number=''):
115 def output_prompt(number=''):
116 """Returns the output prompt by subsituting into
116 """Returns the output prompt by subsituting into
117 self.output_prompt_template
117 self.output_prompt_template
118 """
118 """
119
119
120 pass
120 pass
121
121
122 def continuation_prompt():
122 def continuation_prompt():
123 """Returns the continuation prompt by subsituting into
123 """Returns the continuation prompt by subsituting into
124 self.continuation_prompt_template
124 self.continuation_prompt_template
125 """
125 """
126
126
127 pass
127 pass
128
128
129 def is_complete(block):
129 def is_complete(block):
130 """Returns True if block is complete, False otherwise."""
130 """Returns True if block is complete, False otherwise."""
131
131
132 pass
132 pass
133
133
134 def compile_ast(block):
134 def compile_ast(block):
135 """Compiles block to an _ast.AST"""
135 """Compiles block to an _ast.AST"""
136
136
137 pass
137 pass
138
138
139
139
140 def get_history_previous(currentBlock):
140 def get_history_previous(currentBlock):
141 """Returns the block previous in the history. Saves currentBlock if
141 """Returns the block previous in the history. Saves currentBlock if
142 the history_cursor is currently at the end of the input history"""
142 the history_cursor is currently at the end of the input history"""
143 pass
143 pass
144
144
145 def get_history_next():
145 def get_history_next():
146 """Returns the next block in the history."""
146 """Returns the next block in the history."""
147
147
148 pass
148 pass
149
149
150
150
151 class FrontEndBase(object):
151 class FrontEndBase(object):
152 """
152 """
153 FrontEndBase manages the state tasks for a CLI frontend:
153 FrontEndBase manages the state tasks for a CLI frontend:
154 - Input and output history management
154 - Input and output history management
155 - Input/continuation and output prompt generation
155 - Input/continuation and output prompt generation
156
156
157 Some issues (due to possibly unavailable engine):
157 Some issues (due to possibly unavailable engine):
158 - How do we get the current cell number for the engine?
158 - How do we get the current cell number for the engine?
159 - How do we handle completions?
159 - How do we handle completions?
160 """
160 """
161
161
162 history_cursor = 0
162 history_cursor = 0
163
163
164 current_indent_level = 0
164 current_indent_level = 0
165
165
166
166
167 input_prompt_template = string.Template(rc.prompt_in1)
167 input_prompt_template = string.Template(rc.prompt_in1)
168 output_prompt_template = string.Template(rc.prompt_out)
168 output_prompt_template = string.Template(rc.prompt_out)
169 continuation_prompt_template = string.Template(rc.prompt_in2)
169 continuation_prompt_template = string.Template(rc.prompt_in2)
170
170
171 def __init__(self, engine=None, history=None):
171 def __init__(self, shell=None, history=None):
172 assert(engine==None or IEngineCore.providedBy(engine))
172 self.shell = shell
173 self.engine = IEngineCore(engine)
174 if history is None:
173 if history is None:
175 self.history = FrontEndHistory(input_cache=[''])
174 self.history = FrontEndHistory(input_cache=[''])
176 else:
175 else:
177 self.history = history
176 self.history = history
178
177
179
178
180 def input_prompt(self, number=''):
179 def input_prompt(self, number=''):
181 """Returns the current input prompt
180 """Returns the current input prompt
182
181
183 It would be great to use ipython1.core.prompts.Prompt1 here
182 It would be great to use ipython1.core.prompts.Prompt1 here
184 """
183 """
185 return self.input_prompt_template.safe_substitute({'number':number})
184 return self.input_prompt_template.safe_substitute({'number':number})
186
185
187
186
188 def continuation_prompt(self):
187 def continuation_prompt(self):
189 """Returns the current continuation prompt"""
188 """Returns the current continuation prompt"""
190
189
191 return self.continuation_prompt_template.safe_substitute()
190 return self.continuation_prompt_template.safe_substitute()
192
191
193 def output_prompt(self, number=''):
192 def output_prompt(self, number=''):
194 """Returns the output prompt for result"""
193 """Returns the output prompt for result"""
195
194
196 return self.output_prompt_template.safe_substitute({'number':number})
195 return self.output_prompt_template.safe_substitute({'number':number})
197
196
198
197
199 def is_complete(self, block):
198 def is_complete(self, block):
200 """Determine if block is complete.
199 """Determine if block is complete.
201
200
202 Parameters
201 Parameters
203 block : string
202 block : string
204
203
205 Result
204 Result
206 True if block can be sent to the engine without compile errors.
205 True if block can be sent to the engine without compile errors.
207 False otherwise.
206 False otherwise.
208 """
207 """
209
208
210 try:
209 try:
211 ast = self.compile_ast(block)
210 ast = self.compile_ast(block)
212 except:
211 except:
213 return False
212 return False
214
213
215 lines = block.split('\n')
214 lines = block.split('\n')
216 return (len(lines)==1 or str(lines[-1])=='')
215 return (len(lines)==1 or str(lines[-1])=='')
217
216
218
217
219 def compile_ast(self, block):
218 def compile_ast(self, block):
220 """Compile block to an AST
219 """Compile block to an AST
221
220
222 Parameters:
221 Parameters:
223 block : str
222 block : str
224
223
225 Result:
224 Result:
226 AST
225 AST
227
226
228 Throws:
227 Throws:
229 Exception if block cannot be compiled
228 Exception if block cannot be compiled
230 """
229 """
231
230
232 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
231 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
233
232
234
233
235 def execute(self, block, blockID=None):
234 def execute(self, block, blockID=None):
236 """Execute the block and return result.
235 """Execute the block and return the result.
237
236
238 Parameters:
237 Parameters:
239 block : {str, AST}
238 block : {str, AST}
240 blockID : any
239 blockID : any
241 Caller may provide an ID to identify this block.
240 Caller may provide an ID to identify this block.
242 result['blockID'] := blockID
241 result['blockID'] := blockID
243
242
244 Result:
243 Result:
245 Deferred result of self.interpreter.execute
244 Deferred result of self.interpreter.execute
246 """
245 """
247
246
248 pass
247 if(not self.is_complete(block)):
248 raise Exception("Block is not compilable")
249
250 if(blockID == None):
251 blockID = uuid.uuid4() #random UUID
252
253 try:
254 result = self.shell.execute(block)
255 except Exception,e:
256 e = self._add_block_id_for_failure(e, blockID=blockID)
257 e = self.update_cell_prompt(e, blockID=blockID)
258 e = self.render_error(e)
259 else:
260 result = self._add_block_id_for_result(result, blockID=blockID)
261 result = self.update_cell_prompt(result, blockID=blockID)
262 result = self.render_result(result)
263
264 return result
249
265
250
266
251 def _add_block_id_for_result(self, result, blockID):
267 def _add_block_id_for_result(self, result, blockID):
252 """Add the blockID to result or failure. Unfortunatley, we have to
268 """Add the blockID to result or failure. Unfortunatley, we have to
253 treat failures differently than result dicts.
269 treat failures differently than result dicts.
254 """
270 """
255
271
256 result['blockID'] = blockID
272 result['blockID'] = blockID
257
273
258 return result
274 return result
259
275
260 def _add_block_id_for_failure(self, failure, blockID):
276 def _add_block_id_for_failure(self, failure, blockID):
261 """_add_block_id_for_failure"""
277 """_add_block_id_for_failure"""
262
278
263 failure.blockID = blockID
279 failure.blockID = blockID
264 return failure
280 return failure
265
281
266
282
267 def _add_history(self, result, block=None):
283 def _add_history(self, result, block=None):
268 """Add block to the history"""
284 """Add block to the history"""
269
285
270 assert(block != None)
286 assert(block != None)
271 self.history.add_items([block])
287 self.history.add_items([block])
272 self.history_cursor += 1
288 self.history_cursor += 1
273
289
274 return result
290 return result
275
291
276
292
277 def get_history_previous(self, currentBlock):
293 def get_history_previous(self, currentBlock):
278 """ Returns previous history string and decrement history cursor.
294 """ Returns previous history string and decrement history cursor.
279 """
295 """
280 command = self.history.get_history_item(self.history_cursor - 1)
296 command = self.history.get_history_item(self.history_cursor - 1)
281
297
282 if command is not None:
298 if command is not None:
283 if(self.history_cursor == len(self.history.input_cache)):
299 if(self.history_cursor == len(self.history.input_cache)):
284 self.history.input_cache[self.history_cursor] = currentBlock
300 self.history.input_cache[self.history_cursor] = currentBlock
285 self.history_cursor -= 1
301 self.history_cursor -= 1
286 return command
302 return command
287
303
288
304
289 def get_history_next(self):
305 def get_history_next(self):
290 """ Returns next history string and increment history cursor.
306 """ Returns next history string and increment history cursor.
291 """
307 """
292 command = self.history.get_history_item(self.history_cursor+1)
308 command = self.history.get_history_item(self.history_cursor+1)
293
309
294 if command is not None:
310 if command is not None:
295 self.history_cursor += 1
311 self.history_cursor += 1
296 return command
312 return command
297
313
298 ###
314 ###
299 # Subclasses probably want to override these methods...
315 # Subclasses probably want to override these methods...
300 ###
316 ###
301
317
302 def update_cell_prompt(self, result, blockID=None):
318 def update_cell_prompt(self, result, blockID=None):
303 """Subclass may override to update the input prompt for a block.
319 """Subclass may override to update the input prompt for a block.
304 Since this method will be called as a
320 Since this method will be called as a
305 twisted.internet.defer.Deferred's callback, implementations should
321 twisted.internet.defer.Deferred's callback, implementations should
306 return result when finished.
322 return result when finished.
307 """
323 """
308
324
309 return result
325 return result
310
326
311
327
312 def render_result(self, result):
328 def render_result(self, result):
313 """Subclasses must override to render result. Since this method will
329 """Subclasses must override to render result. Since this method will
314 be called as a twisted.internet.defer.Deferred's callback,
330 be called as a twisted.internet.defer.Deferred's callback,
315 implementations should return result when finished.
331 implementations should return result when finished.
316 """
332 """
317
333
318 return result
334 return result
319
335
320
336
321 def render_error(self, failure):
337 def render_error(self, failure):
322 """Subclasses must override to render the failure. Since this method
338 """Subclasses must override to render the failure. Since this method
323 will be called as a twisted.internet.defer.Deferred's callback,
339 will be called as a twisted.internet.defer.Deferred's callback,
324 implementations should return result when finished.
340 implementations should return result when finished.
325 """
341 """
326
342
327 return failure
343 return failure
328
344
329
345
330
346
331 class AsynchronousFrontEndBase(FrontEndBase):
347 class AsynchronousFrontEndBase(FrontEndBase):
332 """
348 """
333 Overrides FrontEndBase to wrap execute in a deferred result.
349 Overrides FrontEndBase to wrap execute in a deferred result.
334 All callbacks are made as callbacks on the deferred result.
350 All callbacks are made as callbacks on the deferred result.
335 """
351 """
336
352
337 zi.implements(IFrontEnd)
353 zi.implements(IFrontEnd)
338 zi.classProvides(IFrontEndFactory)
354 zi.classProvides(IFrontEndFactory)
339
355
356 def __init__(self, engine=None, history=None):
357 assert(engine==None or IEngineCore.providedBy(engine))
358 self.engine = IEngineCore(engine)
359 if history is None:
360 self.history = FrontEndHistory(input_cache=[''])
361 else:
362 self.history = history
363
364
340 def execute(self, block, blockID=None):
365 def execute(self, block, blockID=None):
341 """Execute the block and return the deferred result.
366 """Execute the block and return the deferred result.
342
367
343 Parameters:
368 Parameters:
344 block : {str, AST}
369 block : {str, AST}
345 blockID : any
370 blockID : any
346 Caller may provide an ID to identify this block.
371 Caller may provide an ID to identify this block.
347 result['blockID'] := blockID
372 result['blockID'] := blockID
348
373
349 Result:
374 Result:
350 Deferred result of self.interpreter.execute
375 Deferred result of self.interpreter.execute
351 """
376 """
352
377
353 if(not self.is_complete(block)):
378 if(not self.is_complete(block)):
354 return Failure(Exception("Block is not compilable"))
379 return Failure(Exception("Block is not compilable"))
355
380
356 if(blockID == None):
381 if(blockID == None):
357 blockID = uuid.uuid4() #random UUID
382 blockID = uuid.uuid4() #random UUID
358
383
359 d = self.engine.execute(block)
384 d = self.engine.execute(block)
360 d.addCallback(self._add_history, block=block)
385 d.addCallback(self._add_history, block=block)
361 d.addCallbacks(self._add_block_id_for_result,
386 d.addCallbacks(self._add_block_id_for_result,
362 errback=self._add_block_id_for_failure,
387 errback=self._add_block_id_for_failure,
363 callbackArgs=(blockID,),
388 callbackArgs=(blockID,),
364 errbackArgs=(blockID,))
389 errbackArgs=(blockID,))
365 d.addBoth(self.update_cell_prompt, blockID=blockID)
390 d.addBoth(self.update_cell_prompt, blockID=blockID)
366 d.addCallbacks(self.render_result,
391 d.addCallbacks(self.render_result,
367 errback=self.render_error)
392 errback=self.render_error)
368
393
369 return d
394 return d
370
395
371
396
General Comments 0
You need to be logged in to leave comments. Login now