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
18import sys
19
20from traitlets.config.configurable import Configurable
21from traitlets import List
22
23# This used to be defined here - it is imported for backwards compatibility
24from .display_functions import publish_display_data
25from .history import HistoryOutput
26
27import typing as t
28
29# -----------------------------------------------------------------------------
30# Main payload class
31# -----------------------------------------------------------------------------
32
33_sentinel = object()
34
35
36class DisplayPublisher(Configurable):
37 """A traited class that publishes display data to frontends.
38
39 Instances of this class are created by the main IPython object and should
40 be accessed there.
41 """
42
43 def __init__(self, shell=None, *args, **kwargs):
44 self.shell = shell
45 self._is_publishing = False
46 super().__init__(*args, **kwargs)
47
48 def _validate_data(self, data, metadata=None):
49 """Validate the display data.
50
51 Parameters
52 ----------
53 data : dict
54 The formata data dictionary.
55 metadata : dict
56 Any metadata for the data.
57 """
58
59 if not isinstance(data, dict):
60 raise TypeError("data must be a dict, got: %r" % data)
61 if metadata is not None:
62 if not isinstance(metadata, dict):
63 raise TypeError("metadata must be a dict, got: %r" % data)
64
65 # use * to indicate transient, update are keyword-only
66 def publish(
67 self,
68 data,
69 metadata=None,
70 source=_sentinel,
71 *,
72 transient=None,
73 update=False,
74 **kwargs,
75 ) -> None:
76 """Publish data and metadata to all frontends.
77
78 See the ``display_data`` message in the messaging documentation for
79 more details about this message type.
80
81 The following MIME types are currently implemented:
82
83 * text/plain
84 * text/html
85 * text/markdown
86 * text/latex
87 * application/json
88 * application/javascript
89 * image/png
90 * image/jpeg
91 * image/svg+xml
92
93 Parameters
94 ----------
95 data : dict
96 A dictionary having keys that are valid MIME types (like
97 'text/plain' or 'image/svg+xml') and values that are the data for
98 that MIME type. The data itself must be a JSON'able data
99 structure. Minimally all data should have the 'text/plain' data,
100 which can be displayed by all frontends. If more than the plain
101 text is given, it is up to the frontend to decide which
102 representation to use.
103 metadata : dict
104 A dictionary for metadata related to the data. This can contain
105 arbitrary key, value pairs that frontends can use to interpret
106 the data. Metadata specific to each mime-type can be specified
107 in the metadata dict with the same mime-type keys as
108 the data itself.
109 source : str, deprecated
110 Unused.
111 transient : dict, keyword-only
112 A dictionary for transient data.
113 Data in this dictionary should not be persisted as part of saving this output.
114 Examples include 'display_id'.
115 update : bool, keyword-only, default: False
116 If True, only update existing outputs with the same display_id,
117 rather than creating a new output.
118 """
119
120 if source is not _sentinel:
121 import warnings
122
123 warnings.warn(
124 "The 'source' parameter is deprecated since IPython 3.0 and will be ignored "
125 "(this warning is present since 9.0). `source` parameter will be removed in the future.",
126 DeprecationWarning,
127 stacklevel=2,
128 )
129
130 handlers: t.Dict = {}
131 if self.shell is not None:
132 handlers = getattr(self.shell, "mime_renderers", {})
133
134 outputs = self.shell.history_manager.outputs
135
136 outputs[self.shell.execution_count].append(
137 HistoryOutput(output_type="display_data", bundle=data)
138 )
139
140 for mime, handler in handlers.items():
141 if mime in data:
142 handler(data[mime], metadata.get(mime, None))
143 return
144
145 self._is_publishing = True
146 if "text/plain" in data:
147 print(data["text/plain"])
148 self._is_publishing = False
149
150 @property
151 def is_publishing(self):
152 return self._is_publishing
153
154 def clear_output(self, wait=False):
155 """Clear the output of the cell receiving output."""
156 print("\033[2K\r", end="")
157 sys.stdout.flush()
158 print("\033[2K\r", end="")
159 sys.stderr.flush()
160
161
162class CapturingDisplayPublisher(DisplayPublisher):
163 """A DisplayPublisher that stores"""
164
165 outputs: List = List()
166
167 def publish(
168 self, data, metadata=None, source=None, *, transient=None, update=False
169 ):
170 self.outputs.append(
171 {
172 "data": data,
173 "metadata": metadata,
174 "transient": transient,
175 "update": update,
176 }
177 )
178
179 def clear_output(self, wait=False):
180 super(CapturingDisplayPublisher, self).clear_output(wait)
181
182 # empty the list, *do not* reassign a new list
183 self.outputs.clear()