frontendbase.py
343 lines
| 10.1 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 | """ | ||
Bernardo B. Marques
|
r4872 | frontendbase provides an interface and base class for GUI frontends for | ||
Barry Wark
|
r1291 | IPython.kernel/IPython.kernel.core. | ||
Barry Wark
|
r1263 | |||
Bernardo B. Marques
|
r4872 | Frontend implementations will likely want to subclass FrontEndBase. | ||
Barry Wark
|
r1277 | |||
Author: Barry Wark | ||||
Barry Wark
|
r1263 | """ | ||
__docformat__ = "restructuredtext en" | ||||
#------------------------------------------------------------------------------- | ||||
Matthias BUSSONNIER
|
r5390 | # Copyright (C) 2008-2011 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 | ||||
Gael Varoquaux
|
r1710 | import codeop | ||
Thomas Spura
|
r5807 | import uuid | ||
Fernando Perez
|
r1706 | |||
Barry Wark
|
r1263 | |||
Brian Granger
|
r1558 | from IPython.frontend.zopeinterface import ( | ||
Bernardo B. Marques
|
r4872 | Interface, | ||
Attribute, | ||||
Brian Granger
|
r1558 | ) | ||
Barry Wark
|
r1263 | from IPython.kernel.core.history import FrontEndHistory | ||
from IPython.kernel.core.util import Bunch | ||||
############################################################################## | ||||
# 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]: ' | ||||
############################################################################## | ||||
gvaroquaux
|
r1455 | # Interface definitions | ||
############################################################################## | ||||
Barry Wark
|
r1263 | |||
Barry Wark
|
r1313 | class IFrontEndFactory(Interface): | ||
Barry Wark
|
r1277 | """Factory interface for frontends.""" | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1277 | def __call__(engine=None, history=None): | ||
Barry Wark
|
r1263 | """ | ||
Parameters: | ||||
interpreter : IPython.kernel.engineservice.IEngineCore | ||||
""" | ||||
Bernardo B. Marques
|
r4872 | |||
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""" | ||
Bernardo B. Marques
|
r4872 | |||
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.") | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1303 | def update_cell_prompt(result, blockID=None): | ||
Bernardo B. Marques
|
r4872 | """Subclass may override to update the input prompt for a block. | ||
gvaroquaux
|
r1455 | |||
In asynchronous frontends, this method will be called as a | ||||
twisted.internet.defer.Deferred's callback/errback. | ||||
Implementations should thus return result when finished. | ||||
Bernardo B. Marques
|
r4872 | |||
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 | """ | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1277 | pass | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1303 | def render_result(result): | ||
Barry Wark
|
r1291 | """Render the result of an execute call. Implementors may choose the | ||
method of rendering. | ||||
Bernardo B. Marques
|
r4872 | For example, a notebook-style frontend might render a Chaco plot | ||
Barry Wark
|
r1291 | inline. | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | Parameters: | ||
result : dict (result of IEngineBase.execute ) | ||||
Barry Wark
|
r1303 | blockID = result['blockID'] | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | Result: | ||
Output of frontend rendering | ||||
""" | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1277 | pass | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1303 | def render_error(failure): | ||
Bernardo B. Marques
|
r4872 | """Subclasses must override to render the failure. | ||
gvaroquaux
|
r1455 | In asynchronous frontend, since this method will be called as a | ||
twisted.internet.defer.Deferred's callback. Implementations | ||||
should thus return result when finished. | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1303 | blockID = failure.blockID | ||
Barry Wark
|
r1291 | """ | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1277 | pass | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1304 | def input_prompt(number=''): | ||
Bernardo B. Marques
|
r4872 | """Returns the input prompt by subsituting into | ||
Barry Wark
|
r1291 | self.input_prompt_template | ||
""" | ||||
Barry Wark
|
r1277 | pass | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1304 | def output_prompt(number=''): | ||
Bernardo B. Marques
|
r4872 | """Returns the output prompt by subsituting into | ||
Barry Wark
|
r1291 | self.output_prompt_template | ||
""" | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1277 | pass | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1282 | def continuation_prompt(): | ||
Bernardo B. Marques
|
r4872 | """Returns the continuation prompt by subsituting into | ||
Barry Wark
|
r1291 | self.continuation_prompt_template | ||
""" | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1277 | pass | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1277 | def is_complete(block): | ||
"""Returns True if block is complete, False otherwise.""" | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1277 | pass | ||
Bernardo B. Marques
|
r4872 | |||
Gael Varoquaux
|
r1358 | def get_history_previous(current_block): | ||
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 | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1281 | def get_history_next(): | ||
Barry Wark
|
r1277 | """Returns the next block in the history.""" | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1277 | pass | ||
Bernardo B. Marques
|
r4872 | |||
gvaroquaux
|
r1455 | def complete(self, line): | ||
"""Returns the list of possible completions, and the completed | ||||
line. | ||||
The input argument is the full line to be completed. This method | ||||
returns both the line completed as much as possible, and the list | ||||
of further possible completions (full words). | ||||
""" | ||||
pass | ||||
############################################################################## | ||||
Bernardo B. Marques
|
r4872 | # Base class for all the frontends. | ||
gvaroquaux
|
r1455 | ############################################################################## | ||
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 | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | Some issues (due to possibly unavailable engine): | ||
- How do we get the current cell number for the engine? | ||||
- How do we handle completions? | ||||
""" | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | history_cursor = 0 | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | input_prompt_template = string.Template(rc.prompt_in1) | ||
output_prompt_template = string.Template(rc.prompt_out) | ||||
continuation_prompt_template = string.Template(rc.prompt_in2) | ||||
Bernardo B. Marques
|
r4872 | |||
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 | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1304 | def input_prompt(self, number=''): | ||
Barry Wark
|
r1263 | """Returns the current input prompt | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | It would be great to use ipython1.core.prompts.Prompt1 here | ||
""" | ||||
Barry Wark
|
r1303 | return self.input_prompt_template.safe_substitute({'number':number}) | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1282 | def continuation_prompt(self): | ||
Barry Wark
|
r1263 | """Returns the current continuation prompt""" | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | return self.continuation_prompt_template.safe_substitute() | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1304 | def output_prompt(self, number=''): | ||
Barry Wark
|
r1263 | """Returns the output prompt for result""" | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1303 | return self.output_prompt_template.safe_substitute({'number':number}) | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | def is_complete(self, block): | ||
"""Determine if block is complete. | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | Parameters | ||
block : string | ||||
Bernardo B. Marques
|
r4872 | |||
Result | ||||
Barry Wark
|
r1263 | True if block can be sent to the engine without compile errors. | ||
False otherwise. | ||||
""" | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | try: | ||
Gael Varoquaux
|
r1710 | is_complete = codeop.compile_command(block.rstrip() + '\n\n', | ||
Bernardo B. Marques
|
r4872 | "<string>", "exec") | ||
Barry Wark
|
r1263 | except: | ||
return False | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1278 | lines = block.split('\n') | ||
Bernardo B. Marques
|
r4872 | return ((is_complete is not None) | ||
Gael Varoquaux
|
r1710 | and (len(lines)==1 or str(lines[-1])=='')) | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | def execute(self, block, blockID=None): | ||
Barry Wark
|
r1306 | """Execute the block and return the result. | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | Parameters: | ||
block : {str, AST} | ||||
blockID : any | ||||
Bernardo B. Marques
|
r4872 | Caller may provide an ID to identify this block. | ||
Barry Wark
|
r1291 | result['blockID'] := blockID | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | Result: | ||
Deferred result of self.interpreter.execute | ||||
""" | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1306 | if(not self.is_complete(block)): | ||
raise Exception("Block is not compilable") | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1306 | if(blockID == None): | ||
Thomas Spura
|
r5807 | blockID = uuid.uuid4() | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1306 | 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) | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1306 | return result | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1303 | def _add_block_id_for_result(self, result, blockID): | ||
Bernardo B. Marques
|
r4872 | """Add the blockID to result or failure. Unfortunatley, we have to | ||
Barry Wark
|
r1291 | treat failures differently than result dicts. | ||
Barry Wark
|
r1283 | """ | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1303 | result['blockID'] = blockID | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | return result | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1303 | def _add_block_id_for_failure(self, failure, blockID): | ||
"""_add_block_id_for_failure""" | ||||
failure.blockID = blockID | ||||
return failure | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1279 | def _add_history(self, result, block=None): | ||
"""Add block to the history""" | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1279 | assert(block != None) | ||
self.history.add_items([block]) | ||||
self.history_cursor += 1 | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1279 | return result | ||
Bernardo B. Marques
|
r4872 | |||
Gael Varoquaux
|
r1358 | def get_history_previous(self, current_block): | ||
Barry Wark
|
r1263 | """ Returns previous history string and decrement history cursor. | ||
""" | ||||
command = self.history.get_history_item(self.history_cursor - 1) | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | if command is not None: | ||
Gael Varoquaux
|
r1358 | if(self.history_cursor+1 == len(self.history.input_cache)): | ||
self.history.input_cache[self.history_cursor] = current_block | ||||
Barry Wark
|
r1263 | self.history_cursor -= 1 | ||
return command | ||||
Bernardo B. Marques
|
r4872 | |||
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) | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | if command is not None: | ||
self.history_cursor += 1 | ||||
return command | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | ### | ||
# Subclasses probably want to override these methods... | ||||
### | ||||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1303 | def update_cell_prompt(self, result, blockID=None): | ||
Bernardo B. Marques
|
r4872 | """Subclass may override to update the input prompt for a block. | ||
gvaroquaux
|
r1455 | |||
This method only really makes sens in asyncrhonous frontend. | ||||
Bernardo B. Marques
|
r4872 | Since this method will be called as a | ||
twisted.internet.defer.Deferred's callback, implementations should | ||||
Barry Wark
|
r1291 | return result when finished. | ||
Barry Wark
|
r1283 | """ | ||
Bernardo B. Marques
|
r4872 | |||
Gael Varoquaux
|
r1458 | raise NotImplementedError | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | def render_result(self, result): | ||
Bernardo B. Marques
|
r4872 | """Subclasses must override to render result. | ||
gvaroquaux
|
r1455 | In asynchronous frontends, this method will be called as a | ||
twisted.internet.defer.Deferred's callback. Implementations | ||||
should thus return result when finished. | ||||
Barry Wark
|
r1291 | """ | ||
Bernardo B. Marques
|
r4872 | |||
Gael Varoquaux
|
r1458 | raise NotImplementedError | ||
Bernardo B. Marques
|
r4872 | |||
Barry Wark
|
r1263 | def render_error(self, failure): | ||
Bernardo B. Marques
|
r4872 | """Subclasses must override to render the failure. | ||
gvaroquaux
|
r1455 | In asynchronous frontends, this method will be called as a | ||
twisted.internet.defer.Deferred's callback. Implementations | ||||
should thus return result when finished. | ||||
Barry Wark
|
r1303 | """ | ||
Bernardo B. Marques
|
r4872 | |||
Gael Varoquaux
|
r1458 | raise NotImplementedError | ||
Barry Wark
|
r1305 | |||