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