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