displayhook.py
325 lines
| 12.3 KiB
| text/x-python
|
PythonLexer
Brian Granger
|
r2781 | # -*- coding: utf-8 -*- | ||
"""Displayhook for IPython. | ||||
Brian Granger
|
r3278 | This defines a callable class that IPython uses for `sys.displayhook`. | ||
Brian Granger
|
r2781 | """ | ||
MinRK
|
r18026 | # Copyright (c) IPython Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||||
Thomas Kluyver
|
r23129 | import builtins as builtin_mod | ||
Pawel Jasinski
|
r8500 | import sys | ||
Matthias Bussonnier
|
r21426 | import io as _io | ||
import tokenize | ||||
Pawel Jasinski
|
r8500 | |||
Min RK
|
r21253 | from traitlets.config.configurable import Configurable | ||
from traitlets import Instance, Float | ||||
Pierre Gerold
|
r22092 | from warnings import warn | ||
Brian Granger
|
r2781 | |||
Thomas Kluyver
|
r5495 | # TODO: Move the various attributes (cache_size, [others now moved]). Some | ||
# of these are also attributes of InteractiveShell. They should be on ONE object | ||||
# only and the other objects should ask that one object for their values. | ||||
Brian Granger
|
r2781 | |||
class DisplayHook(Configurable): | ||||
"""The custom IPython displayhook to replace sys.displayhook. | ||||
This class does many things, but the basic idea is that it is a callable | ||||
that gets called anytime user code returns a value. | ||||
""" | ||||
Sylvain Corlay
|
r20940 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', | ||
allow_none=True) | ||||
Thomas Kluyver
|
r19630 | exec_result = Instance('IPython.core.interactiveshell.ExecutionResult', | ||
allow_none=True) | ||||
Min RK
|
r18841 | cull_fraction = Float(0.2) | ||
Fernando Perez
|
r3077 | |||
MinRK
|
r11064 | def __init__(self, shell=None, cache_size=1000, **kwargs): | ||
super(DisplayHook, self).__init__(shell=shell, **kwargs) | ||||
Brian Granger
|
r2781 | cache_size_min = 3 | ||
if cache_size <= 0: | ||||
self.do_full_cache = 0 | ||||
cache_size = 0 | ||||
elif cache_size < cache_size_min: | ||||
self.do_full_cache = 0 | ||||
cache_size = 0 | ||||
warn('caching was disabled (min value for cache size is %s).' % | ||||
Srinivas Reddy Thatiparthy
|
r22969 | cache_size_min,stacklevel=3) | ||
Brian Granger
|
r2781 | else: | ||
self.do_full_cache = 1 | ||||
self.cache_size = cache_size | ||||
# we need a reference to the user-level namespace | ||||
self.shell = shell | ||||
Thomas Kluyver
|
r5495 | |||
Brian Granger
|
r2781 | self._,self.__,self.___ = '','','' | ||
# these are deliberately global: | ||||
to_user_ns = {'_':self._,'__':self.__,'___':self.___} | ||||
self.shell.user_ns.update(to_user_ns) | ||||
Fernando Perez
|
r3077 | @property | ||
def prompt_count(self): | ||||
return self.shell.execution_count | ||||
Brian Granger
|
r2781 | #------------------------------------------------------------------------- | ||
# Methods used in __call__. Override these methods to modify the behavior | ||||
# of the displayhook. | ||||
#------------------------------------------------------------------------- | ||||
def check_for_underscore(self): | ||||
"""Check if the user has set the '_' variable by hand.""" | ||||
# If something injected a '_' variable in __builtin__, delete | ||||
# ipython's automatic one so we don't clobber that. gettext() in | ||||
# particular uses _, so we need to stay away from it. | ||||
Thomas Kluyver
|
r13351 | if '_' in builtin_mod.__dict__: | ||
Brian Granger
|
r2781 | try: | ||
Matthias Bussonnier
|
r22838 | user_value = self.shell.user_ns['_'] | ||
if user_value is not self._: | ||||
return | ||||
Brian Granger
|
r2781 | del self.shell.user_ns['_'] | ||
except KeyError: | ||||
pass | ||||
Brian Granger
|
r2786 | def quiet(self): | ||
Brian Granger
|
r2781 | """Should we silence the display hook because of ';'?""" | ||
# do not print output if input ends in ';' | ||||
Matthias Bussonnier
|
r21426 | |||
thethomask
|
r21423 | try: | ||
Srinivas Reddy Thatiparthy
|
r23669 | cell = self.shell.history_manager.input_hist_parsed[-1] | ||
thethomask
|
r21423 | except IndexError: | ||
# some uses of ipshellembed may fail here | ||||
return False | ||||
Matthias Bussonnier
|
r21426 | |||
sio = _io.StringIO(cell) | ||||
tokens = list(tokenize.generate_tokens(sio.readline)) | ||||
for token in reversed(tokens): | ||||
Sebastian Bank
|
r22076 | if token[0] in (tokenize.ENDMARKER, tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT): | ||
Matthias Bussonnier
|
r21426 | continue | ||
if (token[0] == tokenize.OP) and (token[1] == ';'): | ||||
return True | ||||
else: | ||||
return False | ||||
Brian Granger
|
r2781 | |||
Brian Granger
|
r2786 | def start_displayhook(self): | ||
"""Start the displayhook, initializing resources.""" | ||||
pass | ||||
Brian Granger
|
r2781 | def write_output_prompt(self): | ||
Brian Granger
|
r3278 | """Write the output prompt. | ||
The default implementation simply writes the prompt to | ||||
Min RK
|
r22742 | ``sys.stdout``. | ||
Brian Granger
|
r3278 | """ | ||
Brian Granger
|
r2781 | # Use write, not print which adds an extra space. | ||
Thomas Kluyver
|
r22192 | sys.stdout.write(self.shell.separate_out) | ||
Thomas Kluyver
|
r22422 | outprompt = 'Out[{}]: '.format(self.shell.execution_count) | ||
Brian Granger
|
r2781 | if self.do_full_cache: | ||
Thomas Kluyver
|
r22192 | sys.stdout.write(outprompt) | ||
Brian Granger
|
r2781 | |||
Brian Granger
|
r3278 | def compute_format_data(self, result): | ||
"""Compute format data of the object to be displayed. | ||||
Brian Granger
|
r2781 | |||
Brian Granger
|
r3278 | The format data is a generalization of the :func:`repr` of an object. | ||
In the default implementation the format data is a :class:`dict` of | ||||
key value pair where the keys are valid MIME types and the values | ||||
are JSON'able data structure containing the raw data for that MIME | ||||
type. It is up to frontends to determine pick a MIME to to use and | ||||
display that data in an appropriate manner. | ||||
Brian Granger
|
r3286 | This method only computes the format data for the object and should | ||
NOT actually print or write that to a stream. | ||||
Brian Granger
|
r3278 | |||
Parameters | ||||
---------- | ||||
result : object | ||||
Brian Granger
|
r3286 | The Python object passed to the display hook, whose format will be | ||
Brian Granger
|
r3278 | computed. | ||
Returns | ||||
------- | ||||
MinRK
|
r10446 | (format_dict, md_dict) : dict | ||
format_dict is a :class:`dict` whose keys are valid MIME types and values are | ||||
Brian Granger
|
r3278 | JSON'able raw data for that MIME type. It is recommended that | ||
all return values of this should always include the "text/plain" | ||||
MIME type representation of the object. | ||||
MinRK
|
r10446 | md_dict is a :class:`dict` with the same MIME type keys | ||
of metadata associated with each output. | ||||
Brian Granger
|
r2781 | """ | ||
Brian Granger
|
r3288 | return self.shell.display_formatter.format(result) | ||
Robert Kern
|
r3215 | |||
Thomas Kluyver
|
r22423 | # This can be set to True by the write_output_prompt method in a subclass | ||
prompt_end_newline = False | ||||
Matthias Bussonnier
|
r25164 | def write_format_data(self, format_dict, md_dict=None) -> None: | ||
Brian Granger
|
r3278 | """Write the format data dict to the frontend. | ||
Brian Granger
|
r2781 | |||
Brian Granger
|
r3278 | This default version of this method simply writes the plain text | ||
Min RK
|
r22742 | representation of the object to ``sys.stdout``. Subclasses should | ||
Brian Granger
|
r3278 | override this method to send the entire `format_dict` to the | ||
frontends. | ||||
Parameters | ||||
---------- | ||||
format_dict : dict | ||||
The format dict for the object passed to `sys.displayhook`. | ||||
MinRK
|
r10446 | md_dict : dict (optional) | ||
The metadata dict to be associated with the display data. | ||||
Brian Granger
|
r3278 | """ | ||
MinRK
|
r18026 | if 'text/plain' not in format_dict: | ||
# nothing to do | ||||
return | ||||
Bernardo B. Marques
|
r4872 | # We want to print because we want to always make sure we have a | ||
Brian Granger
|
r2781 | # newline, even if all the prompt separators are ''. This is the | ||
# standard IPython behavior. | ||||
Brian Granger
|
r3278 | result_repr = format_dict['text/plain'] | ||
Robert Kern
|
r3224 | if '\n' in result_repr: | ||
# So that multi-line strings line up with the left column of | ||||
# the screen, instead of having the output prompt mess up | ||||
# their first line. | ||||
Thomas Kluyver
|
r5495 | # We use the prompt template instead of the expanded prompt | ||
Robert Kern
|
r3224 | # because the expansion may add ANSI escapes that will interfere | ||
# with our ability to determine whether or not we should add | ||||
# a newline. | ||||
Thomas Kluyver
|
r22423 | if not self.prompt_end_newline: | ||
Robert Kern
|
r3224 | # But avoid extraneous empty lines. | ||
result_repr = '\n' + result_repr | ||||
Piers Titus van der Torren
|
r25023 | try: | ||
print(result_repr) | ||||
except UnicodeEncodeError: | ||||
# If a character is not supported by the terminal encoding replace | ||||
# it with its \u or \x representation | ||||
print(result_repr.encode(sys.stdout.encoding,'backslashreplace').decode(sys.stdout.encoding)) | ||||
Brian Granger
|
r2781 | |||
def update_user_ns(self, result): | ||||
"""Update user_ns with various things like _, __, _1, etc.""" | ||||
# Avoid recursive reference when displaying _oh/Out | ||||
if result is not self.shell.user_ns['_oh']: | ||||
if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache: | ||||
Min RK
|
r18841 | self.cull_cache() | ||
Fernando Perez
|
r3297 | |||
Matthias Bussonnier
|
r22838 | # Don't overwrite '_' and friends if '_' is in __builtin__ | ||
# (otherwise we cause buggy behavior for things like gettext). and | ||||
# do not overwrite _, __ or ___ if one of these has been assigned | ||||
# by the user. | ||||
update_unders = True | ||||
for unders in ['_'*i for i in range(1,4)]: | ||||
if not unders in self.shell.user_ns: | ||||
continue | ||||
if getattr(self, unders) is not self.shell.user_ns.get(unders): | ||||
update_unders = False | ||||
self.___ = self.__ | ||||
self.__ = self._ | ||||
self._ = result | ||||
if ('_' not in builtin_mod.__dict__) and (update_unders): | ||||
Thomas Kluyver
|
r5458 | self.shell.push({'_':self._, | ||
'__':self.__, | ||||
'___':self.___}, interactive=False) | ||||
Brian Granger
|
r2781 | |||
# hackish access to top-level namespace to create _1,_2... dynamically | ||||
to_main = {} | ||||
if self.do_full_cache: | ||||
Matthias Bussonnier
|
r22838 | new_result = '_%s' % self.prompt_count | ||
Brian Granger
|
r2781 | to_main[new_result] = result | ||
Thomas Kluyver
|
r5458 | self.shell.push(to_main, interactive=False) | ||
Thomas Kluyver
|
r3417 | self.shell.user_ns['_oh'][self.prompt_count] = result | ||
Brian Granger
|
r2781 | |||
Thomas Kluyver
|
r19630 | def fill_exec_result(self, result): | ||
if self.exec_result is not None: | ||||
self.exec_result.result = result | ||||
Thomas Kluyver
|
r3392 | def log_output(self, format_dict): | ||
Brian Granger
|
r2781 | """Log the output.""" | ||
MinRK
|
r18026 | if 'text/plain' not in format_dict: | ||
# nothing to do | ||||
return | ||||
Brian Granger
|
r2781 | if self.shell.logger.log_output: | ||
Thomas Kluyver
|
r3392 | self.shell.logger.log_write(format_dict['text/plain'], 'output') | ||
Thomas Kluyver
|
r3741 | self.shell.history_manager.output_hist_reprs[self.prompt_count] = \ | ||
format_dict['text/plain'] | ||||
Brian Granger
|
r2781 | |||
def finish_displayhook(self): | ||||
"""Finish up all displayhook activities.""" | ||||
Thomas Kluyver
|
r22192 | sys.stdout.write(self.shell.separate_out2) | ||
sys.stdout.flush() | ||||
Brian Granger
|
r2781 | |||
def __call__(self, result=None): | ||||
"""Printing with history cache management. | ||||
Bernardo B. Marques
|
r4872 | |||
Min ho Kim
|
r25146 | This is invoked every time the interpreter needs to print, and is | ||
Brian Granger
|
r2781 | activated by setting the variable sys.displayhook to it. | ||
""" | ||||
self.check_for_underscore() | ||||
Brian Granger
|
r2786 | if result is not None and not self.quiet(): | ||
MinRK
|
r14795 | self.start_displayhook() | ||
self.write_output_prompt() | ||||
format_dict, md_dict = self.compute_format_data(result) | ||||
self.update_user_ns(result) | ||||
Thomas Kluyver
|
r19630 | self.fill_exec_result(result) | ||
Min RK
|
r19381 | if format_dict: | ||
self.write_format_data(format_dict, md_dict) | ||||
self.log_output(format_dict) | ||||
MinRK
|
r14795 | self.finish_displayhook() | ||
Brian Granger
|
r2781 | |||
Min RK
|
r18841 | def cull_cache(self): | ||
"""Output cache is full, cull the oldest entries""" | ||||
oh = self.shell.user_ns.get('_oh', {}) | ||||
sz = len(oh) | ||||
cull_count = max(int(sz * self.cull_fraction), 2) | ||||
warn('Output cache limit (currently {sz} entries) hit.\n' | ||||
'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count)) | ||||
for i, n in enumerate(sorted(oh)): | ||||
if i >= cull_count: | ||||
break | ||||
self.shell.user_ns.pop('_%i' % n, None) | ||||
oh.pop(n, None) | ||||
Brian Granger
|
r2781 | def flush(self): | ||
if not self.do_full_cache: | ||||
Bradley M. Froehle
|
r7843 | raise ValueError("You shouldn't have reached the cache flush " | ||
"if full caching is not enabled!") | ||||
Brian Granger
|
r2781 | # delete auto-generated vars from global namespace | ||
Bernardo B. Marques
|
r4872 | |||
Brian Granger
|
r2781 | for n in range(1,self.prompt_count + 1): | ||
Matthias BUSSONNIER
|
r7815 | key = '_'+repr(n) | ||
Brian Granger
|
r2781 | try: | ||
del self.shell.user_ns[key] | ||||
except: pass | ||||
Robert Kern
|
r6269 | # In some embedded circumstances, the user_ns doesn't have the | ||
# '_oh' key set up. | ||||
oh = self.shell.user_ns.get('_oh', None) | ||||
if oh is not None: | ||||
oh.clear() | ||||
Bernardo B. Marques
|
r4872 | |||
Thomas Kluyver
|
r3521 | # Release our own references to objects: | ||
self._, self.__, self.___ = '', '', '' | ||||
Bernardo B. Marques
|
r4872 | |||
Thomas Kluyver
|
r13351 | if '_' not in builtin_mod.__dict__: | ||
Matthias Bussonnier
|
r25041 | self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___}) | ||
Brian Granger
|
r2781 | import gc | ||
Brian Granger
|
r3278 | # TODO: Is this really needed? | ||
Pawel Jasinski
|
r8500 | # IronPython blocks here forever | ||
if sys.platform != "cli": | ||||
gc.collect() | ||||
Thomas Kluyver
|
r22774 | |||
class CapturingDisplayHook(object): | ||||
def __init__(self, shell, outputs=None): | ||||
self.shell = shell | ||||
if outputs is None: | ||||
outputs = [] | ||||
self.outputs = outputs | ||||
def __call__(self, result=None): | ||||
if result is None: | ||||
return | ||||
format_dict, md_dict = self.shell.display_formatter.format(result) | ||||
Matthew Seal
|
r24296 | self.outputs.append({ 'data': format_dict, 'metadata': md_dict }) | ||