frontendbase.py
407 lines
| 12.0 KiB
| text/x-python
|
PythonLexer
Barry Wark
|
r1263 | # encoding: utf-8 | ||
Barry Wark
|
r1291 | # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*- | ||
Barry Wark
|
r1263 | """ | ||
Barry Wark
|
r1291 | frontendbase provides an interface and base class for GUI frontends for | ||
IPython.kernel/IPython.kernel.core. | ||||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1277 | Frontend implementations will likely want to subclass FrontEndBase. | ||
Author: Barry Wark | ||||
Barry Wark
|
r1263 | """ | ||
__docformat__ = "restructuredtext en" | ||||
#------------------------------------------------------------------------------- | ||||
Barry Wark
|
r1277 | # Copyright (C) 2008 The IPython Development Team | ||
Barry Wark
|
r1263 | # | ||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#------------------------------------------------------------------------------- | ||||
#------------------------------------------------------------------------------- | ||||
# Imports | ||||
#------------------------------------------------------------------------------- | ||||
import string | ||||
import uuid | ||||
Barry Wark
|
r1279 | import _ast | ||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1313 | try: | ||
from zope.interface import Interface, Attribute, implements, classProvides | ||||
except ImportError: | ||||
#zope.interface is not available | ||||
Interface = object | ||||
def Attribute(name, doc): pass | ||||
def implements(interface): pass | ||||
def classProvides(interface): pass | ||||
Barry Wark
|
r1263 | |||
from IPython.kernel.core.history import FrontEndHistory | ||||
from IPython.kernel.core.util import Bunch | ||||
from IPython.kernel.engineservice import IEngineCore | ||||
Barry Wark
|
r1313 | try: | ||
from twisted.python.failure import Failure | ||||
except ImportError: | ||||
#Twisted not available | ||||
Failure = Exception | ||||
Barry Wark
|
r1263 | |||
############################################################################## | ||||
# TEMPORARY!!! fake configuration, while we decide whether to use tconfig or | ||||
# not | ||||
rc = Bunch() | ||||
rc.prompt_in1 = r'In [$number]: ' | ||||
rc.prompt_in2 = r'...' | ||||
rc.prompt_out = r'Out [$number]: ' | ||||
############################################################################## | ||||
Barry Wark
|
r1313 | class IFrontEndFactory(Interface): | ||
Barry Wark
|
r1277 | """Factory interface for frontends.""" | ||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1277 | def __call__(engine=None, history=None): | ||
Barry Wark
|
r1263 | """ | ||
Parameters: | ||||
interpreter : IPython.kernel.engineservice.IEngineCore | ||||
""" | ||||
Barry Wark
|
r1277 | |||
Barry Wark
|
r1263 | pass | ||
Barry Wark
|
r1277 | |||
Barry Wark
|
r1313 | class IFrontEnd(Interface): | ||
Barry Wark
|
r1277 | """Interface for frontends. All methods return t.i.d.Deferred""" | ||
Barry Wark
|
r1313 | Attribute("input_prompt_template", "string.Template instance\ | ||
Barry Wark
|
r1291 | substituteable with execute result.") | ||
Barry Wark
|
r1313 | Attribute("output_prompt_template", "string.Template instance\ | ||
Barry Wark
|
r1291 | substituteable with execute result.") | ||
Barry Wark
|
r1313 | Attribute("continuation_prompt_template", "string.Template instance\ | ||
Barry Wark
|
r1291 | substituteable with execute result.") | ||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1303 | def update_cell_prompt(result, blockID=None): | ||
Barry Wark
|
r1263 | """Subclass may override to update the input prompt for a block. | ||
Barry Wark
|
r1291 | Since this method will be called as a | ||
Barry Wark
|
r1303 | twisted.internet.defer.Deferred's callback/errback, | ||
Barry Wark
|
r1293 | implementations should return result when finished. | ||
Barry Wark
|
r1303 | Result is a result dict in case of success, and a | ||
twisted.python.util.failure.Failure in case of an error | ||||
Barry Wark
|
r1293 | """ | ||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1277 | pass | ||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1303 | |||
def render_result(result): | ||||
Barry Wark
|
r1291 | """Render the result of an execute call. Implementors may choose the | ||
method of rendering. | ||||
For example, a notebook-style frontend might render a Chaco plot | ||||
inline. | ||||
Barry Wark
|
r1263 | |||
Parameters: | ||||
result : dict (result of IEngineBase.execute ) | ||||
Barry Wark
|
r1303 | blockID = result['blockID'] | ||
Barry Wark
|
r1263 | |||
Result: | ||||
Output of frontend rendering | ||||
""" | ||||
Barry Wark
|
r1277 | pass | ||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1303 | def render_error(failure): | ||
Barry Wark
|
r1291 | """Subclasses must override to render the failure. Since this method | ||
Barry Wark
|
r1303 | will be called as a twisted.internet.defer.Deferred's callback, | ||
Barry Wark
|
r1291 | implementations should return result when finished. | ||
Barry Wark
|
r1303 | |||
blockID = failure.blockID | ||||
Barry Wark
|
r1291 | """ | ||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1277 | pass | ||
Barry Wark
|
r1304 | def input_prompt(number=''): | ||
Barry Wark
|
r1291 | """Returns the input prompt by subsituting into | ||
self.input_prompt_template | ||||
""" | ||||
Barry Wark
|
r1277 | pass | ||
Barry Wark
|
r1304 | def output_prompt(number=''): | ||
Barry Wark
|
r1291 | """Returns the output prompt by subsituting into | ||
self.output_prompt_template | ||||
""" | ||||
Barry Wark
|
r1277 | |||
pass | ||||
Barry Wark
|
r1282 | def continuation_prompt(): | ||
Barry Wark
|
r1291 | """Returns the continuation prompt by subsituting into | ||
self.continuation_prompt_template | ||||
""" | ||||
Barry Wark
|
r1277 | |||
pass | ||||
def is_complete(block): | ||||
"""Returns True if block is complete, False otherwise.""" | ||||
pass | ||||
def compile_ast(block): | ||||
"""Compiles block to an _ast.AST""" | ||||
pass | ||||
Barry Wark
|
r1279 | def get_history_previous(currentBlock): | ||
Barry Wark
|
r1281 | """Returns the block previous in the history. Saves currentBlock if | ||
the history_cursor is currently at the end of the input history""" | ||||
Barry Wark
|
r1277 | pass | ||
Barry Wark
|
r1281 | def get_history_next(): | ||
Barry Wark
|
r1277 | """Returns the next block in the history.""" | ||
pass | ||||
Barry Wark
|
r1263 | |||
class FrontEndBase(object): | ||||
""" | ||||
FrontEndBase manages the state tasks for a CLI frontend: | ||||
- Input and output history management | ||||
- Input/continuation and output prompt generation | ||||
Some issues (due to possibly unavailable engine): | ||||
- How do we get the current cell number for the engine? | ||||
- How do we handle completions? | ||||
""" | ||||
history_cursor = 0 | ||||
current_indent_level = 0 | ||||
input_prompt_template = string.Template(rc.prompt_in1) | ||||
output_prompt_template = string.Template(rc.prompt_out) | ||||
continuation_prompt_template = string.Template(rc.prompt_in2) | ||||
Barry Wark
|
r1306 | def __init__(self, shell=None, history=None): | ||
self.shell = shell | ||||
Barry Wark
|
r1263 | if history is None: | ||
self.history = FrontEndHistory(input_cache=['']) | ||||
else: | ||||
self.history = history | ||||
Barry Wark
|
r1304 | def input_prompt(self, number=''): | ||
Barry Wark
|
r1263 | """Returns the current input prompt | ||
It would be great to use ipython1.core.prompts.Prompt1 here | ||||
""" | ||||
Barry Wark
|
r1303 | return self.input_prompt_template.safe_substitute({'number':number}) | ||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1282 | def continuation_prompt(self): | ||
Barry Wark
|
r1263 | """Returns the current continuation prompt""" | ||
return self.continuation_prompt_template.safe_substitute() | ||||
Barry Wark
|
r1304 | def output_prompt(self, number=''): | ||
Barry Wark
|
r1263 | """Returns the output prompt for result""" | ||
Barry Wark
|
r1303 | return self.output_prompt_template.safe_substitute({'number':number}) | ||
Barry Wark
|
r1263 | |||
def is_complete(self, block): | ||||
"""Determine if block is complete. | ||||
Parameters | ||||
block : string | ||||
Result | ||||
True if block can be sent to the engine without compile errors. | ||||
False otherwise. | ||||
""" | ||||
try: | ||||
Barry Wark
|
r1278 | ast = self.compile_ast(block) | ||
Barry Wark
|
r1263 | except: | ||
return False | ||||
Barry Wark
|
r1278 | |||
lines = block.split('\n') | ||||
return (len(lines)==1 or str(lines[-1])=='') | ||||
Barry Wark
|
r1263 | |||
def compile_ast(self, block): | ||||
"""Compile block to an AST | ||||
Parameters: | ||||
block : str | ||||
Result: | ||||
AST | ||||
Throws: | ||||
Exception if block cannot be compiled | ||||
""" | ||||
return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST) | ||||
def execute(self, block, blockID=None): | ||||
Barry Wark
|
r1306 | """Execute the block and return the result. | ||
Barry Wark
|
r1263 | |||
Parameters: | ||||
block : {str, AST} | ||||
blockID : any | ||||
Barry Wark
|
r1291 | Caller may provide an ID to identify this block. | ||
result['blockID'] := blockID | ||||
Barry Wark
|
r1263 | |||
Result: | ||||
Deferred result of self.interpreter.execute | ||||
""" | ||||
Barry Wark
|
r1279 | |||
Barry Wark
|
r1306 | if(not self.is_complete(block)): | ||
raise Exception("Block is not compilable") | ||||
if(blockID == None): | ||||
blockID = uuid.uuid4() #random UUID | ||||
try: | ||||
result = self.shell.execute(block) | ||||
except Exception,e: | ||||
e = self._add_block_id_for_failure(e, blockID=blockID) | ||||
e = self.update_cell_prompt(e, blockID=blockID) | ||||
e = self.render_error(e) | ||||
else: | ||||
result = self._add_block_id_for_result(result, blockID=blockID) | ||||
result = self.update_cell_prompt(result, blockID=blockID) | ||||
result = self.render_result(result) | ||||
return result | ||||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1279 | |||
Barry Wark
|
r1303 | def _add_block_id_for_result(self, result, blockID): | ||
Barry Wark
|
r1291 | """Add the blockID to result or failure. Unfortunatley, we have to | ||
treat failures differently than result dicts. | ||||
Barry Wark
|
r1283 | """ | ||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1303 | result['blockID'] = blockID | ||
Barry Wark
|
r1263 | |||
return result | ||||
Barry Wark
|
r1303 | def _add_block_id_for_failure(self, failure, blockID): | ||
"""_add_block_id_for_failure""" | ||||
failure.blockID = blockID | ||||
return failure | ||||
Barry Wark
|
r1279 | def _add_history(self, result, block=None): | ||
"""Add block to the history""" | ||||
assert(block != None) | ||||
self.history.add_items([block]) | ||||
self.history_cursor += 1 | ||||
return result | ||||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1279 | def get_history_previous(self, currentBlock): | ||
Barry Wark
|
r1263 | """ Returns previous history string and decrement history cursor. | ||
""" | ||||
command = self.history.get_history_item(self.history_cursor - 1) | ||||
Barry Wark
|
r1281 | |||
Barry Wark
|
r1263 | if command is not None: | ||
Barry Wark
|
r1281 | if(self.history_cursor == len(self.history.input_cache)): | ||
self.history.input_cache[self.history_cursor] = currentBlock | ||||
Barry Wark
|
r1263 | self.history_cursor -= 1 | ||
return command | ||||
Barry Wark
|
r1281 | def get_history_next(self): | ||
Barry Wark
|
r1263 | """ Returns next history string and increment history cursor. | ||
""" | ||||
Barry Wark
|
r1281 | command = self.history.get_history_item(self.history_cursor+1) | ||
Barry Wark
|
r1263 | if command is not None: | ||
self.history_cursor += 1 | ||||
return command | ||||
### | ||||
# Subclasses probably want to override these methods... | ||||
### | ||||
Barry Wark
|
r1303 | def update_cell_prompt(self, result, blockID=None): | ||
Barry Wark
|
r1263 | """Subclass may override to update the input prompt for a block. | ||
Barry Wark
|
r1291 | Since this method will be called as a | ||
twisted.internet.defer.Deferred's callback, implementations should | ||||
return result when finished. | ||||
Barry Wark
|
r1283 | """ | ||
Barry Wark
|
r1263 | |||
return result | ||||
def render_result(self, result): | ||||
Barry Wark
|
r1291 | """Subclasses must override to render result. Since this method will | ||
be called as a twisted.internet.defer.Deferred's callback, | ||||
implementations should return result when finished. | ||||
""" | ||||
Barry Wark
|
r1263 | |||
return result | ||||
def render_error(self, failure): | ||||
Barry Wark
|
r1291 | """Subclasses must override to render the failure. Since this method | ||
will be called as a twisted.internet.defer.Deferred's callback, | ||||
Barry Wark
|
r1303 | implementations should return result when finished. | ||
""" | ||||
Barry Wark
|
r1263 | |||
return failure | ||||
Barry Wark <barrywarkatgmaildotcom>
|
r1315 | class AsyncFrontEndBase(FrontEndBase): | ||
Barry Wark
|
r1305 | """ | ||
Overrides FrontEndBase to wrap execute in a deferred result. | ||||
All callbacks are made as callbacks on the deferred result. | ||||
""" | ||||
Barry Wark
|
r1313 | implements(IFrontEnd) | ||
classProvides(IFrontEndFactory) | ||||
Barry Wark
|
r1305 | |||
Barry Wark
|
r1306 | def __init__(self, engine=None, history=None): | ||
assert(engine==None or IEngineCore.providedBy(engine)) | ||||
self.engine = IEngineCore(engine) | ||||
if history is None: | ||||
self.history = FrontEndHistory(input_cache=['']) | ||||
else: | ||||
self.history = history | ||||
Barry Wark
|
r1305 | def execute(self, block, blockID=None): | ||
"""Execute the block and return the deferred result. | ||||
Parameters: | ||||
block : {str, AST} | ||||
blockID : any | ||||
Caller may provide an ID to identify this block. | ||||
result['blockID'] := blockID | ||||
Result: | ||||
Deferred result of self.interpreter.execute | ||||
""" | ||||
if(not self.is_complete(block)): | ||||
return Failure(Exception("Block is not compilable")) | ||||
if(blockID == None): | ||||
blockID = uuid.uuid4() #random UUID | ||||
d = self.engine.execute(block) | ||||
d.addCallback(self._add_history, block=block) | ||||
d.addCallbacks(self._add_block_id_for_result, | ||||
errback=self._add_block_id_for_failure, | ||||
callbackArgs=(blockID,), | ||||
errbackArgs=(blockID,)) | ||||
d.addBoth(self.update_cell_prompt, blockID=blockID) | ||||
d.addCallbacks(self.render_result, | ||||
errback=self.render_error) | ||||
return d | ||||