1"""An interface for publishing rich data to frontends.
2
3There are two components of the display system:
4
5* Display formatters, which take a Python object and compute the
6  representation of the object in various formats (text, HTML, SVG, etc.).
7* The display publisher that is used to send the representation data to the
8  various frontends.
9
10This module defines the logic display publishing. The display publisher uses
11the ``display_data`` message type that is defined in the IPython messaging
12spec.
13"""
14
15# Copyright (c) IPython Development Team.
16# Distributed under the terms of the Modified BSD License.
17
18
19import sys
20
21from traitlets.config.configurable import Configurable
22from traitlets import List
23
24# This used to be defined here - it is imported for backwards compatibility
25from .display import publish_display_data
26
27#-----------------------------------------------------------------------------
28# Main payload class
29#-----------------------------------------------------------------------------
30
31
32class DisplayPublisher(Configurable):
33    """A traited class that publishes display data to frontends.
34
35    Instances of this class are created by the main IPython object and should
36    be accessed there.
37    """
38
39    def __init__(self, shell=None, *args, **kwargs):
40        self.shell = shell
41        super().__init__(*args, **kwargs)
42
43    def _validate_data(self, data, metadata=None):
44        """Validate the display data.
45
46        Parameters
47        ----------
48        data : dict
49            The formata data dictionary.
50        metadata : dict
51            Any metadata for the data.
52        """
53
54        if not isinstance(data, dict):
55            raise TypeError('data must be a dict, got: %r' % data)
56        if metadata is not None:
57            if not isinstance(metadata, dict):
58                raise TypeError('metadata must be a dict, got: %r' % data)
59
60    # use * to indicate transient, update are keyword-only
61    def publish(self, data, metadata=None, source=None, *, transient=None, update=False, **kwargs) -> None:
62        """Publish data and metadata to all frontends.
63
64        See the ``display_data`` message in the messaging documentation for
65        more details about this message type.
66
67        The following MIME types are currently implemented:
68
69        * text/plain
70        * text/html
71        * text/markdown
72        * text/latex
73        * application/json
74        * application/javascript
75        * image/png
76        * image/jpeg
77        * image/svg+xml
78
79        Parameters
80        ----------
81        data : dict
82            A dictionary having keys that are valid MIME types (like
83            'text/plain' or 'image/svg+xml') and values that are the data for
84            that MIME type. The data itself must be a JSON'able data
85            structure. Minimally all data should have the 'text/plain' data,
86            which can be displayed by all frontends. If more than the plain
87            text is given, it is up to the frontend to decide which
88            representation to use.
89        metadata : dict
90            A dictionary for metadata related to the data. This can contain
91            arbitrary key, value pairs that frontends can use to interpret
92            the data.  Metadata specific to each mime-type can be specified
93            in the metadata dict with the same mime-type keys as
94            the data itself.
95        source : str, deprecated
96            Unused.
97        transient: dict, keyword-only
98            A dictionary for transient data.
99            Data in this dictionary should not be persisted as part of saving this output.
100            Examples include 'display_id'.
101        update: bool, keyword-only, default: False
102            If True, only update existing outputs with the same display_id,
103            rather than creating a new output.
104        """
105
106        handlers = {}
107        if self.shell is not None:
108            handlers = getattr(self.shell, 'mime_renderers', {})
109
110        for mime, handler in handlers.items():
111            if mime in data:
112                handler(data[mime], metadata.get(mime, None))
113                return
114
115        if 'text/plain' in data:
116            print(data['text/plain'])
117
118    def clear_output(self, wait=False):
119        """Clear the output of the cell receiving output."""
120        print('\033[2K\r', end='')
121        sys.stdout.flush()
122        print('\033[2K\r', end='')
123        sys.stderr.flush()
124
125
126class CapturingDisplayPublisher(DisplayPublisher):
127    """A DisplayPublisher that stores"""
128    outputs = List()
129
130    def publish(self, data, metadata=None, source=None, *, transient=None, update=False):
131        self.outputs.append({'data':data, 'metadata':metadata,
132                             'transient':transient, 'update':update})
133
134    def clear_output(self, wait=False):
135        super(CapturingDisplayPublisher, self).clear_output(wait)
136
137        # empty the list, *do not* reassign a new list
138        self.outputs.clear()
139