##// END OF EJS Templates
Backport PR #9851: Also capture execution results using sys.displayhook...
Matthias Bussonnier -
Show More
@@ -0,0 +1,3 b''
1 - The :cellmagic:`capture` magic can now capture the result of a cell (from an
2 expression on the last line), as well as printed and displayed output.
3 :ghpull:`9851`.
@@ -1,295 +1,309 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Displayhook for IPython.
3 3
4 4 This defines a callable class that IPython uses for `sys.displayhook`.
5 5 """
6 6
7 7 # Copyright (c) IPython Development Team.
8 8 # Distributed under the terms of the Modified BSD License.
9 9
10 10 from __future__ import print_function
11 11
12 12 import sys
13 13 import io as _io
14 14 import tokenize
15 15
16 16 from traitlets.config.configurable import Configurable
17 17 from IPython.utils.py3compat import builtin_mod, cast_unicode_py2
18 18 from traitlets import Instance, Float
19 19 from warnings import warn
20 20
21 21 # TODO: Move the various attributes (cache_size, [others now moved]). Some
22 22 # of these are also attributes of InteractiveShell. They should be on ONE object
23 23 # only and the other objects should ask that one object for their values.
24 24
25 25 class DisplayHook(Configurable):
26 26 """The custom IPython displayhook to replace sys.displayhook.
27 27
28 28 This class does many things, but the basic idea is that it is a callable
29 29 that gets called anytime user code returns a value.
30 30 """
31 31
32 32 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
33 33 allow_none=True)
34 34 exec_result = Instance('IPython.core.interactiveshell.ExecutionResult',
35 35 allow_none=True)
36 36 cull_fraction = Float(0.2)
37 37
38 38 def __init__(self, shell=None, cache_size=1000, **kwargs):
39 39 super(DisplayHook, self).__init__(shell=shell, **kwargs)
40 40 cache_size_min = 3
41 41 if cache_size <= 0:
42 42 self.do_full_cache = 0
43 43 cache_size = 0
44 44 elif cache_size < cache_size_min:
45 45 self.do_full_cache = 0
46 46 cache_size = 0
47 47 warn('caching was disabled (min value for cache size is %s).' %
48 48 cache_size_min,level=3)
49 49 else:
50 50 self.do_full_cache = 1
51 51
52 52 self.cache_size = cache_size
53 53
54 54 # we need a reference to the user-level namespace
55 55 self.shell = shell
56 56
57 57 self._,self.__,self.___ = '','',''
58 58
59 59 # these are deliberately global:
60 60 to_user_ns = {'_':self._,'__':self.__,'___':self.___}
61 61 self.shell.user_ns.update(to_user_ns)
62 62
63 63 @property
64 64 def prompt_count(self):
65 65 return self.shell.execution_count
66 66
67 67 #-------------------------------------------------------------------------
68 68 # Methods used in __call__. Override these methods to modify the behavior
69 69 # of the displayhook.
70 70 #-------------------------------------------------------------------------
71 71
72 72 def check_for_underscore(self):
73 73 """Check if the user has set the '_' variable by hand."""
74 74 # If something injected a '_' variable in __builtin__, delete
75 75 # ipython's automatic one so we don't clobber that. gettext() in
76 76 # particular uses _, so we need to stay away from it.
77 77 if '_' in builtin_mod.__dict__:
78 78 try:
79 79 del self.shell.user_ns['_']
80 80 except KeyError:
81 81 pass
82 82
83 83 def quiet(self):
84 84 """Should we silence the display hook because of ';'?"""
85 85 # do not print output if input ends in ';'
86 86
87 87 try:
88 88 cell = cast_unicode_py2(self.shell.history_manager.input_hist_parsed[-1])
89 89 except IndexError:
90 90 # some uses of ipshellembed may fail here
91 91 return False
92 92
93 93 sio = _io.StringIO(cell)
94 94 tokens = list(tokenize.generate_tokens(sio.readline))
95 95
96 96 for token in reversed(tokens):
97 97 if token[0] in (tokenize.ENDMARKER, tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT):
98 98 continue
99 99 if (token[0] == tokenize.OP) and (token[1] == ';'):
100 100 return True
101 101 else:
102 102 return False
103 103
104 104 def start_displayhook(self):
105 105 """Start the displayhook, initializing resources."""
106 106 pass
107 107
108 108 def write_output_prompt(self):
109 109 """Write the output prompt.
110 110
111 111 The default implementation simply writes the prompt to
112 112 ``sys.stdout``.
113 113 """
114 114 # Use write, not print which adds an extra space.
115 115 sys.stdout.write(self.shell.separate_out)
116 116 outprompt = 'Out[{}]: '.format(self.shell.execution_count)
117 117 if self.do_full_cache:
118 118 sys.stdout.write(outprompt)
119 119
120 120 def compute_format_data(self, result):
121 121 """Compute format data of the object to be displayed.
122 122
123 123 The format data is a generalization of the :func:`repr` of an object.
124 124 In the default implementation the format data is a :class:`dict` of
125 125 key value pair where the keys are valid MIME types and the values
126 126 are JSON'able data structure containing the raw data for that MIME
127 127 type. It is up to frontends to determine pick a MIME to to use and
128 128 display that data in an appropriate manner.
129 129
130 130 This method only computes the format data for the object and should
131 131 NOT actually print or write that to a stream.
132 132
133 133 Parameters
134 134 ----------
135 135 result : object
136 136 The Python object passed to the display hook, whose format will be
137 137 computed.
138 138
139 139 Returns
140 140 -------
141 141 (format_dict, md_dict) : dict
142 142 format_dict is a :class:`dict` whose keys are valid MIME types and values are
143 143 JSON'able raw data for that MIME type. It is recommended that
144 144 all return values of this should always include the "text/plain"
145 145 MIME type representation of the object.
146 146 md_dict is a :class:`dict` with the same MIME type keys
147 147 of metadata associated with each output.
148 148
149 149 """
150 150 return self.shell.display_formatter.format(result)
151 151
152 152 # This can be set to True by the write_output_prompt method in a subclass
153 153 prompt_end_newline = False
154 154
155 155 def write_format_data(self, format_dict, md_dict=None):
156 156 """Write the format data dict to the frontend.
157 157
158 158 This default version of this method simply writes the plain text
159 159 representation of the object to ``sys.stdout``. Subclasses should
160 160 override this method to send the entire `format_dict` to the
161 161 frontends.
162 162
163 163 Parameters
164 164 ----------
165 165 format_dict : dict
166 166 The format dict for the object passed to `sys.displayhook`.
167 167 md_dict : dict (optional)
168 168 The metadata dict to be associated with the display data.
169 169 """
170 170 if 'text/plain' not in format_dict:
171 171 # nothing to do
172 172 return
173 173 # We want to print because we want to always make sure we have a
174 174 # newline, even if all the prompt separators are ''. This is the
175 175 # standard IPython behavior.
176 176 result_repr = format_dict['text/plain']
177 177 if '\n' in result_repr:
178 178 # So that multi-line strings line up with the left column of
179 179 # the screen, instead of having the output prompt mess up
180 180 # their first line.
181 181 # We use the prompt template instead of the expanded prompt
182 182 # because the expansion may add ANSI escapes that will interfere
183 183 # with our ability to determine whether or not we should add
184 184 # a newline.
185 185 if not self.prompt_end_newline:
186 186 # But avoid extraneous empty lines.
187 187 result_repr = '\n' + result_repr
188 188
189 189 print(result_repr)
190 190
191 191 def update_user_ns(self, result):
192 192 """Update user_ns with various things like _, __, _1, etc."""
193 193
194 194 # Avoid recursive reference when displaying _oh/Out
195 195 if result is not self.shell.user_ns['_oh']:
196 196 if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache:
197 197 self.cull_cache()
198 198 # Don't overwrite '_' and friends if '_' is in __builtin__ (otherwise
199 199 # we cause buggy behavior for things like gettext).
200 200
201 201 if '_' not in builtin_mod.__dict__:
202 202 self.___ = self.__
203 203 self.__ = self._
204 204 self._ = result
205 205 self.shell.push({'_':self._,
206 206 '__':self.__,
207 207 '___':self.___}, interactive=False)
208 208
209 209 # hackish access to top-level namespace to create _1,_2... dynamically
210 210 to_main = {}
211 211 if self.do_full_cache:
212 212 new_result = '_'+repr(self.prompt_count)
213 213 to_main[new_result] = result
214 214 self.shell.push(to_main, interactive=False)
215 215 self.shell.user_ns['_oh'][self.prompt_count] = result
216 216
217 217 def fill_exec_result(self, result):
218 218 if self.exec_result is not None:
219 219 self.exec_result.result = result
220 220
221 221 def log_output(self, format_dict):
222 222 """Log the output."""
223 223 if 'text/plain' not in format_dict:
224 224 # nothing to do
225 225 return
226 226 if self.shell.logger.log_output:
227 227 self.shell.logger.log_write(format_dict['text/plain'], 'output')
228 228 self.shell.history_manager.output_hist_reprs[self.prompt_count] = \
229 229 format_dict['text/plain']
230 230
231 231 def finish_displayhook(self):
232 232 """Finish up all displayhook activities."""
233 233 sys.stdout.write(self.shell.separate_out2)
234 234 sys.stdout.flush()
235 235
236 236 def __call__(self, result=None):
237 237 """Printing with history cache management.
238 238
239 239 This is invoked everytime the interpreter needs to print, and is
240 240 activated by setting the variable sys.displayhook to it.
241 241 """
242 242 self.check_for_underscore()
243 243 if result is not None and not self.quiet():
244 244 self.start_displayhook()
245 245 self.write_output_prompt()
246 246 format_dict, md_dict = self.compute_format_data(result)
247 247 self.update_user_ns(result)
248 248 self.fill_exec_result(result)
249 249 if format_dict:
250 250 self.write_format_data(format_dict, md_dict)
251 251 self.log_output(format_dict)
252 252 self.finish_displayhook()
253 253
254 254 def cull_cache(self):
255 255 """Output cache is full, cull the oldest entries"""
256 256 oh = self.shell.user_ns.get('_oh', {})
257 257 sz = len(oh)
258 258 cull_count = max(int(sz * self.cull_fraction), 2)
259 259 warn('Output cache limit (currently {sz} entries) hit.\n'
260 260 'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count))
261 261
262 262 for i, n in enumerate(sorted(oh)):
263 263 if i >= cull_count:
264 264 break
265 265 self.shell.user_ns.pop('_%i' % n, None)
266 266 oh.pop(n, None)
267 267
268 268
269 269 def flush(self):
270 270 if not self.do_full_cache:
271 271 raise ValueError("You shouldn't have reached the cache flush "
272 272 "if full caching is not enabled!")
273 273 # delete auto-generated vars from global namespace
274 274
275 275 for n in range(1,self.prompt_count + 1):
276 276 key = '_'+repr(n)
277 277 try:
278 278 del self.shell.user_ns[key]
279 279 except: pass
280 280 # In some embedded circumstances, the user_ns doesn't have the
281 281 # '_oh' key set up.
282 282 oh = self.shell.user_ns.get('_oh', None)
283 283 if oh is not None:
284 284 oh.clear()
285 285
286 286 # Release our own references to objects:
287 287 self._, self.__, self.___ = '', '', ''
288 288
289 289 if '_' not in builtin_mod.__dict__:
290 290 self.shell.user_ns.update({'_':None,'__':None, '___':None})
291 291 import gc
292 292 # TODO: Is this really needed?
293 293 # IronPython blocks here forever
294 294 if sys.platform != "cli":
295 295 gc.collect()
296
297
298 class CapturingDisplayHook(object):
299 def __init__(self, shell, outputs=None):
300 self.shell = shell
301 if outputs is None:
302 outputs = []
303 self.outputs = outputs
304
305 def __call__(self, result=None):
306 if result is None:
307 return
308 format_dict, md_dict = self.shell.display_formatter.format(result)
309 self.outputs.append((format_dict, md_dict))
@@ -1,169 +1,173 b''
1 1 # encoding: utf-8
2 2 """IO capturing utilities."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from __future__ import print_function, absolute_import
8 8
9 9 import sys
10 10
11 11 from IPython.utils.py3compat import PY3
12 12
13 13 if PY3:
14 14 from io import StringIO
15 15 else:
16 16 from StringIO import StringIO
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Classes and functions
20 20 #-----------------------------------------------------------------------------
21 21
22 22
23 23 class RichOutput(object):
24 24 def __init__(self, data=None, metadata=None):
25 25 self.data = data or {}
26 26 self.metadata = metadata or {}
27 27
28 28 def display(self):
29 29 from IPython.display import publish_display_data
30 30 publish_display_data(data=self.data, metadata=self.metadata)
31 31
32 32 def _repr_mime_(self, mime):
33 33 if mime not in self.data:
34 34 return
35 35 data = self.data[mime]
36 36 if mime in self.metadata:
37 37 return data, self.metadata[mime]
38 38 else:
39 39 return data
40 40
41 41 def _repr_html_(self):
42 42 return self._repr_mime_("text/html")
43 43
44 44 def _repr_latex_(self):
45 45 return self._repr_mime_("text/latex")
46 46
47 47 def _repr_json_(self):
48 48 return self._repr_mime_("application/json")
49 49
50 50 def _repr_javascript_(self):
51 51 return self._repr_mime_("application/javascript")
52 52
53 53 def _repr_png_(self):
54 54 return self._repr_mime_("image/png")
55 55
56 56 def _repr_jpeg_(self):
57 57 return self._repr_mime_("image/jpeg")
58 58
59 59 def _repr_svg_(self):
60 60 return self._repr_mime_("image/svg+xml")
61 61
62 62
63 63 class CapturedIO(object):
64 64 """Simple object for containing captured stdout/err and rich display StringIO objects
65 65
66 66 Each instance `c` has three attributes:
67 67
68 68 - ``c.stdout`` : standard output as a string
69 69 - ``c.stderr`` : standard error as a string
70 70 - ``c.outputs``: a list of rich display outputs
71 71
72 72 Additionally, there's a ``c.show()`` method which will print all of the
73 73 above in the same order, and can be invoked simply via ``c()``.
74 74 """
75 75
76 76 def __init__(self, stdout, stderr, outputs=None):
77 77 self._stdout = stdout
78 78 self._stderr = stderr
79 79 if outputs is None:
80 80 outputs = []
81 81 self._outputs = outputs
82 82
83 83 def __str__(self):
84 84 return self.stdout
85 85
86 86 @property
87 87 def stdout(self):
88 88 "Captured standard output"
89 89 if not self._stdout:
90 90 return ''
91 91 return self._stdout.getvalue()
92 92
93 93 @property
94 94 def stderr(self):
95 95 "Captured standard error"
96 96 if not self._stderr:
97 97 return ''
98 98 return self._stderr.getvalue()
99 99
100 100 @property
101 101 def outputs(self):
102 102 """A list of the captured rich display outputs, if any.
103 103
104 104 If you have a CapturedIO object ``c``, these can be displayed in IPython
105 105 using::
106 106
107 107 from IPython.display import display
108 108 for o in c.outputs:
109 109 display(o)
110 110 """
111 111 return [ RichOutput(d, md) for d, md in self._outputs ]
112 112
113 113 def show(self):
114 114 """write my output to sys.stdout/err as appropriate"""
115 115 sys.stdout.write(self.stdout)
116 116 sys.stderr.write(self.stderr)
117 117 sys.stdout.flush()
118 118 sys.stderr.flush()
119 119 for data, metadata in self._outputs:
120 120 RichOutput(data, metadata).display()
121 121
122 122 __call__ = show
123 123
124 124
125 125 class capture_output(object):
126 126 """context manager for capturing stdout/err"""
127 127 stdout = True
128 128 stderr = True
129 129 display = True
130 130
131 131 def __init__(self, stdout=True, stderr=True, display=True):
132 132 self.stdout = stdout
133 133 self.stderr = stderr
134 134 self.display = display
135 135 self.shell = None
136 136
137 137 def __enter__(self):
138 138 from IPython.core.getipython import get_ipython
139 139 from IPython.core.displaypub import CapturingDisplayPublisher
140 from IPython.core.displayhook import CapturingDisplayHook
140 141
141 142 self.sys_stdout = sys.stdout
142 143 self.sys_stderr = sys.stderr
143 144
144 145 if self.display:
145 146 self.shell = get_ipython()
146 147 if self.shell is None:
147 148 self.save_display_pub = None
148 149 self.display = False
149 150
150 151 stdout = stderr = outputs = None
151 152 if self.stdout:
152 153 stdout = sys.stdout = StringIO()
153 154 if self.stderr:
154 155 stderr = sys.stderr = StringIO()
155 156 if self.display:
156 157 self.save_display_pub = self.shell.display_pub
157 158 self.shell.display_pub = CapturingDisplayPublisher()
158 159 outputs = self.shell.display_pub.outputs
159
160 self.save_display_hook = sys.displayhook
161 sys.displayhook = CapturingDisplayHook(shell=self.shell,
162 outputs=outputs)
160 163
161 164 return CapturedIO(stdout, stderr, outputs)
162 165
163 166 def __exit__(self, exc_type, exc_value, traceback):
164 167 sys.stdout = self.sys_stdout
165 168 sys.stderr = self.sys_stderr
166 169 if self.display and self.shell:
167 170 self.shell.display_pub = self.save_display_pub
171 sys.displayhook = self.save_display_hook
168 172
169 173
General Comments 0
You need to be logged in to leave comments. Login now