##// END OF EJS Templates
Do not import from IPython.core.display and warn users.
Matthias Bussonnier -
Show More
@@ -1,1154 +1,1176 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Top-level display functions for displaying object in different formats."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7
8 8 from binascii import b2a_base64, hexlify
9 9 import json
10 10 import mimetypes
11 11 import os
12 12 import struct
13 13 import warnings
14 14 from copy import deepcopy
15 15 from os.path import splitext
16 16 from pathlib import Path, PurePath
17 17
18 18 from IPython.utils.py3compat import cast_unicode
19 19 from IPython.testing.skipdoctest import skip_doctest
20 from .display_functions import display, clear_output, publish_display_data, update_display, DisplayHandle
20 from . import display_functions
21 21
22 __all__ = ['display', 'display_pretty', 'display_html', 'display_markdown',
22
23 __all__ = ['display_pretty', 'display_html', 'display_markdown',
23 24 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json',
24 25 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject',
25 26 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'ProgressBar', 'JSON',
26 'GeoJSON', 'Javascript', 'Image', 'clear_output', 'set_matplotlib_formats',
27 'set_matplotlib_close', 'publish_display_data', 'update_display', 'DisplayHandle',
27 'GeoJSON', 'Javascript', 'Image', 'set_matplotlib_formats',
28 'set_matplotlib_close',
28 29 'Video']
29 30
31 _deprecated_names = ["display", "clear_output", "publish_display_data", "update_display", "DisplayHandle"]
32
33 __all__ = __all__ + _deprecated_names
34
35
36 # ----- warn to import from IPython.display -----
37
38 from warnings import warn
39
40
41 def __getattr__(name):
42 if name in _deprecated_names:
43 warn(f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython display", DeprecationWarning, stacklevel=2)
44 return getattr(display_functions, name)
45
46 if name in globals().keys():
47 return globals()[name]
48 else:
49 raise AttributeError(f"module {__name__} has no attribute {name}")
50
51
30 52 #-----------------------------------------------------------------------------
31 53 # utility functions
32 54 #-----------------------------------------------------------------------------
33 55
34 56 def _safe_exists(path):
35 57 """Check path, but don't let exceptions raise"""
36 58 try:
37 59 return os.path.exists(path)
38 60 except Exception:
39 61 return False
40 62
41 63
42 64 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
43 65 """internal implementation of all display_foo methods
44 66
45 67 Parameters
46 68 ----------
47 69 mimetype : str
48 70 The mimetype to be published (e.g. 'image/png')
49 71 *objs : object
50 72 The Python objects to display, or if raw=True raw text data to
51 73 display.
52 74 raw : bool
53 75 Are the data objects raw data or Python objects that need to be
54 76 formatted before display? [default: False]
55 77 metadata : dict (optional)
56 78 Metadata to be associated with the specific mimetype output.
57 79 """
58 80 if metadata:
59 81 metadata = {mimetype: metadata}
60 82 if raw:
61 83 # turn list of pngdata into list of { 'image/png': pngdata }
62 84 objs = [ {mimetype: obj} for obj in objs ]
63 85 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
64 86
65 87 #-----------------------------------------------------------------------------
66 88 # Main functions
67 89 #-----------------------------------------------------------------------------
68 90
69 91
70 92 def display_pretty(*objs, **kwargs):
71 93 """Display the pretty (default) representation of an object.
72 94
73 95 Parameters
74 96 ----------
75 97 *objs : object
76 98 The Python objects to display, or if raw=True raw text data to
77 99 display.
78 100 raw : bool
79 101 Are the data objects raw data or Python objects that need to be
80 102 formatted before display? [default: False]
81 103 metadata : dict (optional)
82 104 Metadata to be associated with the specific mimetype output.
83 105 """
84 106 _display_mimetype('text/plain', objs, **kwargs)
85 107
86 108
87 109 def display_html(*objs, **kwargs):
88 110 """Display the HTML representation of an object.
89 111
90 112 Note: If raw=False and the object does not have a HTML
91 113 representation, no HTML will be shown.
92 114
93 115 Parameters
94 116 ----------
95 117 *objs : object
96 118 The Python objects to display, or if raw=True raw HTML data to
97 119 display.
98 120 raw : bool
99 121 Are the data objects raw data or Python objects that need to be
100 122 formatted before display? [default: False]
101 123 metadata : dict (optional)
102 124 Metadata to be associated with the specific mimetype output.
103 125 """
104 126 _display_mimetype('text/html', objs, **kwargs)
105 127
106 128
107 129 def display_markdown(*objs, **kwargs):
108 130 """Displays the Markdown representation of an object.
109 131
110 132 Parameters
111 133 ----------
112 134 *objs : object
113 135 The Python objects to display, or if raw=True raw markdown data to
114 136 display.
115 137 raw : bool
116 138 Are the data objects raw data or Python objects that need to be
117 139 formatted before display? [default: False]
118 140 metadata : dict (optional)
119 141 Metadata to be associated with the specific mimetype output.
120 142 """
121 143
122 144 _display_mimetype('text/markdown', objs, **kwargs)
123 145
124 146
125 147 def display_svg(*objs, **kwargs):
126 148 """Display the SVG representation of an object.
127 149
128 150 Parameters
129 151 ----------
130 152 *objs : object
131 153 The Python objects to display, or if raw=True raw svg data to
132 154 display.
133 155 raw : bool
134 156 Are the data objects raw data or Python objects that need to be
135 157 formatted before display? [default: False]
136 158 metadata : dict (optional)
137 159 Metadata to be associated with the specific mimetype output.
138 160 """
139 161 _display_mimetype('image/svg+xml', objs, **kwargs)
140 162
141 163
142 164 def display_png(*objs, **kwargs):
143 165 """Display the PNG representation of an object.
144 166
145 167 Parameters
146 168 ----------
147 169 *objs : object
148 170 The Python objects to display, or if raw=True raw png data to
149 171 display.
150 172 raw : bool
151 173 Are the data objects raw data or Python objects that need to be
152 174 formatted before display? [default: False]
153 175 metadata : dict (optional)
154 176 Metadata to be associated with the specific mimetype output.
155 177 """
156 178 _display_mimetype('image/png', objs, **kwargs)
157 179
158 180
159 181 def display_jpeg(*objs, **kwargs):
160 182 """Display the JPEG representation of an object.
161 183
162 184 Parameters
163 185 ----------
164 186 *objs : object
165 187 The Python objects to display, or if raw=True raw JPEG data to
166 188 display.
167 189 raw : bool
168 190 Are the data objects raw data or Python objects that need to be
169 191 formatted before display? [default: False]
170 192 metadata : dict (optional)
171 193 Metadata to be associated with the specific mimetype output.
172 194 """
173 195 _display_mimetype('image/jpeg', objs, **kwargs)
174 196
175 197
176 198 def display_latex(*objs, **kwargs):
177 199 """Display the LaTeX representation of an object.
178 200
179 201 Parameters
180 202 ----------
181 203 *objs : object
182 204 The Python objects to display, or if raw=True raw latex data to
183 205 display.
184 206 raw : bool
185 207 Are the data objects raw data or Python objects that need to be
186 208 formatted before display? [default: False]
187 209 metadata : dict (optional)
188 210 Metadata to be associated with the specific mimetype output.
189 211 """
190 212 _display_mimetype('text/latex', objs, **kwargs)
191 213
192 214
193 215 def display_json(*objs, **kwargs):
194 216 """Display the JSON representation of an object.
195 217
196 218 Note that not many frontends support displaying JSON.
197 219
198 220 Parameters
199 221 ----------
200 222 *objs : object
201 223 The Python objects to display, or if raw=True raw json data to
202 224 display.
203 225 raw : bool
204 226 Are the data objects raw data or Python objects that need to be
205 227 formatted before display? [default: False]
206 228 metadata : dict (optional)
207 229 Metadata to be associated with the specific mimetype output.
208 230 """
209 231 _display_mimetype('application/json', objs, **kwargs)
210 232
211 233
212 234 def display_javascript(*objs, **kwargs):
213 235 """Display the Javascript representation of an object.
214 236
215 237 Parameters
216 238 ----------
217 239 *objs : object
218 240 The Python objects to display, or if raw=True raw javascript data to
219 241 display.
220 242 raw : bool
221 243 Are the data objects raw data or Python objects that need to be
222 244 formatted before display? [default: False]
223 245 metadata : dict (optional)
224 246 Metadata to be associated with the specific mimetype output.
225 247 """
226 248 _display_mimetype('application/javascript', objs, **kwargs)
227 249
228 250
229 251 def display_pdf(*objs, **kwargs):
230 252 """Display the PDF representation of an object.
231 253
232 254 Parameters
233 255 ----------
234 256 *objs : object
235 257 The Python objects to display, or if raw=True raw javascript data to
236 258 display.
237 259 raw : bool
238 260 Are the data objects raw data or Python objects that need to be
239 261 formatted before display? [default: False]
240 262 metadata : dict (optional)
241 263 Metadata to be associated with the specific mimetype output.
242 264 """
243 265 _display_mimetype('application/pdf', objs, **kwargs)
244 266
245 267
246 268 #-----------------------------------------------------------------------------
247 269 # Smart classes
248 270 #-----------------------------------------------------------------------------
249 271
250 272
251 273 class DisplayObject(object):
252 274 """An object that wraps data to be displayed."""
253 275
254 276 _read_flags = 'r'
255 277 _show_mem_addr = False
256 278 metadata = None
257 279
258 280 def __init__(self, data=None, url=None, filename=None, metadata=None):
259 281 """Create a display object given raw data.
260 282
261 283 When this object is returned by an expression or passed to the
262 284 display function, it will result in the data being displayed
263 285 in the frontend. The MIME type of the data should match the
264 286 subclasses used, so the Png subclass should be used for 'image/png'
265 287 data. If the data is a URL, the data will first be downloaded
266 288 and then displayed. If
267 289
268 290 Parameters
269 291 ----------
270 292 data : unicode, str or bytes
271 293 The raw data or a URL or file to load the data from
272 294 url : unicode
273 295 A URL to download the data from.
274 296 filename : unicode
275 297 Path to a local file to load the data from.
276 298 metadata : dict
277 299 Dict of metadata associated to be the object when displayed
278 300 """
279 301 if isinstance(data, (Path, PurePath)):
280 302 data = str(data)
281 303
282 304 if data is not None and isinstance(data, str):
283 305 if data.startswith('http') and url is None:
284 306 url = data
285 307 filename = None
286 308 data = None
287 309 elif _safe_exists(data) and filename is None:
288 310 url = None
289 311 filename = data
290 312 data = None
291 313
292 314 self.data = data
293 315 self.url = url
294 316 self.filename = filename
295 317
296 318 if metadata is not None:
297 319 self.metadata = metadata
298 320 elif self.metadata is None:
299 321 self.metadata = {}
300 322
301 323 self.reload()
302 324 self._check_data()
303 325
304 326 def __repr__(self):
305 327 if not self._show_mem_addr:
306 328 cls = self.__class__
307 329 r = "<%s.%s object>" % (cls.__module__, cls.__name__)
308 330 else:
309 331 r = super(DisplayObject, self).__repr__()
310 332 return r
311 333
312 334 def _check_data(self):
313 335 """Override in subclasses if there's something to check."""
314 336 pass
315 337
316 338 def _data_and_metadata(self):
317 339 """shortcut for returning metadata with shape information, if defined"""
318 340 if self.metadata:
319 341 return self.data, deepcopy(self.metadata)
320 342 else:
321 343 return self.data
322 344
323 345 def reload(self):
324 346 """Reload the raw data from file or URL."""
325 347 if self.filename is not None:
326 348 with open(self.filename, self._read_flags) as f:
327 349 self.data = f.read()
328 350 elif self.url is not None:
329 351 try:
330 352 # Deferred import
331 353 from urllib.request import urlopen
332 354 response = urlopen(self.url)
333 355 self.data = response.read()
334 356 # extract encoding from header, if there is one:
335 357 encoding = None
336 358 for sub in response.headers['content-type'].split(';'):
337 359 sub = sub.strip()
338 360 if sub.startswith('charset'):
339 361 encoding = sub.split('=')[-1].strip()
340 362 break
341 363 # decode data, if an encoding was specified
342 364 if encoding:
343 365 self.data = self.data.decode(encoding, 'replace')
344 366 except:
345 367 self.data = None
346 368
347 369 class TextDisplayObject(DisplayObject):
348 370 """Validate that display data is text"""
349 371 def _check_data(self):
350 372 if self.data is not None and not isinstance(self.data, str):
351 373 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
352 374
353 375 class Pretty(TextDisplayObject):
354 376
355 377 def _repr_pretty_(self, pp, cycle):
356 378 return pp.text(self.data)
357 379
358 380
359 381 class HTML(TextDisplayObject):
360 382
361 383 def __init__(self, data=None, url=None, filename=None, metadata=None):
362 384 def warn():
363 385 if not data:
364 386 return False
365 387
366 388 #
367 389 # Avoid calling lower() on the entire data, because it could be a
368 390 # long string and we're only interested in its beginning and end.
369 391 #
370 392 prefix = data[:10].lower()
371 393 suffix = data[-10:].lower()
372 394 return prefix.startswith("<iframe ") and suffix.endswith("</iframe>")
373 395
374 396 if warn():
375 397 warnings.warn("Consider using IPython.display.IFrame instead")
376 398 super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata)
377 399
378 400 def _repr_html_(self):
379 401 return self._data_and_metadata()
380 402
381 403 def __html__(self):
382 404 """
383 405 This method exists to inform other HTML-using modules (e.g. Markupsafe,
384 406 htmltag, etc) that this object is HTML and does not need things like
385 407 special characters (<>&) escaped.
386 408 """
387 409 return self._repr_html_()
388 410
389 411
390 412 class Markdown(TextDisplayObject):
391 413
392 414 def _repr_markdown_(self):
393 415 return self._data_and_metadata()
394 416
395 417
396 418 class Math(TextDisplayObject):
397 419
398 420 def _repr_latex_(self):
399 421 s = r"$\displaystyle %s$" % self.data.strip('$')
400 422 if self.metadata:
401 423 return s, deepcopy(self.metadata)
402 424 else:
403 425 return s
404 426
405 427
406 428 class Latex(TextDisplayObject):
407 429
408 430 def _repr_latex_(self):
409 431 return self._data_and_metadata()
410 432
411 433
412 434 class SVG(DisplayObject):
413 435
414 436 _read_flags = 'rb'
415 437 # wrap data in a property, which extracts the <svg> tag, discarding
416 438 # document headers
417 439 _data = None
418 440
419 441 @property
420 442 def data(self):
421 443 return self._data
422 444
423 445 @data.setter
424 446 def data(self, svg):
425 447 if svg is None:
426 448 self._data = None
427 449 return
428 450 # parse into dom object
429 451 from xml.dom import minidom
430 452 x = minidom.parseString(svg)
431 453 # get svg tag (should be 1)
432 454 found_svg = x.getElementsByTagName('svg')
433 455 if found_svg:
434 456 svg = found_svg[0].toxml()
435 457 else:
436 458 # fallback on the input, trust the user
437 459 # but this is probably an error.
438 460 pass
439 461 svg = cast_unicode(svg)
440 462 self._data = svg
441 463
442 464 def _repr_svg_(self):
443 465 return self._data_and_metadata()
444 466
445 467 class ProgressBar(DisplayObject):
446 468 """Progressbar supports displaying a progressbar like element
447 469 """
448 470 def __init__(self, total):
449 471 """Creates a new progressbar
450 472
451 473 Parameters
452 474 ----------
453 475 total : int
454 476 maximum size of the progressbar
455 477 """
456 478 self.total = total
457 479 self._progress = 0
458 480 self.html_width = '60ex'
459 481 self.text_width = 60
460 482 self._display_id = hexlify(os.urandom(8)).decode('ascii')
461 483
462 484 def __repr__(self):
463 485 fraction = self.progress / self.total
464 486 filled = '=' * int(fraction * self.text_width)
465 487 rest = ' ' * (self.text_width - len(filled))
466 488 return '[{}{}] {}/{}'.format(
467 489 filled, rest,
468 490 self.progress, self.total,
469 491 )
470 492
471 493 def _repr_html_(self):
472 494 return "<progress style='width:{}' max='{}' value='{}'></progress>".format(
473 495 self.html_width, self.total, self.progress)
474 496
475 497 def display(self):
476 498 display(self, display_id=self._display_id)
477 499
478 500 def update(self):
479 501 display(self, display_id=self._display_id, update=True)
480 502
481 503 @property
482 504 def progress(self):
483 505 return self._progress
484 506
485 507 @progress.setter
486 508 def progress(self, value):
487 509 self._progress = value
488 510 self.update()
489 511
490 512 def __iter__(self):
491 513 self.display()
492 514 self._progress = -1 # First iteration is 0
493 515 return self
494 516
495 517 def __next__(self):
496 518 """Returns current value and increments display by one."""
497 519 self.progress += 1
498 520 if self.progress < self.total:
499 521 return self.progress
500 522 else:
501 523 raise StopIteration()
502 524
503 525 class JSON(DisplayObject):
504 526 """JSON expects a JSON-able dict or list
505 527
506 528 not an already-serialized JSON string.
507 529
508 530 Scalar types (None, number, string) are not allowed, only dict or list containers.
509 531 """
510 532 # wrap data in a property, which warns about passing already-serialized JSON
511 533 _data = None
512 534 def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs):
513 535 """Create a JSON display object given raw data.
514 536
515 537 Parameters
516 538 ----------
517 539 data : dict or list
518 540 JSON data to display. Not an already-serialized JSON string.
519 541 Scalar types (None, number, string) are not allowed, only dict
520 542 or list containers.
521 543 url : unicode
522 544 A URL to download the data from.
523 545 filename : unicode
524 546 Path to a local file to load the data from.
525 547 expanded : boolean
526 548 Metadata to control whether a JSON display component is expanded.
527 549 metadata: dict
528 550 Specify extra metadata to attach to the json display object.
529 551 root : str
530 552 The name of the root element of the JSON tree
531 553 """
532 554 self.metadata = {
533 555 'expanded': expanded,
534 556 'root': root,
535 557 }
536 558 if metadata:
537 559 self.metadata.update(metadata)
538 560 if kwargs:
539 561 self.metadata.update(kwargs)
540 562 super(JSON, self).__init__(data=data, url=url, filename=filename)
541 563
542 564 def _check_data(self):
543 565 if self.data is not None and not isinstance(self.data, (dict, list)):
544 566 raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data))
545 567
546 568 @property
547 569 def data(self):
548 570 return self._data
549 571
550 572 @data.setter
551 573 def data(self, data):
552 574 if isinstance(data, (Path, PurePath)):
553 575 data = str(data)
554 576
555 577 if isinstance(data, str):
556 578 if getattr(self, 'filename', None) is None:
557 579 warnings.warn("JSON expects JSONable dict or list, not JSON strings")
558 580 data = json.loads(data)
559 581 self._data = data
560 582
561 583 def _data_and_metadata(self):
562 584 return self.data, self.metadata
563 585
564 586 def _repr_json_(self):
565 587 return self._data_and_metadata()
566 588
567 589 _css_t = """var link = document.createElement("link");
568 590 link.ref = "stylesheet";
569 591 link.type = "text/css";
570 592 link.href = "%s";
571 593 document.head.appendChild(link);
572 594 """
573 595
574 596 _lib_t1 = """new Promise(function(resolve, reject) {
575 597 var script = document.createElement("script");
576 598 script.onload = resolve;
577 599 script.onerror = reject;
578 600 script.src = "%s";
579 601 document.head.appendChild(script);
580 602 }).then(() => {
581 603 """
582 604
583 605 _lib_t2 = """
584 606 });"""
585 607
586 608 class GeoJSON(JSON):
587 609 """GeoJSON expects JSON-able dict
588 610
589 611 not an already-serialized JSON string.
590 612
591 613 Scalar types (None, number, string) are not allowed, only dict containers.
592 614 """
593 615
594 616 def __init__(self, *args, **kwargs):
595 617 """Create a GeoJSON display object given raw data.
596 618
597 619 Parameters
598 620 ----------
599 621 data : dict or list
600 622 VegaLite data. Not an already-serialized JSON string.
601 623 Scalar types (None, number, string) are not allowed, only dict
602 624 or list containers.
603 625 url_template : string
604 626 Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template
605 627 layer_options : dict
606 628 Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options
607 629 url : unicode
608 630 A URL to download the data from.
609 631 filename : unicode
610 632 Path to a local file to load the data from.
611 633 metadata: dict
612 634 Specify extra metadata to attach to the json display object.
613 635
614 636 Examples
615 637 --------
616 638
617 639 The following will display an interactive map of Mars with a point of
618 640 interest on frontend that do support GeoJSON display.
619 641
620 642 >>> from IPython.display import GeoJSON
621 643
622 644 >>> GeoJSON(data={
623 645 ... "type": "Feature",
624 646 ... "geometry": {
625 647 ... "type": "Point",
626 648 ... "coordinates": [-81.327, 296.038]
627 649 ... }
628 650 ... },
629 651 ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
630 652 ... layer_options={
631 653 ... "basemap_id": "celestia_mars-shaded-16k_global",
632 654 ... "attribution" : "Celestia/praesepe",
633 655 ... "minZoom" : 0,
634 656 ... "maxZoom" : 18,
635 657 ... })
636 658 <IPython.core.display.GeoJSON object>
637 659
638 660 In the terminal IPython, you will only see the text representation of
639 661 the GeoJSON object.
640 662
641 663 """
642 664
643 665 super(GeoJSON, self).__init__(*args, **kwargs)
644 666
645 667
646 668 def _ipython_display_(self):
647 669 bundle = {
648 670 'application/geo+json': self.data,
649 671 'text/plain': '<IPython.display.GeoJSON object>'
650 672 }
651 673 metadata = {
652 674 'application/geo+json': self.metadata
653 675 }
654 676 display(bundle, metadata=metadata, raw=True)
655 677
656 678 class Javascript(TextDisplayObject):
657 679
658 680 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
659 681 """Create a Javascript display object given raw data.
660 682
661 683 When this object is returned by an expression or passed to the
662 684 display function, it will result in the data being displayed
663 685 in the frontend. If the data is a URL, the data will first be
664 686 downloaded and then displayed.
665 687
666 688 In the Notebook, the containing element will be available as `element`,
667 689 and jQuery will be available. Content appended to `element` will be
668 690 visible in the output area.
669 691
670 692 Parameters
671 693 ----------
672 694 data : unicode, str or bytes
673 695 The Javascript source code or a URL to download it from.
674 696 url : unicode
675 697 A URL to download the data from.
676 698 filename : unicode
677 699 Path to a local file to load the data from.
678 700 lib : list or str
679 701 A sequence of Javascript library URLs to load asynchronously before
680 702 running the source code. The full URLs of the libraries should
681 703 be given. A single Javascript library URL can also be given as a
682 704 string.
683 705 css: : list or str
684 706 A sequence of css files to load before running the source code.
685 707 The full URLs of the css files should be given. A single css URL
686 708 can also be given as a string.
687 709 """
688 710 if isinstance(lib, str):
689 711 lib = [lib]
690 712 elif lib is None:
691 713 lib = []
692 714 if isinstance(css, str):
693 715 css = [css]
694 716 elif css is None:
695 717 css = []
696 718 if not isinstance(lib, (list,tuple)):
697 719 raise TypeError('expected sequence, got: %r' % lib)
698 720 if not isinstance(css, (list,tuple)):
699 721 raise TypeError('expected sequence, got: %r' % css)
700 722 self.lib = lib
701 723 self.css = css
702 724 super(Javascript, self).__init__(data=data, url=url, filename=filename)
703 725
704 726 def _repr_javascript_(self):
705 727 r = ''
706 728 for c in self.css:
707 729 r += _css_t % c
708 730 for l in self.lib:
709 731 r += _lib_t1 % l
710 732 r += self.data
711 733 r += _lib_t2*len(self.lib)
712 734 return r
713 735
714 736 # constants for identifying png/jpeg data
715 737 _PNG = b'\x89PNG\r\n\x1a\n'
716 738 _JPEG = b'\xff\xd8'
717 739
718 740 def _pngxy(data):
719 741 """read the (width, height) from a PNG header"""
720 742 ihdr = data.index(b'IHDR')
721 743 # next 8 bytes are width/height
722 744 return struct.unpack('>ii', data[ihdr+4:ihdr+12])
723 745
724 746 def _jpegxy(data):
725 747 """read the (width, height) from a JPEG header"""
726 748 # adapted from http://www.64lines.com/jpeg-width-height
727 749
728 750 idx = 4
729 751 while True:
730 752 block_size = struct.unpack('>H', data[idx:idx+2])[0]
731 753 idx = idx + block_size
732 754 if data[idx:idx+2] == b'\xFF\xC0':
733 755 # found Start of Frame
734 756 iSOF = idx
735 757 break
736 758 else:
737 759 # read another block
738 760 idx += 2
739 761
740 762 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
741 763 return w, h
742 764
743 765 def _gifxy(data):
744 766 """read the (width, height) from a GIF header"""
745 767 return struct.unpack('<HH', data[6:10])
746 768
747 769
748 770 class Image(DisplayObject):
749 771
750 772 _read_flags = 'rb'
751 773 _FMT_JPEG = u'jpeg'
752 774 _FMT_PNG = u'png'
753 775 _FMT_GIF = u'gif'
754 776 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF]
755 777 _MIMETYPES = {
756 778 _FMT_PNG: 'image/png',
757 779 _FMT_JPEG: 'image/jpeg',
758 780 _FMT_GIF: 'image/gif',
759 781 }
760 782
761 783 def __init__(self, data=None, url=None, filename=None, format=None,
762 784 embed=None, width=None, height=None, retina=False,
763 785 unconfined=False, metadata=None):
764 786 """Create a PNG/JPEG/GIF image object given raw data.
765 787
766 788 When this object is returned by an input cell or passed to the
767 789 display function, it will result in the image being displayed
768 790 in the frontend.
769 791
770 792 Parameters
771 793 ----------
772 794 data : unicode, str or bytes
773 795 The raw image data or a URL or filename to load the data from.
774 796 This always results in embedded image data.
775 797 url : unicode
776 798 A URL to download the data from. If you specify `url=`,
777 799 the image data will not be embedded unless you also specify `embed=True`.
778 800 filename : unicode
779 801 Path to a local file to load the data from.
780 802 Images from a file are always embedded.
781 803 format : unicode
782 804 The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given
783 805 for format will be inferred from the filename extension.
784 806 embed : bool
785 807 Should the image data be embedded using a data URI (True) or be
786 808 loaded using an <img> tag. Set this to True if you want the image
787 809 to be viewable later with no internet connection in the notebook.
788 810
789 811 Default is `True`, unless the keyword argument `url` is set, then
790 812 default value is `False`.
791 813
792 814 Note that QtConsole is not able to display images if `embed` is set to `False`
793 815 width : int
794 816 Width in pixels to which to constrain the image in html
795 817 height : int
796 818 Height in pixels to which to constrain the image in html
797 819 retina : bool
798 820 Automatically set the width and height to half of the measured
799 821 width and height.
800 822 This only works for embedded images because it reads the width/height
801 823 from image data.
802 824 For non-embedded images, you can just set the desired display width
803 825 and height directly.
804 826 unconfined: bool
805 827 Set unconfined=True to disable max-width confinement of the image.
806 828 metadata: dict
807 829 Specify extra metadata to attach to the image.
808 830
809 831 Examples
810 832 --------
811 833 # embedded image data, works in qtconsole and notebook
812 834 # when passed positionally, the first arg can be any of raw image data,
813 835 # a URL, or a filename from which to load image data.
814 836 # The result is always embedding image data for inline images.
815 837 Image('http://www.google.fr/images/srpr/logo3w.png')
816 838 Image('/path/to/image.jpg')
817 839 Image(b'RAW_PNG_DATA...')
818 840
819 841 # Specifying Image(url=...) does not embed the image data,
820 842 # it only generates `<img>` tag with a link to the source.
821 843 # This will not work in the qtconsole or offline.
822 844 Image(url='http://www.google.fr/images/srpr/logo3w.png')
823 845
824 846 """
825 847 if isinstance(data, (Path, PurePath)):
826 848 data = str(data)
827 849
828 850 if filename is not None:
829 851 ext = self._find_ext(filename)
830 852 elif url is not None:
831 853 ext = self._find_ext(url)
832 854 elif data is None:
833 855 raise ValueError("No image data found. Expecting filename, url, or data.")
834 856 elif isinstance(data, str) and (
835 857 data.startswith('http') or _safe_exists(data)
836 858 ):
837 859 ext = self._find_ext(data)
838 860 else:
839 861 ext = None
840 862
841 863 if format is None:
842 864 if ext is not None:
843 865 if ext == u'jpg' or ext == u'jpeg':
844 866 format = self._FMT_JPEG
845 867 elif ext == u'png':
846 868 format = self._FMT_PNG
847 869 elif ext == u'gif':
848 870 format = self._FMT_GIF
849 871 else:
850 872 format = ext.lower()
851 873 elif isinstance(data, bytes):
852 874 # infer image type from image data header,
853 875 # only if format has not been specified.
854 876 if data[:2] == _JPEG:
855 877 format = self._FMT_JPEG
856 878
857 879 # failed to detect format, default png
858 880 if format is None:
859 881 format = self._FMT_PNG
860 882
861 883 if format.lower() == 'jpg':
862 884 # jpg->jpeg
863 885 format = self._FMT_JPEG
864 886
865 887 self.format = format.lower()
866 888 self.embed = embed if embed is not None else (url is None)
867 889
868 890 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
869 891 raise ValueError("Cannot embed the '%s' image format" % (self.format))
870 892 if self.embed:
871 893 self._mimetype = self._MIMETYPES.get(self.format)
872 894
873 895 self.width = width
874 896 self.height = height
875 897 self.retina = retina
876 898 self.unconfined = unconfined
877 899 super(Image, self).__init__(data=data, url=url, filename=filename,
878 900 metadata=metadata)
879 901
880 902 if self.width is None and self.metadata.get('width', {}):
881 903 self.width = metadata['width']
882 904
883 905 if self.height is None and self.metadata.get('height', {}):
884 906 self.height = metadata['height']
885 907
886 908 if retina:
887 909 self._retina_shape()
888 910
889 911
890 912 def _retina_shape(self):
891 913 """load pixel-doubled width and height from image data"""
892 914 if not self.embed:
893 915 return
894 916 if self.format == self._FMT_PNG:
895 917 w, h = _pngxy(self.data)
896 918 elif self.format == self._FMT_JPEG:
897 919 w, h = _jpegxy(self.data)
898 920 elif self.format == self._FMT_GIF:
899 921 w, h = _gifxy(self.data)
900 922 else:
901 923 # retina only supports png
902 924 return
903 925 self.width = w // 2
904 926 self.height = h // 2
905 927
906 928 def reload(self):
907 929 """Reload the raw data from file or URL."""
908 930 if self.embed:
909 931 super(Image,self).reload()
910 932 if self.retina:
911 933 self._retina_shape()
912 934
913 935 def _repr_html_(self):
914 936 if not self.embed:
915 937 width = height = klass = ''
916 938 if self.width:
917 939 width = ' width="%d"' % self.width
918 940 if self.height:
919 941 height = ' height="%d"' % self.height
920 942 if self.unconfined:
921 943 klass = ' class="unconfined"'
922 944 return u'<img src="{url}"{width}{height}{klass}/>'.format(
923 945 url=self.url,
924 946 width=width,
925 947 height=height,
926 948 klass=klass,
927 949 )
928 950
929 951 def _repr_mimebundle_(self, include=None, exclude=None):
930 952 """Return the image as a mimebundle
931 953
932 954 Any new mimetype support should be implemented here.
933 955 """
934 956 if self.embed:
935 957 mimetype = self._mimetype
936 958 data, metadata = self._data_and_metadata(always_both=True)
937 959 if metadata:
938 960 metadata = {mimetype: metadata}
939 961 return {mimetype: data}, metadata
940 962 else:
941 963 return {'text/html': self._repr_html_()}
942 964
943 965 def _data_and_metadata(self, always_both=False):
944 966 """shortcut for returning metadata with shape information, if defined"""
945 967 try:
946 968 b64_data = b2a_base64(self.data).decode('ascii')
947 969 except TypeError:
948 970 raise FileNotFoundError(
949 971 "No such file or directory: '%s'" % (self.data))
950 972 md = {}
951 973 if self.metadata:
952 974 md.update(self.metadata)
953 975 if self.width:
954 976 md['width'] = self.width
955 977 if self.height:
956 978 md['height'] = self.height
957 979 if self.unconfined:
958 980 md['unconfined'] = self.unconfined
959 981 if md or always_both:
960 982 return b64_data, md
961 983 else:
962 984 return b64_data
963 985
964 986 def _repr_png_(self):
965 987 if self.embed and self.format == self._FMT_PNG:
966 988 return self._data_and_metadata()
967 989
968 990 def _repr_jpeg_(self):
969 991 if self.embed and self.format == self._FMT_JPEG:
970 992 return self._data_and_metadata()
971 993
972 994 def _find_ext(self, s):
973 995 base, ext = splitext(s)
974 996
975 997 if not ext:
976 998 return base
977 999
978 1000 # `splitext` includes leading period, so we skip it
979 1001 return ext[1:].lower()
980 1002
981 1003
982 1004 class Video(DisplayObject):
983 1005
984 1006 def __init__(self, data=None, url=None, filename=None, embed=False,
985 1007 mimetype=None, width=None, height=None):
986 1008 """Create a video object given raw data or an URL.
987 1009
988 1010 When this object is returned by an input cell or passed to the
989 1011 display function, it will result in the video being displayed
990 1012 in the frontend.
991 1013
992 1014 Parameters
993 1015 ----------
994 1016 data : unicode, str or bytes
995 1017 The raw video data or a URL or filename to load the data from.
996 1018 Raw data will require passing `embed=True`.
997 1019 url : unicode
998 1020 A URL for the video. If you specify `url=`,
999 1021 the image data will not be embedded.
1000 1022 filename : unicode
1001 1023 Path to a local file containing the video.
1002 1024 Will be interpreted as a local URL unless `embed=True`.
1003 1025 embed : bool
1004 1026 Should the video be embedded using a data URI (True) or be
1005 1027 loaded using a <video> tag (False).
1006 1028
1007 1029 Since videos are large, embedding them should be avoided, if possible.
1008 1030 You must confirm embedding as your intention by passing `embed=True`.
1009 1031
1010 1032 Local files can be displayed with URLs without embedding the content, via::
1011 1033
1012 1034 Video('./video.mp4')
1013 1035
1014 1036 mimetype: unicode
1015 1037 Specify the mimetype for embedded videos.
1016 1038 Default will be guessed from file extension, if available.
1017 1039 width : int
1018 1040 Width in pixels to which to constrain the video in HTML.
1019 1041 If not supplied, defaults to the width of the video.
1020 1042 height : int
1021 1043 Height in pixels to which to constrain the video in html.
1022 1044 If not supplied, defaults to the height of the video.
1023 1045
1024 1046 Examples
1025 1047 --------
1026 1048
1027 1049 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1028 1050 Video('path/to/video.mp4')
1029 1051 Video('path/to/video.mp4', embed=True)
1030 1052 Video(b'raw-videodata', embed=True)
1031 1053 """
1032 1054 if isinstance(data, (Path, PurePath)):
1033 1055 data = str(data)
1034 1056
1035 1057 if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
1036 1058 url = data
1037 1059 data = None
1038 1060 elif os.path.exists(data):
1039 1061 filename = data
1040 1062 data = None
1041 1063
1042 1064 if data and not embed:
1043 1065 msg = ''.join([
1044 1066 "To embed videos, you must pass embed=True ",
1045 1067 "(this may make your notebook files huge)\n",
1046 1068 "Consider passing Video(url='...')",
1047 1069 ])
1048 1070 raise ValueError(msg)
1049 1071
1050 1072 self.mimetype = mimetype
1051 1073 self.embed = embed
1052 1074 self.width = width
1053 1075 self.height = height
1054 1076 super(Video, self).__init__(data=data, url=url, filename=filename)
1055 1077
1056 1078 def _repr_html_(self):
1057 1079 width = height = ''
1058 1080 if self.width:
1059 1081 width = ' width="%d"' % self.width
1060 1082 if self.height:
1061 1083 height = ' height="%d"' % self.height
1062 1084
1063 1085 # External URLs and potentially local files are not embedded into the
1064 1086 # notebook output.
1065 1087 if not self.embed:
1066 1088 url = self.url if self.url is not None else self.filename
1067 1089 output = """<video src="{0}" controls {1} {2}>
1068 1090 Your browser does not support the <code>video</code> element.
1069 1091 </video>""".format(url, width, height)
1070 1092 return output
1071 1093
1072 1094 # Embedded videos are base64-encoded.
1073 1095 mimetype = self.mimetype
1074 1096 if self.filename is not None:
1075 1097 if not mimetype:
1076 1098 mimetype, _ = mimetypes.guess_type(self.filename)
1077 1099
1078 1100 with open(self.filename, 'rb') as f:
1079 1101 video = f.read()
1080 1102 else:
1081 1103 video = self.data
1082 1104 if isinstance(video, str):
1083 1105 # unicode input is already b64-encoded
1084 1106 b64_video = video
1085 1107 else:
1086 1108 b64_video = b2a_base64(video).decode('ascii').rstrip()
1087 1109
1088 1110 output = """<video controls {0} {1}>
1089 1111 <source src="data:{2};base64,{3}" type="{2}">
1090 1112 Your browser does not support the video tag.
1091 1113 </video>""".format(width, height, mimetype, b64_video)
1092 1114 return output
1093 1115
1094 1116 def reload(self):
1095 1117 # TODO
1096 1118 pass
1097 1119
1098 1120
1099 1121 @skip_doctest
1100 1122 def set_matplotlib_formats(*formats, **kwargs):
1101 1123 """Select figure formats for the inline backend. Optionally pass quality for JPEG.
1102 1124
1103 1125 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
1104 1126
1105 1127 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
1106 1128
1107 1129 To set this in your config files use the following::
1108 1130
1109 1131 c.InlineBackend.figure_formats = {'png', 'jpeg'}
1110 1132 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
1111 1133
1112 1134 Parameters
1113 1135 ----------
1114 1136 *formats : strs
1115 1137 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
1116 1138 **kwargs :
1117 1139 Keyword args will be relayed to ``figure.canvas.print_figure``.
1118 1140 """
1119 1141 from IPython.core.interactiveshell import InteractiveShell
1120 1142 from IPython.core.pylabtools import select_figure_formats
1121 1143 # build kwargs, starting with InlineBackend config
1122 1144 kw = {}
1123 1145 from ipykernel.pylab.config import InlineBackend
1124 1146 cfg = InlineBackend.instance()
1125 1147 kw.update(cfg.print_figure_kwargs)
1126 1148 kw.update(**kwargs)
1127 1149 shell = InteractiveShell.instance()
1128 1150 select_figure_formats(shell, formats, **kw)
1129 1151
1130 1152 @skip_doctest
1131 1153 def set_matplotlib_close(close=True):
1132 1154 """Set whether the inline backend closes all figures automatically or not.
1133 1155
1134 1156 By default, the inline backend used in the IPython Notebook will close all
1135 1157 matplotlib figures automatically after each cell is run. This means that
1136 1158 plots in different cells won't interfere. Sometimes, you may want to make
1137 1159 a plot in one cell and then refine it in later cells. This can be accomplished
1138 1160 by::
1139 1161
1140 1162 In [1]: set_matplotlib_close(False)
1141 1163
1142 1164 To set this in your config files use the following::
1143 1165
1144 1166 c.InlineBackend.close_figures = False
1145 1167
1146 1168 Parameters
1147 1169 ----------
1148 1170 close : bool
1149 1171 Should all matplotlib figures be automatically closed after each cell is
1150 1172 run?
1151 1173 """
1152 1174 from ipykernel.pylab.config import InlineBackend
1153 1175 cfg = InlineBackend.instance()
1154 1176 cfg.close_figures = close
@@ -1,138 +1,138 b''
1 1 """An interface for publishing rich data to frontends.
2 2
3 3 There are two components of the display system:
4 4
5 5 * Display formatters, which take a Python object and compute the
6 6 representation of the object in various formats (text, HTML, SVG, etc.).
7 7 * The display publisher that is used to send the representation data to the
8 8 various frontends.
9 9
10 10 This module defines the logic display publishing. The display publisher uses
11 11 the ``display_data`` message type that is defined in the IPython messaging
12 12 spec.
13 13 """
14 14
15 15 # Copyright (c) IPython Development Team.
16 16 # Distributed under the terms of the Modified BSD License.
17 17
18 18
19 19 import sys
20 20
21 21 from traitlets.config.configurable import Configurable
22 22 from traitlets import List
23 23
24 24 # This used to be defined here - it is imported for backwards compatibility
25 from .display import publish_display_data
25 from .display_functions import publish_display_data
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Main payload class
29 29 #-----------------------------------------------------------------------------
30 30
31 31
32 32 class DisplayPublisher(Configurable):
33 33 """A traited class that publishes display data to frontends.
34 34
35 35 Instances of this class are created by the main IPython object and should
36 36 be accessed there.
37 37 """
38 38
39 39 def __init__(self, shell=None, *args, **kwargs):
40 40 self.shell = shell
41 41 super().__init__(*args, **kwargs)
42 42
43 43 def _validate_data(self, data, metadata=None):
44 44 """Validate the display data.
45 45
46 46 Parameters
47 47 ----------
48 48 data : dict
49 49 The formata data dictionary.
50 50 metadata : dict
51 51 Any metadata for the data.
52 52 """
53 53
54 54 if not isinstance(data, dict):
55 55 raise TypeError('data must be a dict, got: %r' % data)
56 56 if metadata is not None:
57 57 if not isinstance(metadata, dict):
58 58 raise TypeError('metadata must be a dict, got: %r' % data)
59 59
60 60 # use * to indicate transient, update are keyword-only
61 61 def publish(self, data, metadata=None, source=None, *, transient=None, update=False, **kwargs) -> None:
62 62 """Publish data and metadata to all frontends.
63 63
64 64 See the ``display_data`` message in the messaging documentation for
65 65 more details about this message type.
66 66
67 67 The following MIME types are currently implemented:
68 68
69 69 * text/plain
70 70 * text/html
71 71 * text/markdown
72 72 * text/latex
73 73 * application/json
74 74 * application/javascript
75 75 * image/png
76 76 * image/jpeg
77 77 * image/svg+xml
78 78
79 79 Parameters
80 80 ----------
81 81 data : dict
82 82 A dictionary having keys that are valid MIME types (like
83 83 'text/plain' or 'image/svg+xml') and values that are the data for
84 84 that MIME type. The data itself must be a JSON'able data
85 85 structure. Minimally all data should have the 'text/plain' data,
86 86 which can be displayed by all frontends. If more than the plain
87 87 text is given, it is up to the frontend to decide which
88 88 representation to use.
89 89 metadata : dict
90 90 A dictionary for metadata related to the data. This can contain
91 91 arbitrary key, value pairs that frontends can use to interpret
92 92 the data. Metadata specific to each mime-type can be specified
93 93 in the metadata dict with the same mime-type keys as
94 94 the data itself.
95 95 source : str, deprecated
96 96 Unused.
97 97 transient: dict, keyword-only
98 98 A dictionary for transient data.
99 99 Data in this dictionary should not be persisted as part of saving this output.
100 100 Examples include 'display_id'.
101 101 update: bool, keyword-only, default: False
102 102 If True, only update existing outputs with the same display_id,
103 103 rather than creating a new output.
104 104 """
105 105
106 106 handlers = {}
107 107 if self.shell is not None:
108 108 handlers = getattr(self.shell, 'mime_renderers', {})
109 109
110 110 for mime, handler in handlers.items():
111 111 if mime in data:
112 112 handler(data[mime], metadata.get(mime, None))
113 113 return
114 114
115 115 if 'text/plain' in data:
116 116 print(data['text/plain'])
117 117
118 118 def clear_output(self, wait=False):
119 119 """Clear the output of the cell receiving output."""
120 120 print('\033[2K\r', end='')
121 121 sys.stdout.flush()
122 122 print('\033[2K\r', end='')
123 123 sys.stderr.flush()
124 124
125 125
126 126 class CapturingDisplayPublisher(DisplayPublisher):
127 127 """A DisplayPublisher that stores"""
128 128 outputs = List()
129 129
130 130 def publish(self, data, metadata=None, source=None, *, transient=None, update=False):
131 131 self.outputs.append({'data':data, 'metadata':metadata,
132 132 'transient':transient, 'update':update})
133 133
134 134 def clear_output(self, wait=False):
135 135 super(CapturingDisplayPublisher, self).clear_output(wait)
136 136
137 137 # empty the list, *do not* reassign a new list
138 138 self.outputs.clear()
@@ -1,82 +1,82 b''
1 1 """Simple magics for display formats"""
2 2 #-----------------------------------------------------------------------------
3 3 # Copyright (c) 2012 The IPython Development Team.
4 4 #
5 5 # Distributed under the terms of the Modified BSD License.
6 6 #
7 7 # The full license is in the file COPYING.txt, distributed with this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 14 # Our own packages
15 from IPython.core.display import display, Javascript, Latex, SVG, HTML, Markdown
15 from IPython.display import display, Javascript, Latex, SVG, HTML, Markdown
16 16 from IPython.core.magic import (
17 17 Magics, magics_class, cell_magic
18 18 )
19 19 from IPython.core import magic_arguments
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Magic implementation classes
23 23 #-----------------------------------------------------------------------------
24 24
25 25
26 26 @magics_class
27 27 class DisplayMagics(Magics):
28 28 """Magics for displaying various output types with literals
29 29
30 30 Defines javascript/latex/svg/html cell magics for writing
31 31 blocks in those languages, to be rendered in the frontend.
32 32 """
33 33
34 34 @cell_magic
35 35 def js(self, line, cell):
36 36 """Run the cell block of Javascript code
37 37
38 38 Alias of `%%javascript`
39 39 """
40 40 self.javascript(line, cell)
41 41
42 42 @cell_magic
43 43 def javascript(self, line, cell):
44 44 """Run the cell block of Javascript code"""
45 45 display(Javascript(cell))
46 46
47 47
48 48 @cell_magic
49 49 def latex(self, line, cell):
50 50 """Render the cell as a block of latex
51 51
52 52 The subset of latex which is support depends on the implementation in
53 53 the client. In the Jupyter Notebook, this magic only renders the subset
54 54 of latex defined by MathJax
55 55 [here](https://docs.mathjax.org/en/v2.5-latest/tex.html)."""
56 56 display(Latex(cell))
57 57
58 58 @cell_magic
59 59 def svg(self, line, cell):
60 60 """Render the cell as an SVG literal"""
61 61 display(SVG(cell))
62 62
63 63 @magic_arguments.magic_arguments()
64 64 @magic_arguments.argument(
65 65 '--isolated', action='store_true', default=False,
66 66 help="""Annotate the cell as 'isolated'.
67 67 Isolated cells are rendered inside their own <iframe> tag"""
68 68 )
69 69 @cell_magic
70 70 def html(self, line, cell):
71 71 """Render the cell as a block of HTML"""
72 72 args = magic_arguments.parse_argstring(self.html, line)
73 73 html = HTML(cell)
74 74 if args.isolated:
75 75 display(html, metadata={'text/html':{'isolated':True}})
76 76 else:
77 77 display(html)
78 78
79 79 @cell_magic
80 80 def markdown(self, line, cell):
81 81 """Render the cell as Markdown text block"""
82 82 display(Markdown(cell))
@@ -1,343 +1,343 b''
1 1 # encoding: utf-8
2 2 """
3 3 Paging capabilities for IPython.core
4 4
5 5 Notes
6 6 -----
7 7
8 8 For now this uses IPython hooks, so it can't be in IPython.utils. If we can get
9 9 rid of that dependency, we could move it there.
10 10 -----
11 11 """
12 12
13 13 # Copyright (c) IPython Development Team.
14 14 # Distributed under the terms of the Modified BSD License.
15 15
16 16
17 17 import os
18 18 import io
19 19 import re
20 20 import sys
21 21 import tempfile
22 22 import subprocess
23 23
24 24 from io import UnsupportedOperation
25 25
26 26 from IPython import get_ipython
27 from IPython.core.display import display
27 from IPython.display import display
28 28 from IPython.core.error import TryNext
29 29 from IPython.utils.data import chop
30 30 from IPython.utils.process import system
31 31 from IPython.utils.terminal import get_terminal_size
32 32 from IPython.utils import py3compat
33 33
34 34
35 35 def display_page(strng, start=0, screen_lines=25):
36 36 """Just display, no paging. screen_lines is ignored."""
37 37 if isinstance(strng, dict):
38 38 data = strng
39 39 else:
40 40 if start:
41 41 strng = u'\n'.join(strng.splitlines()[start:])
42 42 data = { 'text/plain': strng }
43 43 display(data, raw=True)
44 44
45 45
46 46 def as_hook(page_func):
47 47 """Wrap a pager func to strip the `self` arg
48 48
49 49 so it can be called as a hook.
50 50 """
51 51 return lambda self, *args, **kwargs: page_func(*args, **kwargs)
52 52
53 53
54 54 esc_re = re.compile(r"(\x1b[^m]+m)")
55 55
56 56 def page_dumb(strng, start=0, screen_lines=25):
57 57 """Very dumb 'pager' in Python, for when nothing else works.
58 58
59 59 Only moves forward, same interface as page(), except for pager_cmd and
60 60 mode.
61 61 """
62 62 if isinstance(strng, dict):
63 63 strng = strng.get('text/plain', '')
64 64 out_ln = strng.splitlines()[start:]
65 65 screens = chop(out_ln,screen_lines-1)
66 66 if len(screens) == 1:
67 67 print(os.linesep.join(screens[0]))
68 68 else:
69 69 last_escape = ""
70 70 for scr in screens[0:-1]:
71 71 hunk = os.linesep.join(scr)
72 72 print(last_escape + hunk)
73 73 if not page_more():
74 74 return
75 75 esc_list = esc_re.findall(hunk)
76 76 if len(esc_list) > 0:
77 77 last_escape = esc_list[-1]
78 78 print(last_escape + os.linesep.join(screens[-1]))
79 79
80 80 def _detect_screen_size(screen_lines_def):
81 81 """Attempt to work out the number of lines on the screen.
82 82
83 83 This is called by page(). It can raise an error (e.g. when run in the
84 84 test suite), so it's separated out so it can easily be called in a try block.
85 85 """
86 86 TERM = os.environ.get('TERM',None)
87 87 if not((TERM=='xterm' or TERM=='xterm-color') and sys.platform != 'sunos5'):
88 88 # curses causes problems on many terminals other than xterm, and
89 89 # some termios calls lock up on Sun OS5.
90 90 return screen_lines_def
91 91
92 92 try:
93 93 import termios
94 94 import curses
95 95 except ImportError:
96 96 return screen_lines_def
97 97
98 98 # There is a bug in curses, where *sometimes* it fails to properly
99 99 # initialize, and then after the endwin() call is made, the
100 100 # terminal is left in an unusable state. Rather than trying to
101 101 # check every time for this (by requesting and comparing termios
102 102 # flags each time), we just save the initial terminal state and
103 103 # unconditionally reset it every time. It's cheaper than making
104 104 # the checks.
105 105 try:
106 106 term_flags = termios.tcgetattr(sys.stdout)
107 107 except termios.error as err:
108 108 # can fail on Linux 2.6, pager_page will catch the TypeError
109 109 raise TypeError('termios error: {0}'.format(err))
110 110
111 111 try:
112 112 scr = curses.initscr()
113 113 except AttributeError:
114 114 # Curses on Solaris may not be complete, so we can't use it there
115 115 return screen_lines_def
116 116
117 117 screen_lines_real,screen_cols = scr.getmaxyx()
118 118 curses.endwin()
119 119
120 120 # Restore terminal state in case endwin() didn't.
121 121 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
122 122 # Now we have what we needed: the screen size in rows/columns
123 123 return screen_lines_real
124 124 #print '***Screen size:',screen_lines_real,'lines x',\
125 125 #screen_cols,'columns.' # dbg
126 126
127 127 def pager_page(strng, start=0, screen_lines=0, pager_cmd=None):
128 128 """Display a string, piping through a pager after a certain length.
129 129
130 130 strng can be a mime-bundle dict, supplying multiple representations,
131 131 keyed by mime-type.
132 132
133 133 The screen_lines parameter specifies the number of *usable* lines of your
134 134 terminal screen (total lines minus lines you need to reserve to show other
135 135 information).
136 136
137 137 If you set screen_lines to a number <=0, page() will try to auto-determine
138 138 your screen size and will only use up to (screen_size+screen_lines) for
139 139 printing, paging after that. That is, if you want auto-detection but need
140 140 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
141 141 auto-detection without any lines reserved simply use screen_lines = 0.
142 142
143 143 If a string won't fit in the allowed lines, it is sent through the
144 144 specified pager command. If none given, look for PAGER in the environment,
145 145 and ultimately default to less.
146 146
147 147 If no system pager works, the string is sent through a 'dumb pager'
148 148 written in python, very simplistic.
149 149 """
150 150
151 151 # for compatibility with mime-bundle form:
152 152 if isinstance(strng, dict):
153 153 strng = strng['text/plain']
154 154
155 155 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
156 156 TERM = os.environ.get('TERM','dumb')
157 157 if TERM in ['dumb','emacs'] and os.name != 'nt':
158 158 print(strng)
159 159 return
160 160 # chop off the topmost part of the string we don't want to see
161 161 str_lines = strng.splitlines()[start:]
162 162 str_toprint = os.linesep.join(str_lines)
163 163 num_newlines = len(str_lines)
164 164 len_str = len(str_toprint)
165 165
166 166 # Dumb heuristics to guesstimate number of on-screen lines the string
167 167 # takes. Very basic, but good enough for docstrings in reasonable
168 168 # terminals. If someone later feels like refining it, it's not hard.
169 169 numlines = max(num_newlines,int(len_str/80)+1)
170 170
171 171 screen_lines_def = get_terminal_size()[1]
172 172
173 173 # auto-determine screen size
174 174 if screen_lines <= 0:
175 175 try:
176 176 screen_lines += _detect_screen_size(screen_lines_def)
177 177 except (TypeError, UnsupportedOperation):
178 178 print(str_toprint)
179 179 return
180 180
181 181 #print 'numlines',numlines,'screenlines',screen_lines # dbg
182 182 if numlines <= screen_lines :
183 183 #print '*** normal print' # dbg
184 184 print(str_toprint)
185 185 else:
186 186 # Try to open pager and default to internal one if that fails.
187 187 # All failure modes are tagged as 'retval=1', to match the return
188 188 # value of a failed system command. If any intermediate attempt
189 189 # sets retval to 1, at the end we resort to our own page_dumb() pager.
190 190 pager_cmd = get_pager_cmd(pager_cmd)
191 191 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
192 192 if os.name == 'nt':
193 193 if pager_cmd.startswith('type'):
194 194 # The default WinXP 'type' command is failing on complex strings.
195 195 retval = 1
196 196 else:
197 197 fd, tmpname = tempfile.mkstemp('.txt')
198 198 try:
199 199 os.close(fd)
200 200 with open(tmpname, 'wt') as tmpfile:
201 201 tmpfile.write(strng)
202 202 cmd = "%s < %s" % (pager_cmd, tmpname)
203 203 # tmpfile needs to be closed for windows
204 204 if os.system(cmd):
205 205 retval = 1
206 206 else:
207 207 retval = None
208 208 finally:
209 209 os.remove(tmpname)
210 210 else:
211 211 try:
212 212 retval = None
213 213 # Emulate os.popen, but redirect stderr
214 214 proc = subprocess.Popen(pager_cmd,
215 215 shell=True,
216 216 stdin=subprocess.PIPE,
217 217 stderr=subprocess.DEVNULL
218 218 )
219 219 pager = os._wrap_close(io.TextIOWrapper(proc.stdin), proc)
220 220 try:
221 221 pager_encoding = pager.encoding or sys.stdout.encoding
222 222 pager.write(strng)
223 223 finally:
224 224 retval = pager.close()
225 225 except IOError as msg: # broken pipe when user quits
226 226 if msg.args == (32, 'Broken pipe'):
227 227 retval = None
228 228 else:
229 229 retval = 1
230 230 except OSError:
231 231 # Other strange problems, sometimes seen in Win2k/cygwin
232 232 retval = 1
233 233 if retval is not None:
234 234 page_dumb(strng,screen_lines=screen_lines)
235 235
236 236
237 237 def page(data, start=0, screen_lines=0, pager_cmd=None):
238 238 """Display content in a pager, piping through a pager after a certain length.
239 239
240 240 data can be a mime-bundle dict, supplying multiple representations,
241 241 keyed by mime-type, or text.
242 242
243 243 Pager is dispatched via the `show_in_pager` IPython hook.
244 244 If no hook is registered, `pager_page` will be used.
245 245 """
246 246 # Some routines may auto-compute start offsets incorrectly and pass a
247 247 # negative value. Offset to 0 for robustness.
248 248 start = max(0, start)
249 249
250 250 # first, try the hook
251 251 ip = get_ipython()
252 252 if ip:
253 253 try:
254 254 ip.hooks.show_in_pager(data, start=start, screen_lines=screen_lines)
255 255 return
256 256 except TryNext:
257 257 pass
258 258
259 259 # fallback on default pager
260 260 return pager_page(data, start, screen_lines, pager_cmd)
261 261
262 262
263 263 def page_file(fname, start=0, pager_cmd=None):
264 264 """Page a file, using an optional pager command and starting line.
265 265 """
266 266
267 267 pager_cmd = get_pager_cmd(pager_cmd)
268 268 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
269 269
270 270 try:
271 271 if os.environ['TERM'] in ['emacs','dumb']:
272 272 raise EnvironmentError
273 273 system(pager_cmd + ' ' + fname)
274 274 except:
275 275 try:
276 276 if start > 0:
277 277 start -= 1
278 278 page(open(fname).read(),start)
279 279 except:
280 280 print('Unable to show file',repr(fname))
281 281
282 282
283 283 def get_pager_cmd(pager_cmd=None):
284 284 """Return a pager command.
285 285
286 286 Makes some attempts at finding an OS-correct one.
287 287 """
288 288 if os.name == 'posix':
289 289 default_pager_cmd = 'less -R' # -R for color control sequences
290 290 elif os.name in ['nt','dos']:
291 291 default_pager_cmd = 'type'
292 292
293 293 if pager_cmd is None:
294 294 try:
295 295 pager_cmd = os.environ['PAGER']
296 296 except:
297 297 pager_cmd = default_pager_cmd
298 298
299 299 if pager_cmd == 'less' and '-r' not in os.environ.get('LESS', '').lower():
300 300 pager_cmd += ' -R'
301 301
302 302 return pager_cmd
303 303
304 304
305 305 def get_pager_start(pager, start):
306 306 """Return the string for paging files with an offset.
307 307
308 308 This is the '+N' argument which less and more (under Unix) accept.
309 309 """
310 310
311 311 if pager in ['less','more']:
312 312 if start:
313 313 start_string = '+' + str(start)
314 314 else:
315 315 start_string = ''
316 316 else:
317 317 start_string = ''
318 318 return start_string
319 319
320 320
321 321 # (X)emacs on win32 doesn't like to be bypassed with msvcrt.getch()
322 322 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
323 323 import msvcrt
324 324 def page_more():
325 325 """ Smart pausing between pages
326 326
327 327 @return: True if need print more lines, False if quit
328 328 """
329 329 sys.stdout.write('---Return to continue, q to quit--- ')
330 330 ans = msvcrt.getwch()
331 331 if ans in ("q", "Q"):
332 332 result = False
333 333 else:
334 334 result = True
335 335 sys.stdout.write("\b"*37 + " "*37 + "\b"*37)
336 336 return result
337 337 else:
338 338 def page_more():
339 339 ans = py3compat.input('---Return to continue, q to quit--- ')
340 340 if ans.lower().startswith('q'):
341 341 return False
342 342 else:
343 343 return True
@@ -1,419 +1,419 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Pylab (matplotlib) support utilities."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from io import BytesIO
8 8
9 9 from IPython.core.display import _pngxy
10 10 from IPython.utils.decorators import flag_calls
11 11
12 12 # If user specifies a GUI, that dictates the backend, otherwise we read the
13 13 # user's mpl default from the mpl rc structure
14 14 backends = {'tk': 'TkAgg',
15 15 'gtk': 'GTKAgg',
16 16 'gtk3': 'GTK3Agg',
17 17 'wx': 'WXAgg',
18 18 'qt4': 'Qt4Agg',
19 19 'qt5': 'Qt5Agg',
20 20 'qt': 'Qt5Agg',
21 21 'osx': 'MacOSX',
22 22 'nbagg': 'nbAgg',
23 23 'notebook': 'nbAgg',
24 24 'agg': 'agg',
25 25 'svg': 'svg',
26 26 'pdf': 'pdf',
27 27 'ps': 'ps',
28 28 'inline': 'module://ipykernel.pylab.backend_inline',
29 29 'ipympl': 'module://ipympl.backend_nbagg',
30 30 'widget': 'module://ipympl.backend_nbagg',
31 31 }
32 32
33 33 # We also need a reverse backends2guis mapping that will properly choose which
34 34 # GUI support to activate based on the desired matplotlib backend. For the
35 35 # most part it's just a reverse of the above dict, but we also need to add a
36 36 # few others that map to the same GUI manually:
37 37 backend2gui = dict(zip(backends.values(), backends.keys()))
38 38 # Our tests expect backend2gui to just return 'qt'
39 39 backend2gui['Qt4Agg'] = 'qt'
40 40 # In the reverse mapping, there are a few extra valid matplotlib backends that
41 41 # map to the same GUI support
42 42 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
43 43 backend2gui['GTK3Cairo'] = 'gtk3'
44 44 backend2gui['WX'] = 'wx'
45 45 backend2gui['CocoaAgg'] = 'osx'
46 46 # And some backends that don't need GUI integration
47 47 del backend2gui['nbAgg']
48 48 del backend2gui['agg']
49 49 del backend2gui['svg']
50 50 del backend2gui['pdf']
51 51 del backend2gui['ps']
52 52 del backend2gui['module://ipykernel.pylab.backend_inline']
53 53
54 54 #-----------------------------------------------------------------------------
55 55 # Matplotlib utilities
56 56 #-----------------------------------------------------------------------------
57 57
58 58
59 59 def getfigs(*fig_nums):
60 60 """Get a list of matplotlib figures by figure numbers.
61 61
62 62 If no arguments are given, all available figures are returned. If the
63 63 argument list contains references to invalid figures, a warning is printed
64 64 but the function continues pasting further figures.
65 65
66 66 Parameters
67 67 ----------
68 68 figs : tuple
69 69 A tuple of ints giving the figure numbers of the figures to return.
70 70 """
71 71 from matplotlib._pylab_helpers import Gcf
72 72 if not fig_nums:
73 73 fig_managers = Gcf.get_all_fig_managers()
74 74 return [fm.canvas.figure for fm in fig_managers]
75 75 else:
76 76 figs = []
77 77 for num in fig_nums:
78 78 f = Gcf.figs.get(num)
79 79 if f is None:
80 80 print('Warning: figure %s not available.' % num)
81 81 else:
82 82 figs.append(f.canvas.figure)
83 83 return figs
84 84
85 85
86 86 def figsize(sizex, sizey):
87 87 """Set the default figure size to be [sizex, sizey].
88 88
89 89 This is just an easy to remember, convenience wrapper that sets::
90 90
91 91 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
92 92 """
93 93 import matplotlib
94 94 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
95 95
96 96
97 97 def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs):
98 98 """Print a figure to an image, and return the resulting file data
99 99
100 100 Returned data will be bytes unless ``fmt='svg'``,
101 101 in which case it will be unicode.
102 102
103 103 Any keyword args are passed to fig.canvas.print_figure,
104 104 such as ``quality`` or ``bbox_inches``.
105 105 """
106 106 # When there's an empty figure, we shouldn't return anything, otherwise we
107 107 # get big blank areas in the qt console.
108 108 if not fig.axes and not fig.lines:
109 109 return
110 110
111 111 dpi = fig.dpi
112 112 if fmt == 'retina':
113 113 dpi = dpi * 2
114 114 fmt = 'png'
115 115
116 116 # build keyword args
117 117 kw = {
118 118 "format":fmt,
119 119 "facecolor":fig.get_facecolor(),
120 120 "edgecolor":fig.get_edgecolor(),
121 121 "dpi":dpi,
122 122 "bbox_inches":bbox_inches,
123 123 }
124 124 # **kwargs get higher priority
125 125 kw.update(kwargs)
126 126
127 127 bytes_io = BytesIO()
128 128 if fig.canvas is None:
129 129 from matplotlib.backend_bases import FigureCanvasBase
130 130 FigureCanvasBase(fig)
131 131
132 132 fig.canvas.print_figure(bytes_io, **kw)
133 133 data = bytes_io.getvalue()
134 134 if fmt == 'svg':
135 135 data = data.decode('utf-8')
136 136 return data
137 137
138 138 def retina_figure(fig, **kwargs):
139 139 """format a figure as a pixel-doubled (retina) PNG"""
140 140 pngdata = print_figure(fig, fmt='retina', **kwargs)
141 141 # Make sure that retina_figure acts just like print_figure and returns
142 142 # None when the figure is empty.
143 143 if pngdata is None:
144 144 return
145 145 w, h = _pngxy(pngdata)
146 146 metadata = {"width": w//2, "height":h//2}
147 147 return pngdata, metadata
148 148
149 149 # We need a little factory function here to create the closure where
150 150 # safe_execfile can live.
151 151 def mpl_runner(safe_execfile):
152 152 """Factory to return a matplotlib-enabled runner for %run.
153 153
154 154 Parameters
155 155 ----------
156 156 safe_execfile : function
157 157 This must be a function with the same interface as the
158 158 :meth:`safe_execfile` method of IPython.
159 159
160 160 Returns
161 161 -------
162 162 A function suitable for use as the ``runner`` argument of the %run magic
163 163 function.
164 164 """
165 165
166 166 def mpl_execfile(fname,*where,**kw):
167 167 """matplotlib-aware wrapper around safe_execfile.
168 168
169 169 Its interface is identical to that of the :func:`execfile` builtin.
170 170
171 171 This is ultimately a call to execfile(), but wrapped in safeties to
172 172 properly handle interactive rendering."""
173 173
174 174 import matplotlib
175 175 import matplotlib.pyplot as plt
176 176
177 177 #print '*** Matplotlib runner ***' # dbg
178 178 # turn off rendering until end of script
179 179 is_interactive = matplotlib.rcParams['interactive']
180 180 matplotlib.interactive(False)
181 181 safe_execfile(fname,*where,**kw)
182 182 matplotlib.interactive(is_interactive)
183 183 # make rendering call now, if the user tried to do it
184 184 if plt.draw_if_interactive.called:
185 185 plt.draw()
186 186 plt.draw_if_interactive.called = False
187 187
188 188 # re-draw everything that is stale
189 189 try:
190 190 da = plt.draw_all
191 191 except AttributeError:
192 192 pass
193 193 else:
194 194 da()
195 195
196 196 return mpl_execfile
197 197
198 198
199 199 def _reshow_nbagg_figure(fig):
200 200 """reshow an nbagg figure"""
201 201 try:
202 202 reshow = fig.canvas.manager.reshow
203 203 except AttributeError:
204 204 raise NotImplementedError()
205 205 else:
206 206 reshow()
207 207
208 208
209 209 def select_figure_formats(shell, formats, **kwargs):
210 210 """Select figure formats for the inline backend.
211 211
212 212 Parameters
213 213 ==========
214 214 shell : InteractiveShell
215 215 The main IPython instance.
216 216 formats : str or set
217 217 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
218 218 **kwargs : any
219 219 Extra keyword arguments to be passed to fig.canvas.print_figure.
220 220 """
221 221 import matplotlib
222 222 from matplotlib.figure import Figure
223 223
224 224 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
225 225 png_formatter = shell.display_formatter.formatters['image/png']
226 226 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
227 227 pdf_formatter = shell.display_formatter.formatters['application/pdf']
228 228
229 229 if isinstance(formats, str):
230 230 formats = {formats}
231 231 # cast in case of list / tuple
232 232 formats = set(formats)
233 233
234 234 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
235 235 mplbackend = matplotlib.get_backend().lower()
236 236 if mplbackend == 'nbagg' or mplbackend == 'module://ipympl.backend_nbagg':
237 237 formatter = shell.display_formatter.ipython_display_formatter
238 238 formatter.for_type(Figure, _reshow_nbagg_figure)
239 239
240 240 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
241 241 bad = formats.difference(supported)
242 242 if bad:
243 243 bs = "%s" % ','.join([repr(f) for f in bad])
244 244 gs = "%s" % ','.join([repr(f) for f in supported])
245 245 raise ValueError("supported formats are: %s not %s" % (gs, bs))
246 246
247 247 if 'png' in formats:
248 248 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
249 249 if 'retina' in formats or 'png2x' in formats:
250 250 png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))
251 251 if 'jpg' in formats or 'jpeg' in formats:
252 252 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', **kwargs))
253 253 if 'svg' in formats:
254 254 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg', **kwargs))
255 255 if 'pdf' in formats:
256 256 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf', **kwargs))
257 257
258 258 #-----------------------------------------------------------------------------
259 259 # Code for initializing matplotlib and importing pylab
260 260 #-----------------------------------------------------------------------------
261 261
262 262
263 263 def find_gui_and_backend(gui=None, gui_select=None):
264 264 """Given a gui string return the gui and mpl backend.
265 265
266 266 Parameters
267 267 ----------
268 268 gui : str
269 269 Can be one of ('tk','gtk','wx','qt','qt4','inline','agg').
270 270 gui_select : str
271 271 Can be one of ('tk','gtk','wx','qt','qt4','inline').
272 272 This is any gui already selected by the shell.
273 273
274 274 Returns
275 275 -------
276 276 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
277 277 'WXAgg','Qt4Agg','module://ipykernel.pylab.backend_inline','agg').
278 278 """
279 279
280 280 import matplotlib
281 281
282 282 if gui and gui != 'auto':
283 283 # select backend based on requested gui
284 284 backend = backends[gui]
285 285 if gui == 'agg':
286 286 gui = None
287 287 else:
288 288 # We need to read the backend from the original data structure, *not*
289 289 # from mpl.rcParams, since a prior invocation of %matplotlib may have
290 290 # overwritten that.
291 291 # WARNING: this assumes matplotlib 1.1 or newer!!
292 292 backend = matplotlib.rcParamsOrig['backend']
293 293 # In this case, we need to find what the appropriate gui selection call
294 294 # should be for IPython, so we can activate inputhook accordingly
295 295 gui = backend2gui.get(backend, None)
296 296
297 297 # If we have already had a gui active, we need it and inline are the
298 298 # ones allowed.
299 299 if gui_select and gui != gui_select:
300 300 gui = gui_select
301 301 backend = backends[gui]
302 302
303 303 return gui, backend
304 304
305 305
306 306 def activate_matplotlib(backend):
307 307 """Activate the given backend and set interactive to True."""
308 308
309 309 import matplotlib
310 310 matplotlib.interactive(True)
311 311
312 312 # Matplotlib had a bug where even switch_backend could not force
313 313 # the rcParam to update. This needs to be set *before* the module
314 314 # magic of switch_backend().
315 315 matplotlib.rcParams['backend'] = backend
316 316
317 317 # Due to circular imports, pyplot may be only partially initialised
318 318 # when this function runs.
319 319 # So avoid needing matplotlib attribute-lookup to access pyplot.
320 320 from matplotlib import pyplot as plt
321 321
322 322 plt.switch_backend(backend)
323 323
324 324 plt.show._needmain = False
325 325 # We need to detect at runtime whether show() is called by the user.
326 326 # For this, we wrap it into a decorator which adds a 'called' flag.
327 327 plt.draw_if_interactive = flag_calls(plt.draw_if_interactive)
328 328
329 329
330 330 def import_pylab(user_ns, import_all=True):
331 331 """Populate the namespace with pylab-related values.
332 332
333 333 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
334 334
335 335 Also imports a few names from IPython (figsize, display, getfigs)
336 336
337 337 """
338 338
339 339 # Import numpy as np/pyplot as plt are conventions we're trying to
340 340 # somewhat standardize on. Making them available to users by default
341 341 # will greatly help this.
342 342 s = ("import numpy\n"
343 343 "import matplotlib\n"
344 344 "from matplotlib import pylab, mlab, pyplot\n"
345 345 "np = numpy\n"
346 346 "plt = pyplot\n"
347 347 )
348 348 exec(s, user_ns)
349 349
350 350 if import_all:
351 351 s = ("from matplotlib.pylab import *\n"
352 352 "from numpy import *\n")
353 353 exec(s, user_ns)
354 354
355 355 # IPython symbols to add
356 356 user_ns['figsize'] = figsize
357 from IPython.core.display import display
357 from IPython.display import display
358 358 # Add display and getfigs to the user's namespace
359 359 user_ns['display'] = display
360 360 user_ns['getfigs'] = getfigs
361 361
362 362
363 363 def configure_inline_support(shell, backend):
364 364 """Configure an IPython shell object for matplotlib use.
365 365
366 366 Parameters
367 367 ----------
368 368 shell : InteractiveShell instance
369 369
370 370 backend : matplotlib backend
371 371 """
372 372 # If using our svg payload backend, register the post-execution
373 373 # function that will pick up the results for display. This can only be
374 374 # done with access to the real shell object.
375 375
376 376 # Note: if we can't load the inline backend, then there's no point
377 377 # continuing (such as in terminal-only shells in environments without
378 378 # zeromq available).
379 379 try:
380 380 from ipykernel.pylab.backend_inline import InlineBackend
381 381 except ImportError:
382 382 return
383 383 import matplotlib
384 384
385 385 cfg = InlineBackend.instance(parent=shell)
386 386 cfg.shell = shell
387 387 if cfg not in shell.configurables:
388 388 shell.configurables.append(cfg)
389 389
390 390 if backend == backends['inline']:
391 391 from ipykernel.pylab.backend_inline import flush_figures
392 392 shell.events.register('post_execute', flush_figures)
393 393
394 394 # Save rcParams that will be overwrittern
395 395 shell._saved_rcParams = {}
396 396 for k in cfg.rc:
397 397 shell._saved_rcParams[k] = matplotlib.rcParams[k]
398 398 # load inline_rc
399 399 matplotlib.rcParams.update(cfg.rc)
400 400 new_backend_name = "inline"
401 401 else:
402 402 from ipykernel.pylab.backend_inline import flush_figures
403 403 try:
404 404 shell.events.unregister('post_execute', flush_figures)
405 405 except ValueError:
406 406 pass
407 407 if hasattr(shell, '_saved_rcParams'):
408 408 matplotlib.rcParams.update(shell._saved_rcParams)
409 409 del shell._saved_rcParams
410 410 new_backend_name = "other"
411 411
412 412 # only enable the formats once -> don't change the enabled formats (which the user may
413 413 # has changed) when getting another "%matplotlib inline" call.
414 414 # See https://github.com/ipython/ipykernel/issues/29
415 415 cur_backend = getattr(configure_inline_support, "current_backend", "unset")
416 416 if new_backend_name != cur_backend:
417 417 # Setup the default figure format
418 418 select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs)
419 419 configure_inline_support.current_backend = new_backend_name
@@ -1,421 +1,421 b''
1 1 # Copyright (c) IPython Development Team.
2 2 # Distributed under the terms of the Modified BSD License.
3 3
4 4 import json
5 5 import os
6 6 import warnings
7 7
8 8 from unittest import mock
9 9
10 10 import nose.tools as nt
11 11
12 from IPython.core import display
12 from IPython import display
13 13 from IPython.core.getipython import get_ipython
14 14 from IPython.utils.io import capture_output
15 15 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
16 16 from IPython import paths as ipath
17 17 from IPython.testing.tools import AssertNotPrints
18 18
19 19 import IPython.testing.decorators as dec
20 20
21 21 def test_image_size():
22 22 """Simple test for display.Image(args, width=x,height=y)"""
23 23 thisurl = 'http://www.google.fr/images/srpr/logo3w.png'
24 24 img = display.Image(url=thisurl, width=200, height=200)
25 25 nt.assert_equal(u'<img src="%s" width="200" height="200"/>' % (thisurl), img._repr_html_())
26 26 img = display.Image(url=thisurl, metadata={'width':200, 'height':200})
27 27 nt.assert_equal(u'<img src="%s" width="200" height="200"/>' % (thisurl), img._repr_html_())
28 28 img = display.Image(url=thisurl, width=200)
29 29 nt.assert_equal(u'<img src="%s" width="200"/>' % (thisurl), img._repr_html_())
30 30 img = display.Image(url=thisurl)
31 31 nt.assert_equal(u'<img src="%s"/>' % (thisurl), img._repr_html_())
32 32 img = display.Image(url=thisurl, unconfined=True)
33 33 nt.assert_equal(u'<img src="%s" class="unconfined"/>' % (thisurl), img._repr_html_())
34 34
35 35
36 36 def test_image_mimes():
37 37 fmt = get_ipython().display_formatter.format
38 38 for format in display.Image._ACCEPTABLE_EMBEDDINGS:
39 39 mime = display.Image._MIMETYPES[format]
40 40 img = display.Image(b'garbage', format=format)
41 41 data, metadata = fmt(img)
42 42 nt.assert_equal(sorted(data), sorted([mime, 'text/plain']))
43 43
44 44
45 45 def test_geojson():
46 46
47 47 gj = display.GeoJSON(data={
48 48 "type": "Feature",
49 49 "geometry": {
50 50 "type": "Point",
51 51 "coordinates": [-81.327, 296.038]
52 52 },
53 53 "properties": {
54 54 "name": "Inca City"
55 55 }
56 56 },
57 57 url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
58 58 layer_options={
59 59 "basemap_id": "celestia_mars-shaded-16k_global",
60 60 "attribution": "Celestia/praesepe",
61 61 "minZoom": 0,
62 62 "maxZoom": 18,
63 63 })
64 64 nt.assert_equal(u'<IPython.core.display.GeoJSON object>', str(gj))
65 65
66 66 def test_retina_png():
67 67 here = os.path.dirname(__file__)
68 68 img = display.Image(os.path.join(here, "2x2.png"), retina=True)
69 69 nt.assert_equal(img.height, 1)
70 70 nt.assert_equal(img.width, 1)
71 71 data, md = img._repr_png_()
72 72 nt.assert_equal(md['width'], 1)
73 73 nt.assert_equal(md['height'], 1)
74 74
75 75 def test_retina_jpeg():
76 76 here = os.path.dirname(__file__)
77 77 img = display.Image(os.path.join(here, "2x2.jpg"), retina=True)
78 78 nt.assert_equal(img.height, 1)
79 79 nt.assert_equal(img.width, 1)
80 80 data, md = img._repr_jpeg_()
81 81 nt.assert_equal(md['width'], 1)
82 82 nt.assert_equal(md['height'], 1)
83 83
84 84 def test_base64image():
85 85 display.Image("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94BCRQnOqNu0b4AAAAKSURBVAjXY2AAAAACAAHiIbwzAAAAAElFTkSuQmCC")
86 86
87 87 def test_image_filename_defaults():
88 88 '''test format constraint, and validity of jpeg and png'''
89 89 tpath = ipath.get_ipython_package_dir()
90 90 nt.assert_raises(ValueError, display.Image, filename=os.path.join(tpath, 'testing/tests/badformat.zip'),
91 91 embed=True)
92 92 nt.assert_raises(ValueError, display.Image)
93 93 nt.assert_raises(ValueError, display.Image, data='this is not an image', format='badformat', embed=True)
94 94 # check boths paths to allow packages to test at build and install time
95 95 imgfile = os.path.join(tpath, 'core/tests/2x2.png')
96 96 img = display.Image(filename=imgfile)
97 97 nt.assert_equal('png', img.format)
98 98 nt.assert_is_not_none(img._repr_png_())
99 99 img = display.Image(filename=os.path.join(tpath, 'testing/tests/logo.jpg'), embed=False)
100 100 nt.assert_equal('jpeg', img.format)
101 101 nt.assert_is_none(img._repr_jpeg_())
102 102
103 103 def _get_inline_config():
104 104 from ipykernel.pylab.config import InlineBackend
105 105 return InlineBackend.instance()
106 106
107 107 @dec.skip_without('matplotlib')
108 108 def test_set_matplotlib_close():
109 109 cfg = _get_inline_config()
110 110 cfg.close_figures = False
111 111 display.set_matplotlib_close()
112 112 assert cfg.close_figures
113 113 display.set_matplotlib_close(False)
114 114 assert not cfg.close_figures
115 115
116 116 _fmt_mime_map = {
117 117 'png': 'image/png',
118 118 'jpeg': 'image/jpeg',
119 119 'pdf': 'application/pdf',
120 120 'retina': 'image/png',
121 121 'svg': 'image/svg+xml',
122 122 }
123 123
124 124 @dec.skip_without('matplotlib')
125 125 def test_set_matplotlib_formats():
126 126 from matplotlib.figure import Figure
127 127 formatters = get_ipython().display_formatter.formatters
128 128 for formats in [
129 129 ('png',),
130 130 ('pdf', 'svg'),
131 131 ('jpeg', 'retina', 'png'),
132 132 (),
133 133 ]:
134 134 active_mimes = {_fmt_mime_map[fmt] for fmt in formats}
135 135 display.set_matplotlib_formats(*formats)
136 136 for mime, f in formatters.items():
137 137 if mime in active_mimes:
138 138 nt.assert_in(Figure, f)
139 139 else:
140 140 nt.assert_not_in(Figure, f)
141 141
142 142 @dec.skip_without('matplotlib')
143 143 def test_set_matplotlib_formats_kwargs():
144 144 from matplotlib.figure import Figure
145 145 ip = get_ipython()
146 146 cfg = _get_inline_config()
147 147 cfg.print_figure_kwargs.update(dict(foo='bar'))
148 148 kwargs = dict(quality=10)
149 149 display.set_matplotlib_formats('png', **kwargs)
150 150 formatter = ip.display_formatter.formatters['image/png']
151 151 f = formatter.lookup_by_type(Figure)
152 152 cell = f.__closure__[0].cell_contents
153 153 expected = kwargs
154 154 expected.update(cfg.print_figure_kwargs)
155 155 nt.assert_equal(cell, expected)
156 156
157 157 def test_display_available():
158 158 """
159 159 Test that display is available without import
160 160
161 161 We don't really care if it's in builtin or anything else, but it should
162 162 always be available.
163 163 """
164 164 ip = get_ipython()
165 165 with AssertNotPrints('NameError'):
166 166 ip.run_cell('display')
167 167 try:
168 168 ip.run_cell('del display')
169 169 except NameError:
170 170 pass # it's ok, it might be in builtins
171 171 # even if deleted it should be back
172 172 with AssertNotPrints('NameError'):
173 173 ip.run_cell('display')
174 174
175 175 def test_textdisplayobj_pretty_repr():
176 176 p = display.Pretty("This is a simple test")
177 177 nt.assert_equal(repr(p), '<IPython.core.display.Pretty object>')
178 178 nt.assert_equal(p.data, 'This is a simple test')
179 179
180 180 p._show_mem_addr = True
181 181 nt.assert_equal(repr(p), object.__repr__(p))
182 182
183 183 def test_displayobject_repr():
184 184 h = display.HTML('<br />')
185 185 nt.assert_equal(repr(h), '<IPython.core.display.HTML object>')
186 186 h._show_mem_addr = True
187 187 nt.assert_equal(repr(h), object.__repr__(h))
188 188 h._show_mem_addr = False
189 189 nt.assert_equal(repr(h), '<IPython.core.display.HTML object>')
190 190
191 191 j = display.Javascript('')
192 192 nt.assert_equal(repr(j), '<IPython.core.display.Javascript object>')
193 193 j._show_mem_addr = True
194 194 nt.assert_equal(repr(j), object.__repr__(j))
195 195 j._show_mem_addr = False
196 196 nt.assert_equal(repr(j), '<IPython.core.display.Javascript object>')
197 197
198 198 @mock.patch('warnings.warn')
199 199 def test_encourage_iframe_over_html(m_warn):
200 200 display.HTML()
201 201 m_warn.assert_not_called()
202 202
203 203 display.HTML('<br />')
204 204 m_warn.assert_not_called()
205 205
206 206 display.HTML('<html><p>Lots of content here</p><iframe src="http://a.com"></iframe>')
207 207 m_warn.assert_not_called()
208 208
209 209 display.HTML('<iframe src="http://a.com"></iframe>')
210 210 m_warn.assert_called_with('Consider using IPython.display.IFrame instead')
211 211
212 212 m_warn.reset_mock()
213 213 display.HTML('<IFRAME SRC="http://a.com"></IFRAME>')
214 214 m_warn.assert_called_with('Consider using IPython.display.IFrame instead')
215 215
216 216 def test_progress():
217 217 p = display.ProgressBar(10)
218 218 nt.assert_in('0/10',repr(p))
219 219 p.html_width = '100%'
220 220 p.progress = 5
221 221 nt.assert_equal(p._repr_html_(), "<progress style='width:100%' max='10' value='5'></progress>")
222 222
223 223 def test_progress_iter():
224 224 with capture_output(display=False) as captured:
225 225 for i in display.ProgressBar(5):
226 226 out = captured.stdout
227 227 nt.assert_in('{0}/5'.format(i), out)
228 228 out = captured.stdout
229 229 nt.assert_in('5/5', out)
230 230
231 231 def test_json():
232 232 d = {'a': 5}
233 233 lis = [d]
234 234 metadata = [
235 235 {'expanded': False, 'root': 'root'},
236 236 {'expanded': True, 'root': 'root'},
237 237 {'expanded': False, 'root': 'custom'},
238 238 {'expanded': True, 'root': 'custom'},
239 239 ]
240 240 json_objs = [
241 241 display.JSON(d),
242 242 display.JSON(d, expanded=True),
243 243 display.JSON(d, root='custom'),
244 244 display.JSON(d, expanded=True, root='custom'),
245 245 ]
246 246 for j, md in zip(json_objs, metadata):
247 247 nt.assert_equal(j._repr_json_(), (d, md))
248 248
249 249 with warnings.catch_warnings(record=True) as w:
250 250 warnings.simplefilter("always")
251 251 j = display.JSON(json.dumps(d))
252 252 nt.assert_equal(len(w), 1)
253 253 nt.assert_equal(j._repr_json_(), (d, metadata[0]))
254 254
255 255 json_objs = [
256 256 display.JSON(lis),
257 257 display.JSON(lis, expanded=True),
258 258 display.JSON(lis, root='custom'),
259 259 display.JSON(lis, expanded=True, root='custom'),
260 260 ]
261 261 for j, md in zip(json_objs, metadata):
262 262 nt.assert_equal(j._repr_json_(), (lis, md))
263 263
264 264 with warnings.catch_warnings(record=True) as w:
265 265 warnings.simplefilter("always")
266 266 j = display.JSON(json.dumps(lis))
267 267 nt.assert_equal(len(w), 1)
268 268 nt.assert_equal(j._repr_json_(), (lis, metadata[0]))
269 269
270 270 def test_video_embedding():
271 271 """use a tempfile, with dummy-data, to ensure that video embedding doesn't crash"""
272 272 v = display.Video("http://ignored")
273 273 assert not v.embed
274 274 html = v._repr_html_()
275 275 nt.assert_not_in('src="data:', html)
276 276 nt.assert_in('src="http://ignored"', html)
277 277
278 278 with nt.assert_raises(ValueError):
279 279 v = display.Video(b'abc')
280 280
281 281 with NamedFileInTemporaryDirectory('test.mp4') as f:
282 282 f.write(b'abc')
283 283 f.close()
284 284
285 285 v = display.Video(f.name)
286 286 assert not v.embed
287 287 html = v._repr_html_()
288 288 nt.assert_not_in('src="data:', html)
289 289
290 290 v = display.Video(f.name, embed=True)
291 291 html = v._repr_html_()
292 292 nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
293 293
294 294 v = display.Video(f.name, embed=True, mimetype='video/other')
295 295 html = v._repr_html_()
296 296 nt.assert_in('src="data:video/other;base64,YWJj"',html)
297 297
298 298 v = display.Video(b'abc', embed=True, mimetype='video/mp4')
299 299 html = v._repr_html_()
300 300 nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
301 301
302 302 v = display.Video(u'YWJj', embed=True, mimetype='video/xyz')
303 303 html = v._repr_html_()
304 304 nt.assert_in('src="data:video/xyz;base64,YWJj"',html)
305 305
306 306 def test_html_metadata():
307 307 s = "<h1>Test</h1>"
308 308 h = display.HTML(s, metadata={"isolated": True})
309 309 nt.assert_equal(h._repr_html_(), (s, {"isolated": True}))
310 310
311 311 def test_display_id():
312 312 ip = get_ipython()
313 313 with mock.patch.object(ip.display_pub, 'publish') as pub:
314 314 handle = display.display('x')
315 315 nt.assert_is(handle, None)
316 316 handle = display.display('y', display_id='secret')
317 317 nt.assert_is_instance(handle, display.DisplayHandle)
318 318 handle2 = display.display('z', display_id=True)
319 319 nt.assert_is_instance(handle2, display.DisplayHandle)
320 320 nt.assert_not_equal(handle.display_id, handle2.display_id)
321 321
322 322 nt.assert_equal(pub.call_count, 3)
323 323 args, kwargs = pub.call_args_list[0]
324 324 nt.assert_equal(args, ())
325 325 nt.assert_equal(kwargs, {
326 326 'data': {
327 327 'text/plain': repr('x')
328 328 },
329 329 'metadata': {},
330 330 })
331 331 args, kwargs = pub.call_args_list[1]
332 332 nt.assert_equal(args, ())
333 333 nt.assert_equal(kwargs, {
334 334 'data': {
335 335 'text/plain': repr('y')
336 336 },
337 337 'metadata': {},
338 338 'transient': {
339 339 'display_id': handle.display_id,
340 340 },
341 341 })
342 342 args, kwargs = pub.call_args_list[2]
343 343 nt.assert_equal(args, ())
344 344 nt.assert_equal(kwargs, {
345 345 'data': {
346 346 'text/plain': repr('z')
347 347 },
348 348 'metadata': {},
349 349 'transient': {
350 350 'display_id': handle2.display_id,
351 351 },
352 352 })
353 353
354 354
355 355 def test_update_display():
356 356 ip = get_ipython()
357 357 with mock.patch.object(ip.display_pub, 'publish') as pub:
358 358 with nt.assert_raises(TypeError):
359 359 display.update_display('x')
360 360 display.update_display('x', display_id='1')
361 361 display.update_display('y', display_id='2')
362 362 args, kwargs = pub.call_args_list[0]
363 363 nt.assert_equal(args, ())
364 364 nt.assert_equal(kwargs, {
365 365 'data': {
366 366 'text/plain': repr('x')
367 367 },
368 368 'metadata': {},
369 369 'transient': {
370 370 'display_id': '1',
371 371 },
372 372 'update': True,
373 373 })
374 374 args, kwargs = pub.call_args_list[1]
375 375 nt.assert_equal(args, ())
376 376 nt.assert_equal(kwargs, {
377 377 'data': {
378 378 'text/plain': repr('y')
379 379 },
380 380 'metadata': {},
381 381 'transient': {
382 382 'display_id': '2',
383 383 },
384 384 'update': True,
385 385 })
386 386
387 387
388 388 def test_display_handle():
389 389 ip = get_ipython()
390 390 handle = display.DisplayHandle()
391 391 nt.assert_is_instance(handle.display_id, str)
392 392 handle = display.DisplayHandle('my-id')
393 393 nt.assert_equal(handle.display_id, 'my-id')
394 394 with mock.patch.object(ip.display_pub, 'publish') as pub:
395 395 handle.display('x')
396 396 handle.update('y')
397 397
398 398 args, kwargs = pub.call_args_list[0]
399 399 nt.assert_equal(args, ())
400 400 nt.assert_equal(kwargs, {
401 401 'data': {
402 402 'text/plain': repr('x')
403 403 },
404 404 'metadata': {},
405 405 'transient': {
406 406 'display_id': handle.display_id,
407 407 }
408 408 })
409 409 args, kwargs = pub.call_args_list[1]
410 410 nt.assert_equal(args, ())
411 411 nt.assert_equal(kwargs, {
412 412 'data': {
413 413 'text/plain': repr('y')
414 414 },
415 415 'metadata': {},
416 416 'transient': {
417 417 'display_id': handle.display_id,
418 418 },
419 419 'update': True,
420 420 })
421 421
@@ -1,16 +1,44 b''
1 1 """Public API for display tools in IPython.
2 2 """
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (C) 2012 The IPython Development Team
6 6 #
7 7 # Distributed under the terms of the BSD License. The full license is in
8 8 # the file COPYING, distributed as part of this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Imports
13 13 #-----------------------------------------------------------------------------
14 14
15 from IPython.core.display import *
15 from IPython.core.display_functions import *
16 from IPython.core.display import (
17 display_pretty,
18 display_html,
19 display_markdown,
20 display_svg,
21 display_png,
22 display_jpeg,
23 display_latex,
24 display_json,
25 display_javascript,
26 display_pdf,
27 DisplayObject,
28 TextDisplayObject,
29 Pretty,
30 HTML,
31 Markdown,
32 Math,
33 Latex,
34 SVG,
35 ProgressBar,
36 JSON,
37 GeoJSON,
38 Javascript,
39 Image,
40 set_matplotlib_formats,
41 set_matplotlib_close,
42 Video,
43 )
16 44 from IPython.lib.display import *
@@ -1,457 +1,458 b''
1 1 # -*- coding: utf-8 -*-
2 2 """IPython Test Suite Runner.
3 3
4 4 This module provides a main entry point to a user script to test IPython
5 5 itself from the command line. There are two ways of running this script:
6 6
7 7 1. With the syntax `iptest all`. This runs our entire test suite by
8 8 calling this script (with different arguments) recursively. This
9 9 causes modules and package to be tested in different processes, using nose
10 10 or trial where appropriate.
11 11 2. With the regular nose syntax, like `iptest IPython -- -vvs`. In this form
12 12 the script simply calls nose, but with special command line flags and
13 13 plugins loaded. Options after `--` are passed to nose.
14 14
15 15 """
16 16
17 17 # Copyright (c) IPython Development Team.
18 18 # Distributed under the terms of the Modified BSD License.
19 19
20 20
21 21 import glob
22 22 from io import BytesIO
23 23 import os
24 24 import os.path as path
25 25 import sys
26 26 from threading import Thread, Lock, Event
27 27 import warnings
28 28
29 29 import nose.plugins.builtin
30 30 from nose.plugins.xunit import Xunit
31 31 from nose import SkipTest
32 32 from nose.core import TestProgram
33 33 from nose.plugins import Plugin
34 34 from nose.util import safe_str
35 35
36 36 from IPython import version_info
37 37 from IPython.utils.py3compat import decode
38 38 from IPython.utils.importstring import import_item
39 39 from IPython.testing.plugin.ipdoctest import IPythonDoctest
40 40 from IPython.external.decorators import KnownFailure, knownfailureif
41 41
42 42 pjoin = path.join
43 43
44 44
45 45 # Enable printing all warnings raise by IPython's modules
46 46 warnings.filterwarnings('ignore', message='.*Matplotlib is building the font cache.*', category=UserWarning, module='.*')
47 47 warnings.filterwarnings('error', message='.*', category=ResourceWarning, module='.*')
48 48 warnings.filterwarnings('error', message=".*{'config': True}.*", category=DeprecationWarning, module='IPy.*')
49 49 warnings.filterwarnings('default', message='.*', category=Warning, module='IPy.*')
50 50
51 51 warnings.filterwarnings('error', message='.*apply_wrapper.*', category=DeprecationWarning, module='.*')
52 52 warnings.filterwarnings('error', message='.*make_label_dec', category=DeprecationWarning, module='.*')
53 53 warnings.filterwarnings('error', message='.*decorated_dummy.*', category=DeprecationWarning, module='.*')
54 54 warnings.filterwarnings('error', message='.*skip_file_no_x11.*', category=DeprecationWarning, module='.*')
55 55 warnings.filterwarnings('error', message='.*onlyif_any_cmd_exists.*', category=DeprecationWarning, module='.*')
56 56
57 57 warnings.filterwarnings('error', message='.*disable_gui.*', category=DeprecationWarning, module='.*')
58 58
59 59 warnings.filterwarnings('error', message='.*ExceptionColors global is deprecated.*', category=DeprecationWarning, module='.*')
60 warnings.filterwarnings('error', message='.*IPython.core.display.*', category=DeprecationWarning, module='.*')
60 61
61 62 # Jedi older versions
62 63 warnings.filterwarnings(
63 64 'error', message='.*elementwise != comparison failed and.*', category=FutureWarning, module='.*')
64 65
65 66 if version_info < (6,):
66 67 # nose.tools renames all things from `camelCase` to `snake_case` which raise an
67 68 # warning with the runner they also import from standard import library. (as of Dec 2015)
68 69 # Ignore, let's revisit that in a couple of years for IPython 6.
69 70 warnings.filterwarnings(
70 71 'ignore', message='.*Please use assertEqual instead', category=Warning, module='IPython.*')
71 72
72 73 if version_info < (8,):
73 74 warnings.filterwarnings('ignore', message='.*Completer.complete.*',
74 75 category=PendingDeprecationWarning, module='.*')
75 76 else:
76 77 warnings.warn(
77 78 'Completer.complete was pending deprecation and should be changed to Deprecated', FutureWarning)
78 79
79 80
80 81
81 82 # ------------------------------------------------------------------------------
82 83 # Monkeypatch Xunit to count known failures as skipped.
83 84 # ------------------------------------------------------------------------------
84 85 def monkeypatch_xunit():
85 86 try:
86 87 dec.knownfailureif(True)(lambda: None)()
87 88 except Exception as e:
88 89 KnownFailureTest = type(e)
89 90
90 91 def addError(self, test, err, capt=None):
91 92 if issubclass(err[0], KnownFailureTest):
92 93 err = (SkipTest,) + err[1:]
93 94 return self.orig_addError(test, err, capt)
94 95
95 96 Xunit.orig_addError = Xunit.addError
96 97 Xunit.addError = addError
97 98
98 99 #-----------------------------------------------------------------------------
99 100 # Check which dependencies are installed and greater than minimum version.
100 101 #-----------------------------------------------------------------------------
101 102 def extract_version(mod):
102 103 return mod.__version__
103 104
104 105 def test_for(item, min_version=None, callback=extract_version):
105 106 """Test to see if item is importable, and optionally check against a minimum
106 107 version.
107 108
108 109 If min_version is given, the default behavior is to check against the
109 110 `__version__` attribute of the item, but specifying `callback` allows you to
110 111 extract the value you are interested in. e.g::
111 112
112 113 In [1]: import sys
113 114
114 115 In [2]: from IPython.testing.iptest import test_for
115 116
116 117 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
117 118 Out[3]: True
118 119
119 120 """
120 121 try:
121 122 check = import_item(item)
122 123 except (ImportError, RuntimeError):
123 124 # GTK reports Runtime error if it can't be initialized even if it's
124 125 # importable.
125 126 return False
126 127 else:
127 128 if min_version:
128 129 if callback:
129 130 # extra processing step to get version to compare
130 131 check = callback(check)
131 132
132 133 return check >= min_version
133 134 else:
134 135 return True
135 136
136 137 # Global dict where we can store information on what we have and what we don't
137 138 # have available at test run time
138 139 have = {'matplotlib': test_for('matplotlib'),
139 140 'pygments': test_for('pygments'),
140 141 }
141 142
142 143 #-----------------------------------------------------------------------------
143 144 # Test suite definitions
144 145 #-----------------------------------------------------------------------------
145 146
146 147 test_group_names = ['core',
147 148 'extensions', 'lib', 'terminal', 'testing', 'utils',
148 149 ]
149 150
150 151 class TestSection(object):
151 152 def __init__(self, name, includes):
152 153 self.name = name
153 154 self.includes = includes
154 155 self.excludes = []
155 156 self.dependencies = []
156 157 self.enabled = True
157 158
158 159 def exclude(self, module):
159 160 if not module.startswith('IPython'):
160 161 module = self.includes[0] + "." + module
161 162 self.excludes.append(module.replace('.', os.sep))
162 163
163 164 def requires(self, *packages):
164 165 self.dependencies.extend(packages)
165 166
166 167 @property
167 168 def will_run(self):
168 169 return self.enabled and all(have[p] for p in self.dependencies)
169 170
170 171 # Name -> (include, exclude, dependencies_met)
171 172 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
172 173
173 174
174 175 # Exclusions and dependencies
175 176 # ---------------------------
176 177
177 178 # core:
178 179 sec = test_sections['core']
179 180 if not have['matplotlib']:
180 181 sec.exclude('pylabtools'),
181 182 sec.exclude('tests.test_pylabtools')
182 183
183 184 # lib:
184 185 sec = test_sections['lib']
185 186 sec.exclude('kernel')
186 187 if not have['pygments']:
187 188 sec.exclude('tests.test_lexers')
188 189 # We do this unconditionally, so that the test suite doesn't import
189 190 # gtk, changing the default encoding and masking some unicode bugs.
190 191 sec.exclude('inputhookgtk')
191 192 # We also do this unconditionally, because wx can interfere with Unix signals.
192 193 # There are currently no tests for it anyway.
193 194 sec.exclude('inputhookwx')
194 195 # Testing inputhook will need a lot of thought, to figure out
195 196 # how to have tests that don't lock up with the gui event
196 197 # loops in the picture
197 198 sec.exclude('inputhook')
198 199
199 200 # testing:
200 201 sec = test_sections['testing']
201 202 # These have to be skipped on win32 because they use echo, rm, cd, etc.
202 203 # See ticket https://github.com/ipython/ipython/issues/87
203 204 if sys.platform == 'win32':
204 205 sec.exclude('plugin.test_exampleip')
205 206 sec.exclude('plugin.dtexample')
206 207
207 208 # don't run jupyter_console tests found via shim
208 209 test_sections['terminal'].exclude('console')
209 210
210 211 # extensions:
211 212 sec = test_sections['extensions']
212 213 # This is deprecated in favour of rpy2
213 214 sec.exclude('rmagic')
214 215 # autoreload does some strange stuff, so move it to its own test section
215 216 sec.exclude('autoreload')
216 217 sec.exclude('tests.test_autoreload')
217 218 test_sections['autoreload'] = TestSection('autoreload',
218 219 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
219 220 test_group_names.append('autoreload')
220 221
221 222
222 223 #-----------------------------------------------------------------------------
223 224 # Functions and classes
224 225 #-----------------------------------------------------------------------------
225 226
226 227 def check_exclusions_exist():
227 228 from IPython.paths import get_ipython_package_dir
228 229 from warnings import warn
229 230 parent = os.path.dirname(get_ipython_package_dir())
230 231 for sec in test_sections:
231 232 for pattern in sec.exclusions:
232 233 fullpath = pjoin(parent, pattern)
233 234 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
234 235 warn("Excluding nonexistent file: %r" % pattern)
235 236
236 237
237 238 class ExclusionPlugin(Plugin):
238 239 """A nose plugin to effect our exclusions of files and directories.
239 240 """
240 241 name = 'exclusions'
241 242 score = 3000 # Should come before any other plugins
242 243
243 244 def __init__(self, exclude_patterns=None):
244 245 """
245 246 Parameters
246 247 ----------
247 248
248 249 exclude_patterns : sequence of strings, optional
249 250 Filenames containing these patterns (as raw strings, not as regular
250 251 expressions) are excluded from the tests.
251 252 """
252 253 self.exclude_patterns = exclude_patterns or []
253 254 super(ExclusionPlugin, self).__init__()
254 255
255 256 def options(self, parser, env=os.environ):
256 257 Plugin.options(self, parser, env)
257 258
258 259 def configure(self, options, config):
259 260 Plugin.configure(self, options, config)
260 261 # Override nose trying to disable plugin.
261 262 self.enabled = True
262 263
263 264 def wantFile(self, filename):
264 265 """Return whether the given filename should be scanned for tests.
265 266 """
266 267 if any(pat in filename for pat in self.exclude_patterns):
267 268 return False
268 269 return None
269 270
270 271 def wantDirectory(self, directory):
271 272 """Return whether the given directory should be scanned for tests.
272 273 """
273 274 if any(pat in directory for pat in self.exclude_patterns):
274 275 return False
275 276 return None
276 277
277 278
278 279 class StreamCapturer(Thread):
279 280 daemon = True # Don't hang if main thread crashes
280 281 started = False
281 282 def __init__(self, echo=False):
282 283 super(StreamCapturer, self).__init__()
283 284 self.echo = echo
284 285 self.streams = []
285 286 self.buffer = BytesIO()
286 287 self.readfd, self.writefd = os.pipe()
287 288 self.buffer_lock = Lock()
288 289 self.stop = Event()
289 290
290 291 def run(self):
291 292 self.started = True
292 293
293 294 while not self.stop.is_set():
294 295 chunk = os.read(self.readfd, 1024)
295 296
296 297 with self.buffer_lock:
297 298 self.buffer.write(chunk)
298 299 if self.echo:
299 300 sys.stdout.write(decode(chunk))
300 301
301 302 os.close(self.readfd)
302 303 os.close(self.writefd)
303 304
304 305 def reset_buffer(self):
305 306 with self.buffer_lock:
306 307 self.buffer.truncate(0)
307 308 self.buffer.seek(0)
308 309
309 310 def get_buffer(self):
310 311 with self.buffer_lock:
311 312 return self.buffer.getvalue()
312 313
313 314 def ensure_started(self):
314 315 if not self.started:
315 316 self.start()
316 317
317 318 def halt(self):
318 319 """Safely stop the thread."""
319 320 if not self.started:
320 321 return
321 322
322 323 self.stop.set()
323 324 os.write(self.writefd, b'\0') # Ensure we're not locked in a read()
324 325 self.join()
325 326
326 327 class SubprocessStreamCapturePlugin(Plugin):
327 328 name='subprocstreams'
328 329 def __init__(self):
329 330 Plugin.__init__(self)
330 331 self.stream_capturer = StreamCapturer()
331 332 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
332 333 # This is ugly, but distant parts of the test machinery need to be able
333 334 # to redirect streams, so we make the object globally accessible.
334 335 nose.iptest_stdstreams_fileno = self.get_write_fileno
335 336
336 337 def get_write_fileno(self):
337 338 if self.destination == 'capture':
338 339 self.stream_capturer.ensure_started()
339 340 return self.stream_capturer.writefd
340 341 elif self.destination == 'discard':
341 342 return os.open(os.devnull, os.O_WRONLY)
342 343 else:
343 344 return sys.__stdout__.fileno()
344 345
345 346 def configure(self, options, config):
346 347 Plugin.configure(self, options, config)
347 348 # Override nose trying to disable plugin.
348 349 if self.destination == 'capture':
349 350 self.enabled = True
350 351
351 352 def startTest(self, test):
352 353 # Reset log capture
353 354 self.stream_capturer.reset_buffer()
354 355
355 356 def formatFailure(self, test, err):
356 357 # Show output
357 358 ec, ev, tb = err
358 359 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
359 360 if captured.strip():
360 361 ev = safe_str(ev)
361 362 out = [ev, '>> begin captured subprocess output <<',
362 363 captured,
363 364 '>> end captured subprocess output <<']
364 365 return ec, '\n'.join(out), tb
365 366
366 367 return err
367 368
368 369 formatError = formatFailure
369 370
370 371 def finalize(self, result):
371 372 self.stream_capturer.halt()
372 373
373 374
374 375 def run_iptest():
375 376 """Run the IPython test suite using nose.
376 377
377 378 This function is called when this script is **not** called with the form
378 379 `iptest all`. It simply calls nose with appropriate command line flags
379 380 and accepts all of the standard nose arguments.
380 381 """
381 382 # Apply our monkeypatch to Xunit
382 383 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
383 384 monkeypatch_xunit()
384 385
385 386 arg1 = sys.argv[1]
386 387 if arg1.startswith('IPython/'):
387 388 if arg1.endswith('.py'):
388 389 arg1 = arg1[:-3]
389 390 sys.argv[1] = arg1.replace('/', '.')
390 391
391 392 arg1 = sys.argv[1]
392 393 if arg1 in test_sections:
393 394 section = test_sections[arg1]
394 395 sys.argv[1:2] = section.includes
395 396 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
396 397 section = test_sections[arg1[8:]]
397 398 sys.argv[1:2] = section.includes
398 399 else:
399 400 section = TestSection(arg1, includes=[arg1])
400 401
401 402
402 403 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
403 404 # We add --exe because of setuptools' imbecility (it
404 405 # blindly does chmod +x on ALL files). Nose does the
405 406 # right thing and it tries to avoid executables,
406 407 # setuptools unfortunately forces our hand here. This
407 408 # has been discussed on the distutils list and the
408 409 # setuptools devs refuse to fix this problem!
409 410 '--exe',
410 411 ]
411 412 if '-a' not in argv and '-A' not in argv:
412 413 argv = argv + ['-a', '!crash']
413 414
414 415 if nose.__version__ >= '0.11':
415 416 # I don't fully understand why we need this one, but depending on what
416 417 # directory the test suite is run from, if we don't give it, 0 tests
417 418 # get run. Specifically, if the test suite is run from the source dir
418 419 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
419 420 # even if the same call done in this directory works fine). It appears
420 421 # that if the requested package is in the current dir, nose bails early
421 422 # by default. Since it's otherwise harmless, leave it in by default
422 423 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
423 424 argv.append('--traverse-namespace')
424 425
425 426 plugins = [ ExclusionPlugin(section.excludes), KnownFailure(),
426 427 SubprocessStreamCapturePlugin() ]
427 428
428 429 # we still have some vestigial doctests in core
429 430 if (section.name.startswith(('core', 'IPython.core', 'IPython.utils'))):
430 431 plugins.append(IPythonDoctest())
431 432 argv.extend([
432 433 '--with-ipdoctest',
433 434 '--ipdoctest-tests',
434 435 '--ipdoctest-extension=txt',
435 436 ])
436 437
437 438
438 439 # Use working directory set by parent process (see iptestcontroller)
439 440 if 'IPTEST_WORKING_DIR' in os.environ:
440 441 os.chdir(os.environ['IPTEST_WORKING_DIR'])
441 442
442 443 # We need a global ipython running in this process, but the special
443 444 # in-process group spawns its own IPython kernels, so for *that* group we
444 445 # must avoid also opening the global one (otherwise there's a conflict of
445 446 # singletons). Ultimately the solution to this problem is to refactor our
446 447 # assumptions about what needs to be a singleton and what doesn't (app
447 448 # objects should, individual shells shouldn't). But for now, this
448 449 # workaround allows the test suite for the inprocess module to complete.
449 450 if 'kernel.inprocess' not in section.name:
450 451 from IPython.testing import globalipapp
451 452 globalipapp.start_ipython()
452 453
453 454 # Now nose can run
454 455 TestProgram(argv=argv, addplugins=plugins)
455 456
456 457 if __name__ == '__main__':
457 458 run_iptest()
General Comments 0
You need to be logged in to leave comments. Login now