Skip to content

Commit a174c8e

Browse files
Darshan808ianthomas23
authored andcommitted
Backport PR ipython#1435: Store display outputs in history for %notebook magic
1 parent e792ab9 commit a174c8e

File tree

2 files changed

+60
-1
lines changed

2 files changed

+60
-1
lines changed

ipykernel/zmqshell.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,17 @@
3333
from IPython.utils.process import arg_split, system # type:ignore[attr-defined]
3434
from jupyter_client.session import Session, extract_header
3535
from jupyter_core.paths import jupyter_runtime_dir
36-
from traitlets import Any, CBool, CBytes, Dict, Instance, Type, default, observe
36+
from traitlets import Any, Bool, CBool, CBytes, Dict, Instance, Type, default, observe
3737

3838
from ipykernel import connect_qtconsole, get_connection_file, get_connection_info
3939
from ipykernel.displayhook import ZMQShellDisplayHook
4040
from ipykernel.jsonutil import encode_images, json_clean
4141

42+
try:
43+
from IPython.core.history import HistoryOutput
44+
except ImportError:
45+
HistoryOutput = None # type: ignore[assignment,misc]
46+
4247
# -----------------------------------------------------------------------------
4348
# Functions and classes
4449
# -----------------------------------------------------------------------------
@@ -52,6 +57,11 @@ class ZMQDisplayPublisher(DisplayPublisher):
5257
parent_header = Dict({})
5358
topic = CBytes(b"display_data")
5459

60+
store_display_history = Bool(
61+
False,
62+
help="If set to True, store display outputs in the history manager. Default is False.",
63+
).tag(config=True)
64+
5565
# thread_local:
5666
# An attribute used to ensure the correct output message
5767
# is processed. See ipykernel Issue 113 for a discussion.
@@ -100,6 +110,21 @@ def publish(
100110
update : bool, optional, keyword-only
101111
If True, send an update_display_data message instead of display_data.
102112
"""
113+
if (
114+
self.store_display_history
115+
and self.shell is not None
116+
and hasattr(self.shell, "history_manager")
117+
and HistoryOutput is not None
118+
):
119+
# Reference: github.com/ipython/ipython/pull/14998
120+
exec_count = self.shell.execution_count
121+
if getattr(self.shell.display_pub, "_in_post_execute", False):
122+
exec_count -= 1
123+
outputs = getattr(self.shell.history_manager, "outputs", None)
124+
if outputs is not None:
125+
outputs.setdefault(exec_count, []).append(
126+
HistoryOutput(output_type="display_data", bundle=data)
127+
)
103128
self._flush_streams()
104129
if metadata is None:
105130
metadata = {}

tests/test_zmq_shell.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
ZMQInteractiveShell,
2323
)
2424

25+
try:
26+
from IPython.core.history import HistoryOutput
27+
except ImportError:
28+
HistoryOutput = None # type: ignore[assignment,misc]
29+
2530

2631
class NoReturnDisplayHook:
2732
"""
@@ -209,6 +214,35 @@ def test_unregister_hook(self):
209214
second = self.disp_pub.unregister_hook(hook)
210215
assert not bool(second)
211216

217+
@unittest.skipIf(HistoryOutput is None, "HistoryOutput not available")
218+
def test_display_stored_in_history(self):
219+
"""
220+
Test that published display data gets stored in shell history
221+
for %notebook magic support, and not stored when disabled.
222+
"""
223+
for enable in [False, True]:
224+
# Mock shell with history manager
225+
mock_shell = MagicMock()
226+
mock_shell.execution_count = 1
227+
mock_shell.history_manager.outputs = dict()
228+
mock_shell.display_pub._in_post_execute = False
229+
230+
self.disp_pub.shell = mock_shell
231+
self.disp_pub.store_display_history = enable
232+
233+
data = {"text/plain": "test output"}
234+
self.disp_pub.publish(data)
235+
236+
if enable:
237+
# Check that output was stored in history
238+
stored_outputs = mock_shell.history_manager.outputs[1]
239+
assert len(stored_outputs) == 1
240+
assert stored_outputs[0].output_type == "display_data"
241+
assert stored_outputs[0].bundle == data
242+
else:
243+
# Should not store anything in history
244+
assert mock_shell.history_manager.outputs == {}
245+
212246

213247
def test_magics(tmp_path):
214248
context = zmq.Context()

0 commit comments

Comments
 (0)