Show More
@@ -0,0 +1,359 b'' | |||||
|
1 | { | |||
|
2 | "cells": [ | |||
|
3 | { | |||
|
4 | "cell_type": "markdown", | |||
|
5 | "metadata": {}, | |||
|
6 | "source": [ | |||
|
7 | "# Updatable Displays\n", | |||
|
8 | "\n", | |||
|
9 | "Note: This feature requires notebook >= 5.0 or JupyterLab, and \n", | |||
|
10 | "\n", | |||
|
11 | "\n", | |||
|
12 | "IPython 6 implements a new API as part of the Jupyter Protocol version 5.1 for easily updating displays.\n", | |||
|
13 | "\n", | |||
|
14 | "When you display something, you can now pass a `display_id` argument to attach an id to that output.\n", | |||
|
15 | "\n", | |||
|
16 | "Any future display with the same ID will also update other displays that had the same ID.\n", | |||
|
17 | "\n", | |||
|
18 | "`display` with a `display_id` will return a `DisplayHandle`\n", | |||
|
19 | "object, which gives you easy access to update the output:" | |||
|
20 | ] | |||
|
21 | }, | |||
|
22 | { | |||
|
23 | "cell_type": "code", | |||
|
24 | "execution_count": 10, | |||
|
25 | "metadata": { | |||
|
26 | "collapsed": true | |||
|
27 | }, | |||
|
28 | "outputs": [], | |||
|
29 | "source": [ | |||
|
30 | "from IPython.display import display, update_display" | |||
|
31 | ] | |||
|
32 | }, | |||
|
33 | { | |||
|
34 | "cell_type": "code", | |||
|
35 | "execution_count": 13, | |||
|
36 | "metadata": {}, | |||
|
37 | "outputs": [ | |||
|
38 | { | |||
|
39 | "data": { | |||
|
40 | "text/plain": [ | |||
|
41 | "'z'" | |||
|
42 | ] | |||
|
43 | }, | |||
|
44 | "metadata": {}, | |||
|
45 | "output_type": "display_data" | |||
|
46 | }, | |||
|
47 | { | |||
|
48 | "data": { | |||
|
49 | "text/plain": [ | |||
|
50 | "<DisplayHandle display_id=update-me>" | |||
|
51 | ] | |||
|
52 | }, | |||
|
53 | "execution_count": 13, | |||
|
54 | "metadata": {}, | |||
|
55 | "output_type": "execute_result" | |||
|
56 | } | |||
|
57 | ], | |||
|
58 | "source": [ | |||
|
59 | "handle = display('x', display_id='update-me')\n", | |||
|
60 | "handle" | |||
|
61 | ] | |||
|
62 | }, | |||
|
63 | { | |||
|
64 | "cell_type": "markdown", | |||
|
65 | "metadata": {}, | |||
|
66 | "source": [ | |||
|
67 | "When we call `handle.display('y')`, we get a new display of 'y',\n", | |||
|
68 | "but in addition to that, we updated the previous display." | |||
|
69 | ] | |||
|
70 | }, | |||
|
71 | { | |||
|
72 | "cell_type": "code", | |||
|
73 | "execution_count": 14, | |||
|
74 | "metadata": {}, | |||
|
75 | "outputs": [ | |||
|
76 | { | |||
|
77 | "data": { | |||
|
78 | "text/plain": [ | |||
|
79 | "'z'" | |||
|
80 | ] | |||
|
81 | }, | |||
|
82 | "metadata": {}, | |||
|
83 | "output_type": "display_data" | |||
|
84 | } | |||
|
85 | ], | |||
|
86 | "source": [ | |||
|
87 | "handle.display('y')" | |||
|
88 | ] | |||
|
89 | }, | |||
|
90 | { | |||
|
91 | "cell_type": "markdown", | |||
|
92 | "metadata": {}, | |||
|
93 | "source": [ | |||
|
94 | "We can also *just* update the existing displays,\n", | |||
|
95 | "without creating a new display:" | |||
|
96 | ] | |||
|
97 | }, | |||
|
98 | { | |||
|
99 | "cell_type": "code", | |||
|
100 | "execution_count": 15, | |||
|
101 | "metadata": { | |||
|
102 | "collapsed": true | |||
|
103 | }, | |||
|
104 | "outputs": [], | |||
|
105 | "source": [ | |||
|
106 | "handle.update('z')" | |||
|
107 | ] | |||
|
108 | }, | |||
|
109 | { | |||
|
110 | "cell_type": "markdown", | |||
|
111 | "metadata": {}, | |||
|
112 | "source": [ | |||
|
113 | "You don't have to generate display_ids yourself,\n", | |||
|
114 | "if you specify `display_id=True`, then a unique ID will be assigned:" | |||
|
115 | ] | |||
|
116 | }, | |||
|
117 | { | |||
|
118 | "cell_type": "code", | |||
|
119 | "execution_count": 16, | |||
|
120 | "metadata": {}, | |||
|
121 | "outputs": [ | |||
|
122 | { | |||
|
123 | "data": { | |||
|
124 | "text/plain": [ | |||
|
125 | "'hello'" | |||
|
126 | ] | |||
|
127 | }, | |||
|
128 | "metadata": {}, | |||
|
129 | "output_type": "display_data" | |||
|
130 | }, | |||
|
131 | { | |||
|
132 | "data": { | |||
|
133 | "text/plain": [ | |||
|
134 | "<DisplayHandle display_id=07fc47b2ef652ccb70addeee3eb0981a>" | |||
|
135 | ] | |||
|
136 | }, | |||
|
137 | "execution_count": 16, | |||
|
138 | "metadata": {}, | |||
|
139 | "output_type": "execute_result" | |||
|
140 | } | |||
|
141 | ], | |||
|
142 | "source": [ | |||
|
143 | "handle = display(\"hello\", display_id=True)\n", | |||
|
144 | "handle" | |||
|
145 | ] | |||
|
146 | }, | |||
|
147 | { | |||
|
148 | "cell_type": "markdown", | |||
|
149 | "metadata": {}, | |||
|
150 | "source": [ | |||
|
151 | "Calling `handle.display(obj)` is the same as calling `display(obj, handle.display_id)`,\n", | |||
|
152 | "so you don't need to use the handle objects if you don't want to:" | |||
|
153 | ] | |||
|
154 | }, | |||
|
155 | { | |||
|
156 | "cell_type": "code", | |||
|
157 | "execution_count": 17, | |||
|
158 | "metadata": {}, | |||
|
159 | "outputs": [ | |||
|
160 | { | |||
|
161 | "data": { | |||
|
162 | "text/plain": [ | |||
|
163 | "'z'" | |||
|
164 | ] | |||
|
165 | }, | |||
|
166 | "metadata": {}, | |||
|
167 | "output_type": "display_data" | |||
|
168 | } | |||
|
169 | ], | |||
|
170 | "source": [ | |||
|
171 | "display('x', display_id='here');" | |||
|
172 | ] | |||
|
173 | }, | |||
|
174 | { | |||
|
175 | "cell_type": "code", | |||
|
176 | "execution_count": 18, | |||
|
177 | "metadata": {}, | |||
|
178 | "outputs": [ | |||
|
179 | { | |||
|
180 | "data": { | |||
|
181 | "text/plain": [ | |||
|
182 | "'z'" | |||
|
183 | ] | |||
|
184 | }, | |||
|
185 | "metadata": {}, | |||
|
186 | "output_type": "display_data" | |||
|
187 | } | |||
|
188 | ], | |||
|
189 | "source": [ | |||
|
190 | "display('y', display_id='here');" | |||
|
191 | ] | |||
|
192 | }, | |||
|
193 | { | |||
|
194 | "cell_type": "markdown", | |||
|
195 | "metadata": {}, | |||
|
196 | "source": [ | |||
|
197 | "And just like `display`, there is now `update_display`,\n", | |||
|
198 | "which is what `DisplayHandle.update` calls:" | |||
|
199 | ] | |||
|
200 | }, | |||
|
201 | { | |||
|
202 | "cell_type": "code", | |||
|
203 | "execution_count": 19, | |||
|
204 | "metadata": { | |||
|
205 | "collapsed": true | |||
|
206 | }, | |||
|
207 | "outputs": [], | |||
|
208 | "source": [ | |||
|
209 | "update_display('z', display_id='here')" | |||
|
210 | ] | |||
|
211 | }, | |||
|
212 | { | |||
|
213 | "cell_type": "markdown", | |||
|
214 | "metadata": {}, | |||
|
215 | "source": [ | |||
|
216 | "## More detailed example\n", | |||
|
217 | "\n", | |||
|
218 | "One of the motivating use cases for this is simple progress bars.\n", | |||
|
219 | "\n", | |||
|
220 | "Here is an example ProgressBar using these APIs:" | |||
|
221 | ] | |||
|
222 | }, | |||
|
223 | { | |||
|
224 | "cell_type": "code", | |||
|
225 | "execution_count": 35, | |||
|
226 | "metadata": {}, | |||
|
227 | "outputs": [ | |||
|
228 | { | |||
|
229 | "data": { | |||
|
230 | "text/html": [ | |||
|
231 | "<progress\n", | |||
|
232 | " value=10\n", | |||
|
233 | " max=10\n", | |||
|
234 | " style=\"width: 60ex\"/>\n", | |||
|
235 | " 10 / 10\n", | |||
|
236 | " " | |||
|
237 | ], | |||
|
238 | "text/plain": [ | |||
|
239 | "[============================================================] 10/10" | |||
|
240 | ] | |||
|
241 | }, | |||
|
242 | "metadata": {}, | |||
|
243 | "output_type": "display_data" | |||
|
244 | } | |||
|
245 | ], | |||
|
246 | "source": [ | |||
|
247 | "import os\n", | |||
|
248 | "from binascii import hexlify\n", | |||
|
249 | "\n", | |||
|
250 | "class ProgressBar(object):\n", | |||
|
251 | " def __init__(self, capacity):\n", | |||
|
252 | " self.progress = 0\n", | |||
|
253 | " self.capacity = capacity\n", | |||
|
254 | " self.html_width = '60ex'\n", | |||
|
255 | " self.text_width = 60\n", | |||
|
256 | " self._display_id = hexlify(os.urandom(8)).decode('ascii')\n", | |||
|
257 | " \n", | |||
|
258 | " def __repr__(self):\n", | |||
|
259 | " fraction = self.progress / self.capacity\n", | |||
|
260 | " filled = '=' * int(fraction * self.text_width)\n", | |||
|
261 | " rest = ' ' * (self.text_width - len(filled))\n", | |||
|
262 | " return '[{}{}] {}/{}'.format(\n", | |||
|
263 | " filled, rest,\n", | |||
|
264 | " self.progress, self.capacity,\n", | |||
|
265 | " )\n", | |||
|
266 | " \n", | |||
|
267 | " def _repr_html_(self):\n", | |||
|
268 | " return \"\"\"<progress\n", | |||
|
269 | " value={progress}\n", | |||
|
270 | " max={capacity}\n", | |||
|
271 | " style=\"width: {width}\"/>\n", | |||
|
272 | " {progress} / {capacity}\n", | |||
|
273 | " \"\"\".format(\n", | |||
|
274 | " progress=self.progress,\n", | |||
|
275 | " capacity=self.capacity,\n", | |||
|
276 | " width=self.html_width,\n", | |||
|
277 | " )\n", | |||
|
278 | " \n", | |||
|
279 | " def display(self):\n", | |||
|
280 | " display(self, display_id=self._display_id)\n", | |||
|
281 | " \n", | |||
|
282 | " def update(self):\n", | |||
|
283 | " update_display(self, display_id=self._display_id)\n", | |||
|
284 | "\n", | |||
|
285 | "bar = ProgressBar(10)\n", | |||
|
286 | "bar.display()" | |||
|
287 | ] | |||
|
288 | }, | |||
|
289 | { | |||
|
290 | "cell_type": "markdown", | |||
|
291 | "metadata": {}, | |||
|
292 | "source": [ | |||
|
293 | "And the ProgressBar has `.display` and `.update` methods:" | |||
|
294 | ] | |||
|
295 | }, | |||
|
296 | { | |||
|
297 | "cell_type": "code", | |||
|
298 | "execution_count": 36, | |||
|
299 | "metadata": {}, | |||
|
300 | "outputs": [ | |||
|
301 | { | |||
|
302 | "data": { | |||
|
303 | "text/html": [ | |||
|
304 | "<progress\n", | |||
|
305 | " value=10\n", | |||
|
306 | " max=10\n", | |||
|
307 | " style=\"width: 60ex\"/>\n", | |||
|
308 | " 10 / 10\n", | |||
|
309 | " " | |||
|
310 | ], | |||
|
311 | "text/plain": [ | |||
|
312 | "[============================================================] 10/10" | |||
|
313 | ] | |||
|
314 | }, | |||
|
315 | "metadata": {}, | |||
|
316 | "output_type": "display_data" | |||
|
317 | } | |||
|
318 | ], | |||
|
319 | "source": [ | |||
|
320 | "import time\n", | |||
|
321 | "\n", | |||
|
322 | "bar.display()\n", | |||
|
323 | "\n", | |||
|
324 | "for i in range(11):\n", | |||
|
325 | " bar.progress = i\n", | |||
|
326 | " bar.update()\n", | |||
|
327 | " time.sleep(0.25)" | |||
|
328 | ] | |||
|
329 | }, | |||
|
330 | { | |||
|
331 | "cell_type": "markdown", | |||
|
332 | "metadata": {}, | |||
|
333 | "source": [ | |||
|
334 | "We would encourage any updatable-display objects that track their own display_ids to follow-suit with `.display()` and `.update()` or `.update_display()` methods." | |||
|
335 | ] | |||
|
336 | } | |||
|
337 | ], | |||
|
338 | "metadata": { | |||
|
339 | "kernelspec": { | |||
|
340 | "display_name": "Python 3", | |||
|
341 | "language": "python", | |||
|
342 | "name": "python3" | |||
|
343 | }, | |||
|
344 | "language_info": { | |||
|
345 | "codemirror_mode": { | |||
|
346 | "name": "ipython", | |||
|
347 | "version": 3 | |||
|
348 | }, | |||
|
349 | "file_extension": ".py", | |||
|
350 | "mimetype": "text/x-python", | |||
|
351 | "name": "python", | |||
|
352 | "nbconvert_exporter": "python", | |||
|
353 | "pygments_lexer": "ipython3", | |||
|
354 | "version": "3.5.1" | |||
|
355 | } | |||
|
356 | }, | |||
|
357 | "nbformat": 4, | |||
|
358 | "nbformat_minor": 1 | |||
|
359 | } |
@@ -11,6 +11,7 b' try:' | |||||
11 | except ImportError: |
|
11 | except ImportError: | |
12 | from base64 import encodestring as base64_encode |
|
12 | from base64 import encodestring as base64_encode | |
13 |
|
13 | |||
|
14 | from binascii import b2a_hex | |||
14 | import json |
|
15 | import json | |
15 | import mimetypes |
|
16 | import mimetypes | |
16 | import os |
|
17 | import os | |
@@ -27,7 +28,7 b" __all__ = ['display', 'display_pretty', 'display_html', 'display_markdown'," | |||||
27 | 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject', |
|
28 | 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject', | |
28 | 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'JSON', 'Javascript', |
|
29 | 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'JSON', 'Javascript', | |
29 | 'Image', 'clear_output', 'set_matplotlib_formats', 'set_matplotlib_close', |
|
30 | 'Image', 'clear_output', 'set_matplotlib_formats', 'set_matplotlib_close', | |
30 | 'publish_display_data'] |
|
31 | 'publish_display_data', 'update_display', 'DisplayHandle'] | |
31 |
|
32 | |||
32 | #----------------------------------------------------------------------------- |
|
33 | #----------------------------------------------------------------------------- | |
33 | # utility functions |
|
34 | # utility functions | |
@@ -79,7 +80,8 b' def _display_mimetype(mimetype, objs, raw=False, metadata=None):' | |||||
79 | # Main functions |
|
80 | # Main functions | |
80 | #----------------------------------------------------------------------------- |
|
81 | #----------------------------------------------------------------------------- | |
81 |
|
82 | |||
82 | def publish_display_data(data, metadata=None, source=None): |
|
83 | # use * to indicate transient is keyword-only | |
|
84 | def publish_display_data(data, metadata=None, source=None, *, transient=None, **kwargs): | |||
83 | """Publish data and metadata to all frontends. |
|
85 | """Publish data and metadata to all frontends. | |
84 |
|
86 | |||
85 | See the ``display_data`` message in the messaging documentation for |
|
87 | See the ``display_data`` message in the messaging documentation for | |
@@ -114,14 +116,32 b' def publish_display_data(data, metadata=None, source=None):' | |||||
114 | to specify metadata about particular representations. |
|
116 | to specify metadata about particular representations. | |
115 | source : str, deprecated |
|
117 | source : str, deprecated | |
116 | Unused. |
|
118 | Unused. | |
|
119 | transient : dict, keyword-only | |||
|
120 | A dictionary of transient data, such as display_id. | |||
117 | """ |
|
121 | """ | |
118 | from IPython.core.interactiveshell import InteractiveShell |
|
122 | from IPython.core.interactiveshell import InteractiveShell | |
119 | InteractiveShell.instance().display_pub.publish( |
|
123 | ||
|
124 | display_pub = InteractiveShell.instance().display_pub | |||
|
125 | ||||
|
126 | # only pass transient if supplied, | |||
|
127 | # to avoid errors with older ipykernel. | |||
|
128 | # TODO: We could check for ipykernel version and provide a detailed upgrade message. | |||
|
129 | if transient: | |||
|
130 | kwargs['transient'] = transient | |||
|
131 | ||||
|
132 | display_pub.publish( | |||
120 | data=data, |
|
133 | data=data, | |
121 | metadata=metadata, |
|
134 | metadata=metadata, | |
|
135 | **kwargs | |||
122 | ) |
|
136 | ) | |
123 |
|
137 | |||
124 | def display(*objs, **kwargs): |
|
138 | ||
|
139 | def _new_id(): | |||
|
140 | """Generate a new random text id with urandom""" | |||
|
141 | return b2a_hex(os.urandom(16)).decode('ascii') | |||
|
142 | ||||
|
143 | ||||
|
144 | def display(*objs, include=None, exclude=None, metadata=None, transient=None, display_id=None, **kwargs): | |||
125 | """Display a Python object in all frontends. |
|
145 | """Display a Python object in all frontends. | |
126 |
|
146 | |||
127 | By default all representations will be computed and sent to the frontends. |
|
147 | By default all representations will be computed and sent to the frontends. | |
@@ -146,11 +166,34 b' def display(*objs, **kwargs):' | |||||
146 | A dictionary of metadata to associate with the output. |
|
166 | A dictionary of metadata to associate with the output. | |
147 | mime-type keys in this dictionary will be associated with the individual |
|
167 | mime-type keys in this dictionary will be associated with the individual | |
148 | representation formats, if they exist. |
|
168 | representation formats, if they exist. | |
|
169 | transient : dict, optional | |||
|
170 | A dictionary of transient data to associate with the output. | |||
|
171 | Data in this dict should not be persisted to files (e.g. notebooks). | |||
|
172 | display_id : str, optional | |||
|
173 | Set an id for the display. | |||
|
174 | This id can be used for updating this display area later via update_display. | |||
|
175 | If given as True, generate a new display_id | |||
|
176 | kwargs: additional keyword-args, optional | |||
|
177 | Additional keyword-arguments are passed through to the display publisher. | |||
|
178 | ||||
|
179 | Returns | |||
|
180 | ------- | |||
|
181 | ||||
|
182 | handle: DisplayHandle | |||
|
183 | Returns a handle on updatable displays, if display_id is given. | |||
|
184 | Returns None if no display_id is given (default). | |||
149 | """ |
|
185 | """ | |
150 |
raw = kwargs. |
|
186 | raw = kwargs.pop('raw', False) | |
151 | include = kwargs.get('include') |
|
187 | if transient is None: | |
152 | exclude = kwargs.get('exclude') |
|
188 | transient = {} | |
153 | metadata = kwargs.get('metadata') |
|
189 | if display_id: | |
|
190 | if display_id == True: | |||
|
191 | display_id = _new_id() | |||
|
192 | transient['display_id'] = display_id | |||
|
193 | if kwargs.get('update') and 'display_id' not in transient: | |||
|
194 | raise TypeError('display_id required for update_display') | |||
|
195 | if transient: | |||
|
196 | kwargs['transient'] = transient | |||
154 |
|
197 | |||
155 | from IPython.core.interactiveshell import InteractiveShell |
|
198 | from IPython.core.interactiveshell import InteractiveShell | |
156 |
|
199 | |||
@@ -159,7 +202,7 b' def display(*objs, **kwargs):' | |||||
159 |
|
202 | |||
160 | for obj in objs: |
|
203 | for obj in objs: | |
161 | if raw: |
|
204 | if raw: | |
162 | publish_display_data(data=obj, metadata=metadata) |
|
205 | publish_display_data(data=obj, metadata=metadata, **kwargs) | |
163 | else: |
|
206 | else: | |
164 | format_dict, md_dict = format(obj, include=include, exclude=exclude) |
|
207 | format_dict, md_dict = format(obj, include=include, exclude=exclude) | |
165 | if not format_dict: |
|
208 | if not format_dict: | |
@@ -168,7 +211,69 b' def display(*objs, **kwargs):' | |||||
168 | if metadata: |
|
211 | if metadata: | |
169 | # kwarg-specified metadata gets precedence |
|
212 | # kwarg-specified metadata gets precedence | |
170 | _merge(md_dict, metadata) |
|
213 | _merge(md_dict, metadata) | |
171 | publish_display_data(data=format_dict, metadata=md_dict) |
|
214 | publish_display_data(data=format_dict, metadata=md_dict, **kwargs) | |
|
215 | if display_id: | |||
|
216 | return DisplayHandle(display_id) | |||
|
217 | ||||
|
218 | ||||
|
219 | # use * for keyword-only display_id arg | |||
|
220 | def update_display(obj, *, display_id, **kwargs): | |||
|
221 | """Update an existing display by id | |||
|
222 | ||||
|
223 | Parameters | |||
|
224 | ---------- | |||
|
225 | ||||
|
226 | obj: | |||
|
227 | The object with which to update the display | |||
|
228 | display_id: keyword-only | |||
|
229 | The id of the display to update | |||
|
230 | """ | |||
|
231 | kwargs['update'] = True | |||
|
232 | display(obj, display_id=display_id, **kwargs) | |||
|
233 | ||||
|
234 | ||||
|
235 | class DisplayHandle(object): | |||
|
236 | """A handle on an updatable display | |||
|
237 | ||||
|
238 | Call .update(obj) to display a new object. | |||
|
239 | ||||
|
240 | Call .display(obj) to add a new instance of this display, | |||
|
241 | and update existing instances. | |||
|
242 | """ | |||
|
243 | ||||
|
244 | def __init__(self, display_id=None): | |||
|
245 | if display_id is None: | |||
|
246 | display_id = _new_id() | |||
|
247 | self.display_id = display_id | |||
|
248 | ||||
|
249 | def __repr__(self): | |||
|
250 | return "<%s display_id=%s>" % (self.__class__.__name__, self.display_id) | |||
|
251 | ||||
|
252 | def display(self, obj, **kwargs): | |||
|
253 | """Make a new display with my id, updating existing instances. | |||
|
254 | ||||
|
255 | Parameters | |||
|
256 | ---------- | |||
|
257 | ||||
|
258 | obj: | |||
|
259 | object to display | |||
|
260 | **kwargs: | |||
|
261 | additional keyword arguments passed to display | |||
|
262 | """ | |||
|
263 | display(obj, display_id=self.display_id, **kwargs) | |||
|
264 | ||||
|
265 | def update(self, obj, **kwargs): | |||
|
266 | """Update existing displays with my id | |||
|
267 | ||||
|
268 | Parameters | |||
|
269 | ---------- | |||
|
270 | ||||
|
271 | obj: | |||
|
272 | object to display | |||
|
273 | **kwargs: | |||
|
274 | additional keyword arguments passed to update_display | |||
|
275 | """ | |||
|
276 | update_display(obj, display_id=self.display_id, **kwargs) | |||
172 |
|
277 | |||
173 |
|
278 | |||
174 | def display_pretty(*objs, **kwargs): |
|
279 | def display_pretty(*objs, **kwargs): |
@@ -53,7 +53,8 b' class DisplayPublisher(Configurable):' | |||||
53 | if not isinstance(metadata, dict): |
|
53 | if not isinstance(metadata, dict): | |
54 | raise TypeError('metadata must be a dict, got: %r' % data) |
|
54 | raise TypeError('metadata must be a dict, got: %r' % data) | |
55 |
|
55 | |||
56 | def publish(self, data, metadata=None, source=None): |
|
56 | # use * to indicate transient, update are keyword-only | |
|
57 | def publish(self, data, metadata=None, source=None, *, transient=None, update=False, **kwargs): | |||
57 | """Publish data and metadata to all frontends. |
|
58 | """Publish data and metadata to all frontends. | |
58 |
|
59 | |||
59 | See the ``display_data`` message in the messaging documentation for |
|
60 | See the ``display_data`` message in the messaging documentation for | |
@@ -89,6 +90,13 b' class DisplayPublisher(Configurable):' | |||||
89 | the data itself. |
|
90 | the data itself. | |
90 | source : str, deprecated |
|
91 | source : str, deprecated | |
91 | Unused. |
|
92 | Unused. | |
|
93 | transient: dict, keyword-only | |||
|
94 | A dictionary for transient data. | |||
|
95 | Data in this dictionary should not be persisted as part of saving this output. | |||
|
96 | Examples include 'display_id'. | |||
|
97 | update: bool, keyword-only, default: False | |||
|
98 | If True, only update existing outputs with the same display_id, | |||
|
99 | rather than creating a new output. | |||
92 | """ |
|
100 | """ | |
93 |
|
101 | |||
94 | # The default is to simply write the plain text data using sys.stdout. |
|
102 | # The default is to simply write the plain text data using sys.stdout. |
@@ -5,6 +5,8 b' import json' | |||||
5 | import os |
|
5 | import os | |
6 | import warnings |
|
6 | import warnings | |
7 |
|
7 | |||
|
8 | from unittest import mock | |||
|
9 | ||||
8 | import nose.tools as nt |
|
10 | import nose.tools as nt | |
9 |
|
11 | |||
10 | from IPython.core import display |
|
12 | from IPython.core import display | |
@@ -189,3 +191,114 b' def test_video_embedding():' | |||||
189 | html = v._repr_html_() |
|
191 | html = v._repr_html_() | |
190 | nt.assert_in('src="data:video/xyz;base64,YWJj"',html) |
|
192 | nt.assert_in('src="data:video/xyz;base64,YWJj"',html) | |
191 |
|
193 | |||
|
194 | ||||
|
195 | def test_display_id(): | |||
|
196 | ip = get_ipython() | |||
|
197 | with mock.patch.object(ip.display_pub, 'publish') as pub: | |||
|
198 | handle = display.display('x') | |||
|
199 | nt.assert_is(handle, None) | |||
|
200 | handle = display.display('y', display_id='secret') | |||
|
201 | nt.assert_is_instance(handle, display.DisplayHandle) | |||
|
202 | handle2 = display.display('z', display_id=True) | |||
|
203 | nt.assert_is_instance(handle2, display.DisplayHandle) | |||
|
204 | nt.assert_not_equal(handle.display_id, handle2.display_id) | |||
|
205 | ||||
|
206 | nt.assert_equal(pub.call_count, 3) | |||
|
207 | args, kwargs = pub.call_args_list[0] | |||
|
208 | nt.assert_equal(args, ()) | |||
|
209 | nt.assert_equal(kwargs, { | |||
|
210 | 'data': { | |||
|
211 | 'text/plain': repr('x') | |||
|
212 | }, | |||
|
213 | 'metadata': {}, | |||
|
214 | }) | |||
|
215 | args, kwargs = pub.call_args_list[1] | |||
|
216 | nt.assert_equal(args, ()) | |||
|
217 | nt.assert_equal(kwargs, { | |||
|
218 | 'data': { | |||
|
219 | 'text/plain': repr('y') | |||
|
220 | }, | |||
|
221 | 'metadata': {}, | |||
|
222 | 'transient': { | |||
|
223 | 'display_id': handle.display_id, | |||
|
224 | }, | |||
|
225 | }) | |||
|
226 | args, kwargs = pub.call_args_list[2] | |||
|
227 | nt.assert_equal(args, ()) | |||
|
228 | nt.assert_equal(kwargs, { | |||
|
229 | 'data': { | |||
|
230 | 'text/plain': repr('z') | |||
|
231 | }, | |||
|
232 | 'metadata': {}, | |||
|
233 | 'transient': { | |||
|
234 | 'display_id': handle2.display_id, | |||
|
235 | }, | |||
|
236 | }) | |||
|
237 | ||||
|
238 | ||||
|
239 | def test_update_display(): | |||
|
240 | ip = get_ipython() | |||
|
241 | with mock.patch.object(ip.display_pub, 'publish') as pub: | |||
|
242 | with nt.assert_raises(TypeError): | |||
|
243 | display.update_display('x') | |||
|
244 | display.update_display('x', display_id='1') | |||
|
245 | display.update_display('y', display_id='2') | |||
|
246 | args, kwargs = pub.call_args_list[0] | |||
|
247 | nt.assert_equal(args, ()) | |||
|
248 | nt.assert_equal(kwargs, { | |||
|
249 | 'data': { | |||
|
250 | 'text/plain': repr('x') | |||
|
251 | }, | |||
|
252 | 'metadata': {}, | |||
|
253 | 'transient': { | |||
|
254 | 'display_id': '1', | |||
|
255 | }, | |||
|
256 | 'update': True, | |||
|
257 | }) | |||
|
258 | args, kwargs = pub.call_args_list[1] | |||
|
259 | nt.assert_equal(args, ()) | |||
|
260 | nt.assert_equal(kwargs, { | |||
|
261 | 'data': { | |||
|
262 | 'text/plain': repr('y') | |||
|
263 | }, | |||
|
264 | 'metadata': {}, | |||
|
265 | 'transient': { | |||
|
266 | 'display_id': '2', | |||
|
267 | }, | |||
|
268 | 'update': True, | |||
|
269 | }) | |||
|
270 | ||||
|
271 | ||||
|
272 | def test_display_handle(): | |||
|
273 | ip = get_ipython() | |||
|
274 | handle = display.DisplayHandle() | |||
|
275 | nt.assert_is_instance(handle.display_id, str) | |||
|
276 | handle = display.DisplayHandle('my-id') | |||
|
277 | nt.assert_equal(handle.display_id, 'my-id') | |||
|
278 | with mock.patch.object(ip.display_pub, 'publish') as pub: | |||
|
279 | handle.display('x') | |||
|
280 | handle.update('y') | |||
|
281 | ||||
|
282 | args, kwargs = pub.call_args_list[0] | |||
|
283 | nt.assert_equal(args, ()) | |||
|
284 | nt.assert_equal(kwargs, { | |||
|
285 | 'data': { | |||
|
286 | 'text/plain': repr('x') | |||
|
287 | }, | |||
|
288 | 'metadata': {}, | |||
|
289 | 'transient': { | |||
|
290 | 'display_id': handle.display_id, | |||
|
291 | } | |||
|
292 | }) | |||
|
293 | args, kwargs = pub.call_args_list[1] | |||
|
294 | nt.assert_equal(args, ()) | |||
|
295 | nt.assert_equal(kwargs, { | |||
|
296 | 'data': { | |||
|
297 | 'text/plain': repr('y') | |||
|
298 | }, | |||
|
299 | 'metadata': {}, | |||
|
300 | 'transient': { | |||
|
301 | 'display_id': handle.display_id, | |||
|
302 | }, | |||
|
303 | 'update': True, | |||
|
304 | }) |
General Comments 0
You need to be logged in to leave comments.
Login now