From 498d9f5596113a9817b9a2fa4a14314c14bb7439 2011-03-14 13:30:39 From: Thomas Kluyver Date: 2011-03-14 13:30:39 Subject: [PATCH] Allow history to store multiple outputs for a single input line. --- diff --git a/IPython/core/displayhook.py b/IPython/core/displayhook.py index 46691e0..6207f41 100644 --- a/IPython/core/displayhook.py +++ b/IPython/core/displayhook.py @@ -275,16 +275,13 @@ class DisplayHook(Configurable): new_result = '_'+`self.prompt_count` to_main[new_result] = result self.shell.user_ns.update(to_main) - self.shell.user_ns['_oh'][self.prompt_count] = result + # This is a defaultdict of lists, so we can always append + self.shell.user_ns['_oh'][self.prompt_count].append(result) def log_output(self, format_dict): """Log the output.""" if self.shell.logger.log_output: self.shell.logger.log_write(format_dict['text/plain'], 'output') - # Write output to the database. Does nothing unless history - # output logging is enabled. - self.shell.history_manager.store_output(self.prompt_count, - format_dict['text/plain']) def finish_displayhook(self): """Finish up all displayhook activities.""" diff --git a/IPython/core/history.py b/IPython/core/history.py index 31e54f2..125d05d 100644 --- a/IPython/core/history.py +++ b/IPython/core/history.py @@ -14,10 +14,13 @@ from __future__ import print_function # Stdlib imports import datetime +import json import os import re import sqlite3 +from collections import defaultdict + # Our own packages from IPython.config.configurable import Configurable import IPython.utils.io @@ -45,7 +48,7 @@ class HistoryManager(Configurable): # A list of directories visited during session dir_hist = List() # A dict of output history, keyed with ints from the shell's execution count - output_hist = Dict() + output_hist = Instance(defaultdict) # String holding the path to the history file hist_file = Unicode() # The SQLite database @@ -94,6 +97,7 @@ class HistoryManager(Configurable): self.new_session() self._i00, self._i, self._ii, self._iii = '','','','' + self.output_hist = defaultdict(list) self._exit_commands = set(['Quit', 'quit', 'Exit', 'exit', '%Quit', '%quit', '%Exit', '%exit']) @@ -179,8 +183,10 @@ class HistoryManager(Configurable): toget = "history.%s, output_history.output" % toget cur = self.db.execute("SELECT session, line, %s FROM %s " %\ (toget, sqlfrom) + sql, params) - if output: # Regroup into 3-tuples - return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur) + if output: # Regroup into 3-tuples, and parse JSON + loads = lambda out: json.loads(out) if out else None + return ((ses, lin, (inp, loads(out))) \ + for ses, lin, inp, out in cur) return cur @@ -221,7 +227,8 @@ class HistoryManager(Configurable): for i in range(start, stop): if output: - line = (input_hist[i], repr(self.output_hist.get(i))) + output_item = [repr(x) for x in self.output_hist[i]] + line = (input_hist[i], output_item) else: line = input_hist[i] yield (0, i, line) @@ -324,9 +331,10 @@ class HistoryManager(Configurable): new_i : self._i00 } self.shell.user_ns.update(to_main) - def store_output(self, line_num, output): - if not self.db_log_output: + def store_output(self, line_num): + if (not self.db_log_output) or not self.output_hist[line_num]: return + output = json.dumps([repr(x) for x in self.output_hist[line_num]]) db_row = (self.session_number, line_num, output) if self.db_cache_size > 1: self.db_output_cache.append(db_row) @@ -524,7 +532,7 @@ def magic_history(self, parameter_s = ''): inline = "\n... ".join(inline.splitlines()) + "\n..." print(inline, file=outfile) if get_output and output: - print(output, file=outfile) + print("\n".join(output), file=outfile) if close_at_end: outfile.close() diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index eeaec60..dce5c39 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2119,8 +2119,11 @@ class InteractiveShell(Configurable, Magic): # Single-block input should behave like an interactive prompt if len(blocks) == 1: - # since we return here, we need to update the execution count out = self.run_source(blocks[0]) + # Write output to the database. Does nothing unless + # history output logging is enabled. + self.history_manager.store_output(self.execution_count) + # since we return here, we need to update the execution count self.execution_count += 1 return out @@ -2148,6 +2151,9 @@ class InteractiveShell(Configurable, Magic): # processed input in history self.run_source(ipy_cell, symbol='exec') + # Write output to the database. Does nothing unless + # history output logging is enabled. + self.history_manager.store_output(self.execution_count) # Each cell is a *single* input, regardless of how many lines it has self.execution_count += 1 diff --git a/IPython/core/tests/test_history.py b/IPython/core/tests/test_history.py index 108c957..5297711 100644 --- a/IPython/core/tests/test_history.py +++ b/IPython/core/tests/test_history.py @@ -38,7 +38,8 @@ def test_history(): ip.history_manager.db_log_output = True # Doesn't match the input, but we'll just check it's stored. - ip.history_manager.store_output(3, "spam") + ip.history_manager.output_hist[3].append("spam") + ip.history_manager.store_output(3) nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist) @@ -59,7 +60,7 @@ def test_history(): # Check get_hist_tail gothist = ip.history_manager.get_hist_tail(4, output=True) - expected = [(1, 3, (hist[-1], "spam")), + expected = [(1, 3, (hist[-1], [repr("spam")])), (2, 1, (newcmds[0], None)), (2, 2, (newcmds[1], None)), (2, 3, (newcmds[2], None)),] @@ -69,7 +70,7 @@ def test_history(): gothist = ip.history_manager.get_hist_search("*test*") nt.assert_equal(list(gothist), [(1,2,hist[1])] ) gothist = ip.history_manager.get_hist_search("b*", output=True) - nt.assert_equal(list(gothist), [(1,3,(hist[2],"spam"))] ) + nt.assert_equal(list(gothist), [(1,3,(hist[2],[repr("spam")]))] ) # Cross testing: check that magic %save can get previous session. testfilename = os.path.realpath(os.path.join(tmpdir, "test.py"))