1# Copyright (c) Jupyter Development Team.
2# Distributed under the terms of the Modified BSD License.
3
4"""Output class.
5
6Represents a widget that can be used to display output within the widget area.
7"""
8
9import sys
10from functools import wraps
11
12from .domwidget import DOMWidget
13from .trait_types import TypedTuple
14from .widget import register
15from .._version import __jupyter_widgets_output_version__
16
17from traitlets import Unicode, Dict
18from IPython.core.interactiveshell import InteractiveShell
19from IPython.display import clear_output
20from IPython import get_ipython
21
22
23@register
24class Output(DOMWidget):
25    """Widget used as a context manager to display output.
26
27    This widget can capture and display stdout, stderr, and rich output.  To use
28    it, create an instance of it and display it.
29
30    You can then use the widget as a context manager: any output produced while in the
31    context will be captured and displayed in the widget instead of the standard output
32    area.
33
34    You can also use the .capture() method to decorate a function or a method. Any output
35    produced by the function will then go to the output widget. This is useful for
36    debugging widget callbacks, for example.
37
38    Example::
39        import ipywidgets as widgets
40        from IPython.display import display
41        out = widgets.Output()
42        display(out)
43
44        print('prints to output area')
45
46        with out:
47            print('prints to output widget')
48
49        @out.capture()
50        def func():
51            print('prints to output widget')
52    """
53    _view_name = Unicode('OutputView').tag(sync=True)
54    _model_name = Unicode('OutputModel').tag(sync=True)
55    _view_module = Unicode('@jupyter-widgets/output').tag(sync=True)
56    _model_module = Unicode('@jupyter-widgets/output').tag(sync=True)
57    _view_module_version = Unicode(__jupyter_widgets_output_version__).tag(sync=True)
58    _model_module_version = Unicode(__jupyter_widgets_output_version__).tag(sync=True)
59
60    msg_id = Unicode('', help="Parent message id of messages to capture").tag(sync=True)
61    outputs = TypedTuple(trait=Dict(), help="The output messages synced from the frontend.").tag(sync=True)
62
63    __counter = 0
64
65    def clear_output(self, *pargs, **kwargs):
66        """
67        Clear the content of the output widget.
68
69        Parameters
70        ----------
71
72        wait: bool
73            If True, wait to clear the output until new output is
74            available to replace it. Default: False
75        """
76        with self:
77            clear_output(*pargs, **kwargs)
78
79    # PY3: Force passing clear_output and clear_kwargs as kwargs
80    def capture(self, clear_output=False, *clear_args, **clear_kwargs):
81        """
82        Decorator to capture the stdout and stderr of a function.
83
84        Parameters
85        ----------
86
87        clear_output: bool
88            If True, clear the content of the output widget at every
89            new function call. Default: False
90
91        wait: bool
92            If True, wait to clear the output until new output is
93            available to replace it. This is only used if clear_output
94            is also True.
95            Default: False
96        """
97        def capture_decorator(func):
98            @wraps(func)
99            def inner(*args, **kwargs):
100                if clear_output:
101                    self.clear_output(*clear_args, **clear_kwargs)
102                with self:
103                    return func(*args, **kwargs)
104            return inner
105        return capture_decorator
106
107    def __enter__(self):
108        """Called upon entering output widget context manager."""
109        self._flush()
110        ip = get_ipython()
111        if ip and hasattr(ip, 'kernel') and hasattr(ip.kernel, '_parent_header'):
112            self.msg_id = ip.kernel._parent_header['header']['msg_id']
113            self.__counter += 1
114
115    def __exit__(self, etype, evalue, tb):
116        """Called upon exiting output widget context manager."""
117        ip = get_ipython()
118        if etype is not None:
119            if ip:
120                ip.showtraceback((etype, evalue, tb), tb_offset=0)
121        self._flush()
122        self.__counter -= 1
123        if self.__counter == 0:
124            self.msg_id = ''
125        # suppress exceptions when in IPython, since they are shown above,
126        # otherwise let someone else handle it
127        return True if ip else None
128
129    def _flush(self):
130        """Flush stdout and stderr buffers."""
131        sys.stdout.flush()
132        sys.stderr.flush()
133
134    def _append_stream_output(self, text, stream_name):
135        """Append a stream output."""
136        self.outputs += (
137            {'output_type': 'stream', 'name': stream_name, 'text': text},
138        )
139
140    def append_stdout(self, text):
141        """Append text to the stdout stream."""
142        self._append_stream_output(text, stream_name='stdout')
143
144    def append_stderr(self, text):
145        """Append text to the stderr stream."""
146        self._append_stream_output(text, stream_name='stderr')
147
148    def append_display_data(self, display_object):
149        """Append a display object as an output.
150
151        Parameters
152        ----------
153        display_object : IPython.core.display.DisplayObject
154            The object to display (e.g., an instance of
155            `IPython.display.Markdown` or `IPython.display.Image`).
156        """
157        fmt = InteractiveShell.instance().display_formatter.format
158        data, metadata = fmt(display_object)
159        self.outputs += (
160            {
161                'output_type': 'display_data',
162                'data': data,
163                'metadata': metadata
164            },
165        )
166