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