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