1# -*- coding: utf-8 -*-
2"""Top-level display functions for displaying object in different formats."""
3
4# Copyright (c) IPython Development Team.
5# Distributed under the terms of the Modified BSD License.
6
7
8from binascii import b2a_hex, b2a_base64, hexlify
9import json
10import mimetypes
11import os
12import struct
13import sys
14import warnings
15from copy import deepcopy
16from os.path import splitext
17from pathlib import Path, PurePath
18
19from IPython.utils.py3compat import cast_unicode
20from IPython.testing.skipdoctest import skip_doctest
21
22__all__ = ['display', 'display_pretty', 'display_html', 'display_markdown',
23'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json',
24'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject',
25'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',
28'Video']
29
30#-----------------------------------------------------------------------------
31# utility functions
32#-----------------------------------------------------------------------------
33
34def _safe_exists(path):
35    """Check path, but don't let exceptions raise"""
36    try:
37        return os.path.exists(path)
38    except Exception:
39        return False
40
41def _merge(d1, d2):
42    """Like update, but merges sub-dicts instead of clobbering at the top level.
43
44    Updates d1 in-place
45    """
46
47    if not isinstance(d2, dict) or not isinstance(d1, dict):
48        return d2
49    for key, value in d2.items():
50        d1[key] = _merge(d1.get(key), value)
51    return d1
52
53def _display_mimetype(mimetype, objs, raw=False, metadata=None):
54    """internal implementation of all display_foo methods
55
56    Parameters
57    ----------
58    mimetype : str
59        The mimetype to be published (e.g. 'image/png')
60    *objs : object
61        The Python objects to display, or if raw=True raw text data to
62        display.
63    raw : bool
64        Are the data objects raw data or Python objects that need to be
65        formatted before display? [default: False]
66    metadata : dict (optional)
67        Metadata to be associated with the specific mimetype output.
68    """
69    if metadata:
70        metadata = {mimetype: metadata}
71    if raw:
72        # turn list of pngdata into list of { 'image/png': pngdata }
73        objs = [ {mimetype: obj} for obj in objs ]
74    display(*objs, raw=raw, metadata=metadata, include=[mimetype])
75
76#-----------------------------------------------------------------------------
77# Main functions
78#-----------------------------------------------------------------------------
79
80# use * to indicate transient is keyword-only
81def publish_display_data(data, metadata=None, source=None, *, transient=None, **kwargs):
82    """Publish data and metadata to all frontends.
83
84    See the ``display_data`` message in the messaging documentation for
85    more details about this message type.
86
87    Keys of data and metadata can be any mime-type.
88
89    Parameters
90    ----------
91    data : dict
92        A dictionary having keys that are valid MIME types (like
93        'text/plain' or 'image/svg+xml') and values that are the data for
94        that MIME type. The data itself must be a JSON'able data
95        structure. Minimally all data should have the 'text/plain' data,
96        which can be displayed by all frontends. If more than the plain
97        text is given, it is up to the frontend to decide which
98        representation to use.
99    metadata : dict
100        A dictionary for metadata related to the data. This can contain
101        arbitrary key, value pairs that frontends can use to interpret
102        the data. mime-type keys matching those in data can be used
103        to specify metadata about particular representations.
104    source : str, deprecated
105        Unused.
106    transient : dict, keyword-only
107        A dictionary of transient data, such as display_id.
108        """
109    from IPython.core.interactiveshell import InteractiveShell
110
111    display_pub = InteractiveShell.instance().display_pub
112
113    # only pass transient if supplied,
114    # to avoid errors with older ipykernel.
115    # TODO: We could check for ipykernel version and provide a detailed upgrade message.
116    if transient:
117        kwargs['transient'] = transient
118
119    display_pub.publish(
120        data=data,
121        metadata=metadata,
122        **kwargs
123    )
124
125
126def _new_id():
127    """Generate a new random text id with urandom"""
128    return b2a_hex(os.urandom(16)).decode('ascii')
129
130
131def display(*objs, include=None, exclude=None, metadata=None, transient=None, display_id=None, **kwargs):
132    """Display a Python object in all frontends.
133
134    By default all representations will be computed and sent to the frontends.
135    Frontends can decide which representation is used and how.
136
137    In terminal IPython this will be similar to using :func:`print`, for use in richer
138    frontends see Jupyter notebook examples with rich display logic.
139
140    Parameters
141    ----------
142    *objs : object
143        The Python objects to display.
144    raw : bool, optional
145        Are the objects to be displayed already mimetype-keyed dicts of raw display data,
146        or Python objects that need to be formatted before display? [default: False]
147    include : list, tuple or set, optional
148        A list of format type strings (MIME types) to include in the
149        format data dict. If this is set *only* the format types included
150        in this list will be computed.
151    exclude : list, tuple or set, optional
152        A list of format type strings (MIME types) to exclude in the format
153        data dict. If this is set all format types will be computed,
154        except for those included in this argument.
155    metadata : dict, optional
156        A dictionary of metadata to associate with the output.
157        mime-type keys in this dictionary will be associated with the individual
158        representation formats, if they exist.
159    transient : dict, optional
160        A dictionary of transient data to associate with the output.
161        Data in this dict should not be persisted to files (e.g. notebooks).
162    display_id : str, bool optional
163        Set an id for the display.
164        This id can be used for updating this display area later via update_display.
165        If given as `True`, generate a new `display_id`
166    clear : bool, optional
167        Should the output area be cleared before displaying anything? If True,
168        this will wait for additional output before clearing. [default: False]
169    kwargs: additional keyword-args, optional
170        Additional keyword-arguments are passed through to the display publisher.
171
172    Returns
173    -------
174
175    handle: DisplayHandle
176        Returns a handle on updatable displays for use with :func:`update_display`,
177        if `display_id` is given. Returns :any:`None` if no `display_id` is given
178        (default).
179
180    Examples
181    --------
182
183    >>> class Json(object):
184    ...     def __init__(self, json):
185    ...         self.json = json
186    ...     def _repr_pretty_(self, pp, cycle):
187    ...         import json
188    ...         pp.text(json.dumps(self.json, indent=2))
189    ...     def __repr__(self):
190    ...         return str(self.json)
191    ...
192
193    >>> d = Json({1:2, 3: {4:5}})
194
195    >>> print(d)
196    {1: 2, 3: {4: 5}}
197
198    >>> display(d)
199    {
200      "1": 2,
201      "3": {
202        "4": 5
203      }
204    }
205
206    >>> def int_formatter(integer, pp, cycle):
207    ...     pp.text('I'*integer)
208
209    >>> plain = get_ipython().display_formatter.formatters['text/plain']
210    >>> plain.for_type(int, int_formatter)
211    <function _repr_pprint at 0x...>
212    >>> display(7-5)
213    II
214
215    >>> del plain.type_printers[int]
216    >>> display(7-5)
217    2
218
219    See Also
220    --------
221
222    :func:`update_display`
223
224    Notes
225    -----
226
227    In Python, objects can declare their textual representation using the
228    `__repr__` method. IPython expands on this idea and allows objects to declare
229    other, rich representations including:
230
231      - HTML
232      - JSON
233      - PNG
234      - JPEG
235      - SVG
236      - LaTeX
237
238    A single object can declare some or all of these representations; all are
239    handled by IPython's display system.
240
241    The main idea of the first approach is that you have to implement special
242    display methods when you define your class, one for each representation you
243    want to use. Here is a list of the names of the special methods and the
244    values they must return:
245
246      - `_repr_html_`: return raw HTML as a string, or a tuple (see below).
247      - `_repr_json_`: return a JSONable dict, or a tuple (see below).
248      - `_repr_jpeg_`: return raw JPEG data, or a tuple (see below).
249      - `_repr_png_`: return raw PNG data, or a tuple (see below).
250      - `_repr_svg_`: return raw SVG data as a string, or a tuple (see below).
251      - `_repr_latex_`: return LaTeX commands in a string surrounded by "$",
252                        or a tuple (see below).
253      - `_repr_mimebundle_`: return a full mimebundle containing the mapping
254                             from all mimetypes to data.
255                             Use this for any mime-type not listed above.
256
257    The above functions may also return the object's metadata alonside the
258    data.  If the metadata is available, the functions will return a tuple
259    containing the data and metadata, in that order.  If there is no metadata
260    available, then the functions will return the data only.
261
262    When you are directly writing your own classes, you can adapt them for
263    display in IPython by following the above approach. But in practice, you
264    often need to work with existing classes that you can't easily modify.
265
266    You can refer to the documentation on integrating with the display system in
267    order to register custom formatters for already existing types
268    (:ref:`integrating_rich_display`).
269
270    .. versionadded:: 5.4 display available without import
271    .. versionadded:: 6.1 display available without import
272
273    Since IPython 5.4 and 6.1 :func:`display` is automatically made available to
274    the user without import. If you are using display in a document that might
275    be used in a pure python context or with older version of IPython, use the
276    following import at the top of your file::
277
278        from IPython.display import display
279
280    """
281    from IPython.core.interactiveshell import InteractiveShell
282
283    if not InteractiveShell.initialized():
284        # Directly print objects.
285        print(*objs)
286        return
287
288    raw = kwargs.pop("raw", False)
289    clear = kwargs.pop("clear", False)
290    if transient is None:
291        transient = {}
292    if metadata is None:
293        metadata={}
294    if display_id:
295        if display_id is True:
296            display_id = _new_id()
297        transient['display_id'] = display_id
298    if kwargs.get('update') and 'display_id' not in transient:
299        raise TypeError('display_id required for update_display')
300    if transient:
301        kwargs['transient'] = transient
302
303    if not objs and display_id:
304        # if given no objects, but still a request for a display_id,
305        # we assume the user wants to insert an empty output that
306        # can be updated later
307        objs = [{}]
308        raw = True
309
310    if not raw:
311        format = InteractiveShell.instance().display_formatter.format
312
313    if clear:
314        clear_output(wait=True)
315
316    for obj in objs:
317        if raw:
318            publish_display_data(data=obj, metadata=metadata, **kwargs)
319        else:
320            format_dict, md_dict = format(obj, include=include, exclude=exclude)
321            if not format_dict:
322                # nothing to display (e.g. _ipython_display_ took over)
323                continue
324            if metadata:
325                # kwarg-specified metadata gets precedence
326                _merge(md_dict, metadata)
327            publish_display_data(data=format_dict, metadata=md_dict, **kwargs)
328    if display_id:
329        return DisplayHandle(display_id)
330
331
332# use * for keyword-only display_id arg
333def update_display(obj, *, display_id, **kwargs):
334    """Update an existing display by id
335
336    Parameters
337    ----------
338
339    obj:
340        The object with which to update the display
341    display_id: keyword-only
342        The id of the display to update
343
344    See Also
345    --------
346
347    :func:`display`
348    """
349    kwargs['update'] = True
350    display(obj, display_id=display_id, **kwargs)
351
352
353class DisplayHandle(object):
354    """A handle on an updatable display
355
356    Call `.update(obj)` to display a new object.
357
358    Call `.display(obj`) to add a new instance of this display,
359    and update existing instances.
360
361    See Also
362    --------
363
364        :func:`display`, :func:`update_display`
365
366    """
367
368    def __init__(self, display_id=None):
369        if display_id is None:
370            display_id = _new_id()
371        self.display_id = display_id
372
373    def __repr__(self):
374        return "<%s display_id=%s>" % (self.__class__.__name__, self.display_id)
375
376    def display(self, obj, **kwargs):
377        """Make a new display with my id, updating existing instances.
378
379        Parameters
380        ----------
381
382        obj:
383            object to display
384        **kwargs:
385            additional keyword arguments passed to display
386        """
387        display(obj, display_id=self.display_id, **kwargs)
388
389    def update(self, obj, **kwargs):
390        """Update existing displays with my id
391
392        Parameters
393        ----------
394
395        obj:
396            object to display
397        **kwargs:
398            additional keyword arguments passed to update_display
399        """
400        update_display(obj, display_id=self.display_id, **kwargs)
401
402
403def display_pretty(*objs, **kwargs):
404    """Display the pretty (default) representation of an object.
405
406    Parameters
407    ----------
408    *objs : object
409        The Python objects to display, or if raw=True raw text data to
410        display.
411    raw : bool
412        Are the data objects raw data or Python objects that need to be
413        formatted before display? [default: False]
414    metadata : dict (optional)
415        Metadata to be associated with the specific mimetype output.
416    """
417    _display_mimetype('text/plain', objs, **kwargs)
418
419
420def display_html(*objs, **kwargs):
421    """Display the HTML representation of an object.
422
423    Note: If raw=False and the object does not have a HTML
424    representation, no HTML will be shown.
425
426    Parameters
427    ----------
428    *objs : object
429        The Python objects to display, or if raw=True raw HTML data to
430        display.
431    raw : bool
432        Are the data objects raw data or Python objects that need to be
433        formatted before display? [default: False]
434    metadata : dict (optional)
435        Metadata to be associated with the specific mimetype output.
436    """
437    _display_mimetype('text/html', objs, **kwargs)
438
439
440def display_markdown(*objs, **kwargs):
441    """Displays the Markdown representation of an object.
442
443    Parameters
444    ----------
445    *objs : object
446        The Python objects to display, or if raw=True raw markdown data to
447        display.
448    raw : bool
449        Are the data objects raw data or Python objects that need to be
450        formatted before display? [default: False]
451    metadata : dict (optional)
452        Metadata to be associated with the specific mimetype output.
453    """
454
455    _display_mimetype('text/markdown', objs, **kwargs)
456
457
458def display_svg(*objs, **kwargs):
459    """Display the SVG representation of an object.
460
461    Parameters
462    ----------
463    *objs : object
464        The Python objects to display, or if raw=True raw svg data to
465        display.
466    raw : bool
467        Are the data objects raw data or Python objects that need to be
468        formatted before display? [default: False]
469    metadata : dict (optional)
470        Metadata to be associated with the specific mimetype output.
471    """
472    _display_mimetype('image/svg+xml', objs, **kwargs)
473
474
475def display_png(*objs, **kwargs):
476    """Display the PNG representation of an object.
477
478    Parameters
479    ----------
480    *objs : object
481        The Python objects to display, or if raw=True raw png data to
482        display.
483    raw : bool
484        Are the data objects raw data or Python objects that need to be
485        formatted before display? [default: False]
486    metadata : dict (optional)
487        Metadata to be associated with the specific mimetype output.
488    """
489    _display_mimetype('image/png', objs, **kwargs)
490
491
492def display_jpeg(*objs, **kwargs):
493    """Display the JPEG representation of an object.
494
495    Parameters
496    ----------
497    *objs : object
498        The Python objects to display, or if raw=True raw JPEG data to
499        display.
500    raw : bool
501        Are the data objects raw data or Python objects that need to be
502        formatted before display? [default: False]
503    metadata : dict (optional)
504        Metadata to be associated with the specific mimetype output.
505    """
506    _display_mimetype('image/jpeg', objs, **kwargs)
507
508
509def display_latex(*objs, **kwargs):
510    """Display the LaTeX representation of an object.
511
512    Parameters
513    ----------
514    *objs : object
515        The Python objects to display, or if raw=True raw latex data to
516        display.
517    raw : bool
518        Are the data objects raw data or Python objects that need to be
519        formatted before display? [default: False]
520    metadata : dict (optional)
521        Metadata to be associated with the specific mimetype output.
522    """
523    _display_mimetype('text/latex', objs, **kwargs)
524
525
526def display_json(*objs, **kwargs):
527    """Display the JSON representation of an object.
528
529    Note that not many frontends support displaying JSON.
530
531    Parameters
532    ----------
533    *objs : object
534        The Python objects to display, or if raw=True raw json data to
535        display.
536    raw : bool
537        Are the data objects raw data or Python objects that need to be
538        formatted before display? [default: False]
539    metadata : dict (optional)
540        Metadata to be associated with the specific mimetype output.
541    """
542    _display_mimetype('application/json', objs, **kwargs)
543
544
545def display_javascript(*objs, **kwargs):
546    """Display the Javascript representation of an object.
547
548    Parameters
549    ----------
550    *objs : object
551        The Python objects to display, or if raw=True raw javascript data to
552        display.
553    raw : bool
554        Are the data objects raw data or Python objects that need to be
555        formatted before display? [default: False]
556    metadata : dict (optional)
557        Metadata to be associated with the specific mimetype output.
558    """
559    _display_mimetype('application/javascript', objs, **kwargs)
560
561
562def display_pdf(*objs, **kwargs):
563    """Display the PDF representation of an object.
564
565    Parameters
566    ----------
567    *objs : object
568        The Python objects to display, or if raw=True raw javascript data to
569        display.
570    raw : bool
571        Are the data objects raw data or Python objects that need to be
572        formatted before display? [default: False]
573    metadata : dict (optional)
574        Metadata to be associated with the specific mimetype output.
575    """
576    _display_mimetype('application/pdf', objs, **kwargs)
577
578
579#-----------------------------------------------------------------------------
580# Smart classes
581#-----------------------------------------------------------------------------
582
583
584class DisplayObject(object):
585    """An object that wraps data to be displayed."""
586
587    _read_flags = 'r'
588    _show_mem_addr = False
589    metadata = None
590
591    def __init__(self, data=None, url=None, filename=None, metadata=None):
592        """Create a display object given raw data.
593
594        When this object is returned by an expression or passed to the
595        display function, it will result in the data being displayed
596        in the frontend. The MIME type of the data should match the
597        subclasses used, so the Png subclass should be used for 'image/png'
598        data. If the data is a URL, the data will first be downloaded
599        and then displayed. If
600
601        Parameters
602        ----------
603        data : unicode, str or bytes
604            The raw data or a URL or file to load the data from
605        url : unicode
606            A URL to download the data from.
607        filename : unicode
608            Path to a local file to load the data from.
609        metadata : dict
610            Dict of metadata associated to be the object when displayed
611        """
612        if isinstance(data, (Path, PurePath)):
613            data = str(data)
614
615        if data is not None and isinstance(data, str):
616            if data.startswith('http') and url is None:
617                url = data
618                filename = None
619                data = None
620            elif _safe_exists(data) and filename is None:
621                url = None
622                filename = data
623                data = None
624
625        self.url = url
626        self.filename = filename
627        # because of @data.setter methods in
628        # subclasses ensure url and filename are set
629        # before assigning to self.data
630        self.data = data
631
632        if metadata is not None:
633            self.metadata = metadata
634        elif self.metadata is None:
635            self.metadata = {}
636
637        self.reload()
638        self._check_data()
639
640    def __repr__(self):
641        if not self._show_mem_addr:
642            cls = self.__class__
643            r = "<%s.%s object>" % (cls.__module__, cls.__name__)
644        else:
645            r = super(DisplayObject, self).__repr__()
646        return r
647
648    def _check_data(self):
649        """Override in subclasses if there's something to check."""
650        pass
651
652    def _data_and_metadata(self):
653        """shortcut for returning metadata with shape information, if defined"""
654        if self.metadata:
655            return self.data, deepcopy(self.metadata)
656        else:
657            return self.data
658
659    def reload(self):
660        """Reload the raw data from file or URL."""
661        if self.filename is not None:
662            with open(self.filename, self._read_flags) as f:
663                self.data = f.read()
664        elif self.url is not None:
665            # Deferred import
666            from urllib.request import urlopen
667            response = urlopen(self.url)
668            data = response.read()
669            # extract encoding from header, if there is one:
670            encoding = None
671            if 'content-type' in response.headers:
672                for sub in response.headers['content-type'].split(';'):
673                    sub = sub.strip()
674                    if sub.startswith('charset'):
675                        encoding = sub.split('=')[-1].strip()
676                        break
677            if 'content-encoding' in response.headers:
678                # TODO: do deflate?
679                if 'gzip' in response.headers['content-encoding']:
680                    import gzip
681                    from io import BytesIO
682                    with gzip.open(BytesIO(data), 'rt', encoding=encoding) as fp:
683                        encoding = None
684                        data = fp.read()
685
686            # decode data, if an encoding was specified
687            # We only touch self.data once since
688            # subclasses such as SVG have @data.setter methods
689            # that transform self.data into ... well svg.
690            if encoding:
691                self.data = data.decode(encoding, 'replace')
692            else:
693                self.data = data
694
695
696class TextDisplayObject(DisplayObject):
697    """Validate that display data is text"""
698    def _check_data(self):
699        if self.data is not None and not isinstance(self.data, str):
700            raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
701
702class Pretty(TextDisplayObject):
703
704    def _repr_pretty_(self, pp, cycle):
705        return pp.text(self.data)
706
707
708class HTML(TextDisplayObject):
709
710    def __init__(self, data=None, url=None, filename=None, metadata=None):
711        def warn():
712            if not data:
713                return False
714
715            #
716            # Avoid calling lower() on the entire data, because it could be a
717            # long string and we're only interested in its beginning and end.
718            #
719            prefix = data[:10].lower()
720            suffix = data[-10:].lower()
721            return prefix.startswith("<iframe ") and suffix.endswith("</iframe>")
722
723        if warn():
724            warnings.warn("Consider using IPython.display.IFrame instead")
725        super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata)
726
727    def _repr_html_(self):
728        return self._data_and_metadata()
729
730    def __html__(self):
731        """
732        This method exists to inform other HTML-using modules (e.g. Markupsafe,
733        htmltag, etc) that this object is HTML and does not need things like
734        special characters (<>&) escaped.
735        """
736        return self._repr_html_()
737
738
739class Markdown(TextDisplayObject):
740
741    def _repr_markdown_(self):
742        return self._data_and_metadata()
743
744
745class Math(TextDisplayObject):
746
747    def _repr_latex_(self):
748        s = r"$\displaystyle %s$" % self.data.strip('$')
749        if self.metadata:
750            return s, deepcopy(self.metadata)
751        else:
752            return s
753
754
755class Latex(TextDisplayObject):
756
757    def _repr_latex_(self):
758        return self._data_and_metadata()
759
760
761class SVG(DisplayObject):
762    """Embed an SVG into the display.
763
764    Note if you just want to view a svg image via a URL use `:class:Image` with
765    a url=URL keyword argument.
766    """
767
768    _read_flags = 'rb'
769    # wrap data in a property, which extracts the <svg> tag, discarding
770    # document headers
771    _data = None
772
773    @property
774    def data(self):
775        return self._data
776
777    @data.setter
778    def data(self, svg):
779        if svg is None:
780            self._data = None
781            return
782        # parse into dom object
783        from xml.dom import minidom
784        x = minidom.parseString(svg)
785        # get svg tag (should be 1)
786        found_svg = x.getElementsByTagName('svg')
787        if found_svg:
788            svg = found_svg[0].toxml()
789        else:
790            # fallback on the input, trust the user
791            # but this is probably an error.
792            pass
793        svg = cast_unicode(svg)
794        self._data = svg
795
796    def _repr_svg_(self):
797        return self._data_and_metadata()
798
799class ProgressBar(DisplayObject):
800    """Progressbar supports displaying a progressbar like element
801    """
802    def __init__(self, total):
803        """Creates a new progressbar
804
805        Parameters
806        ----------
807        total : int
808            maximum size of the progressbar
809        """
810        self.total = total
811        self._progress = 0
812        self.html_width = '60ex'
813        self.text_width = 60
814        self._display_id = hexlify(os.urandom(8)).decode('ascii')
815
816    def __repr__(self):
817        fraction = self.progress / self.total
818        filled = '=' * int(fraction * self.text_width)
819        rest = ' ' * (self.text_width - len(filled))
820        return '[{}{}] {}/{}'.format(
821            filled, rest,
822            self.progress, self.total,
823        )
824
825    def _repr_html_(self):
826        return "<progress style='width:{}' max='{}' value='{}'></progress>".format(
827            self.html_width, self.total, self.progress)
828
829    def display(self):
830        display(self, display_id=self._display_id)
831
832    def update(self):
833        display(self, display_id=self._display_id, update=True)
834
835    @property
836    def progress(self):
837        return self._progress
838
839    @progress.setter
840    def progress(self, value):
841        self._progress = value
842        self.update()
843
844    def __iter__(self):
845        self.display()
846        self._progress = -1 # First iteration is 0
847        return self
848
849    def __next__(self):
850        """Returns current value and increments display by one."""
851        self.progress += 1
852        if self.progress < self.total:
853            return self.progress
854        else:
855            raise StopIteration()
856
857class JSON(DisplayObject):
858    """JSON expects a JSON-able dict or list
859
860    not an already-serialized JSON string.
861
862    Scalar types (None, number, string) are not allowed, only dict or list containers.
863    """
864    # wrap data in a property, which warns about passing already-serialized JSON
865    _data = None
866    def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs):
867        """Create a JSON display object given raw data.
868
869        Parameters
870        ----------
871        data : dict or list
872            JSON data to display. Not an already-serialized JSON string.
873            Scalar types (None, number, string) are not allowed, only dict
874            or list containers.
875        url : unicode
876            A URL to download the data from.
877        filename : unicode
878            Path to a local file to load the data from.
879        expanded : boolean
880            Metadata to control whether a JSON display component is expanded.
881        metadata: dict
882            Specify extra metadata to attach to the json display object.
883        root : str
884            The name of the root element of the JSON tree
885        """
886        self.metadata = {
887            'expanded': expanded,
888            'root': root,
889        }
890        if metadata:
891            self.metadata.update(metadata)
892        if kwargs:
893            self.metadata.update(kwargs)
894        super(JSON, self).__init__(data=data, url=url, filename=filename)
895
896    def _check_data(self):
897        if self.data is not None and not isinstance(self.data, (dict, list)):
898            raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data))
899
900    @property
901    def data(self):
902        return self._data
903
904    @data.setter
905    def data(self, data):
906        if isinstance(data, (Path, PurePath)):
907            data = str(data)
908
909        if isinstance(data, str):
910            if self.filename is None and self.url is None:
911                warnings.warn("JSON expects JSONable dict or list, not JSON strings")
912            data = json.loads(data)
913        self._data = data
914
915    def _data_and_metadata(self):
916        return self.data, self.metadata
917
918    def _repr_json_(self):
919        return self._data_and_metadata()
920
921_css_t = """var link = document.createElement("link");
922	link.ref = "stylesheet";
923	link.type = "text/css";
924	link.href = "%s";
925	document.head.appendChild(link);
926"""
927
928_lib_t1 = """new Promise(function(resolve, reject) {
929	var script = document.createElement("script");
930	script.onload = resolve;
931	script.onerror = reject;
932	script.src = "%s";
933	document.head.appendChild(script);
934}).then(() => {
935"""
936
937_lib_t2 = """
938});"""
939
940class GeoJSON(JSON):
941    """GeoJSON expects JSON-able dict
942
943    not an already-serialized JSON string.
944
945    Scalar types (None, number, string) are not allowed, only dict containers.
946    """
947
948    def __init__(self, *args, **kwargs):
949        """Create a GeoJSON display object given raw data.
950
951        Parameters
952        ----------
953        data : dict or list
954            VegaLite data. Not an already-serialized JSON string.
955            Scalar types (None, number, string) are not allowed, only dict
956            or list containers.
957        url_template : string
958            Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template
959        layer_options : dict
960            Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options
961        url : unicode
962            A URL to download the data from.
963        filename : unicode
964            Path to a local file to load the data from.
965        metadata: dict
966            Specify extra metadata to attach to the json display object.
967
968        Examples
969        --------
970
971        The following will display an interactive map of Mars with a point of
972        interest on frontend that do support GeoJSON display.
973
974            >>> from IPython.display import GeoJSON
975
976            >>> GeoJSON(data={
977            ...     "type": "Feature",
978            ...     "geometry": {
979            ...         "type": "Point",
980            ...         "coordinates": [-81.327, 296.038]
981            ...     }
982            ... },
983            ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
984            ... layer_options={
985            ...     "basemap_id": "celestia_mars-shaded-16k_global",
986            ...     "attribution" : "Celestia/praesepe",
987            ...     "minZoom" : 0,
988            ...     "maxZoom" : 18,
989            ... })
990            <IPython.core.display.GeoJSON object>
991
992        In the terminal IPython, you will only see the text representation of
993        the GeoJSON object.
994
995        """
996
997        super(GeoJSON, self).__init__(*args, **kwargs)
998
999
1000    def _ipython_display_(self):
1001        bundle = {
1002            'application/geo+json': self.data,
1003            'text/plain': '<IPython.display.GeoJSON object>'
1004        }
1005        metadata = {
1006            'application/geo+json': self.metadata
1007        }
1008        display(bundle, metadata=metadata, raw=True)
1009
1010class Javascript(TextDisplayObject):
1011
1012    def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
1013        """Create a Javascript display object given raw data.
1014
1015        When this object is returned by an expression or passed to the
1016        display function, it will result in the data being displayed
1017        in the frontend. If the data is a URL, the data will first be
1018        downloaded and then displayed.
1019
1020        In the Notebook, the containing element will be available as `element`,
1021        and jQuery will be available.  Content appended to `element` will be
1022        visible in the output area.
1023
1024        Parameters
1025        ----------
1026        data : unicode, str or bytes
1027            The Javascript source code or a URL to download it from.
1028        url : unicode
1029            A URL to download the data from.
1030        filename : unicode
1031            Path to a local file to load the data from.
1032        lib : list or str
1033            A sequence of Javascript library URLs to load asynchronously before
1034            running the source code. The full URLs of the libraries should
1035            be given. A single Javascript library URL can also be given as a
1036            string.
1037        css: : list or str
1038            A sequence of css files to load before running the source code.
1039            The full URLs of the css files should be given. A single css URL
1040            can also be given as a string.
1041        """
1042        if isinstance(lib, str):
1043            lib = [lib]
1044        elif lib is None:
1045            lib = []
1046        if isinstance(css, str):
1047            css = [css]
1048        elif css is None:
1049            css = []
1050        if not isinstance(lib, (list,tuple)):
1051            raise TypeError('expected sequence, got: %r' % lib)
1052        if not isinstance(css, (list,tuple)):
1053            raise TypeError('expected sequence, got: %r' % css)
1054        self.lib = lib
1055        self.css = css
1056        super(Javascript, self).__init__(data=data, url=url, filename=filename)
1057
1058    def _repr_javascript_(self):
1059        r = ''
1060        for c in self.css:
1061            r += _css_t % c
1062        for l in self.lib:
1063            r += _lib_t1 % l
1064        r += self.data
1065        r += _lib_t2*len(self.lib)
1066        return r
1067
1068# constants for identifying png/jpeg data
1069_PNG = b'\x89PNG\r\n\x1a\n'
1070_JPEG = b'\xff\xd8'
1071
1072def _pngxy(data):
1073    """read the (width, height) from a PNG header"""
1074    ihdr = data.index(b'IHDR')
1075    # next 8 bytes are width/height
1076    return struct.unpack('>ii', data[ihdr+4:ihdr+12])
1077
1078def _jpegxy(data):
1079    """read the (width, height) from a JPEG header"""
1080    # adapted from http://www.64lines.com/jpeg-width-height
1081
1082    idx = 4
1083    while True:
1084        block_size = struct.unpack('>H', data[idx:idx+2])[0]
1085        idx = idx + block_size
1086        if data[idx:idx+2] == b'\xFF\xC0':
1087            # found Start of Frame
1088            iSOF = idx
1089            break
1090        else:
1091            # read another block
1092            idx += 2
1093
1094    h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
1095    return w, h
1096
1097def _gifxy(data):
1098    """read the (width, height) from a GIF header"""
1099    return struct.unpack('<HH', data[6:10])
1100
1101
1102class Image(DisplayObject):
1103
1104    _read_flags = 'rb'
1105    _FMT_JPEG = u'jpeg'
1106    _FMT_PNG = u'png'
1107    _FMT_GIF = u'gif'
1108    _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF]
1109    _MIMETYPES = {
1110        _FMT_PNG: 'image/png',
1111        _FMT_JPEG: 'image/jpeg',
1112        _FMT_GIF: 'image/gif',
1113    }
1114
1115    def __init__(self, data=None, url=None, filename=None, format=None,
1116                 embed=None, width=None, height=None, retina=False,
1117                 unconfined=False, metadata=None):
1118        """Create a PNG/JPEG/GIF image object given raw data.
1119
1120        When this object is returned by an input cell or passed to the
1121        display function, it will result in the image being displayed
1122        in the frontend.
1123
1124        Parameters
1125        ----------
1126        data : unicode, str or bytes
1127            The raw image data or a URL or filename to load the data from.
1128            This always results in embedded image data.
1129        url : unicode
1130            A URL to download the data from. If you specify `url=`,
1131            the image data will not be embedded unless you also specify `embed=True`.
1132        filename : unicode
1133            Path to a local file to load the data from.
1134            Images from a file are always embedded.
1135        format : unicode
1136            The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given
1137            for format will be inferred from the filename extension.
1138        embed : bool
1139            Should the image data be embedded using a data URI (True) or be
1140            loaded using an <img> tag. Set this to True if you want the image
1141            to be viewable later with no internet connection in the notebook.
1142
1143            Default is `True`, unless the keyword argument `url` is set, then
1144            default value is `False`.
1145
1146            Note that QtConsole is not able to display images if `embed` is set to `False`
1147        width : int
1148            Width in pixels to which to constrain the image in html
1149        height : int
1150            Height in pixels to which to constrain the image in html
1151        retina : bool
1152            Automatically set the width and height to half of the measured
1153            width and height.
1154            This only works for embedded images because it reads the width/height
1155            from image data.
1156            For non-embedded images, you can just set the desired display width
1157            and height directly.
1158        unconfined: bool
1159            Set unconfined=True to disable max-width confinement of the image.
1160        metadata: dict
1161            Specify extra metadata to attach to the image.
1162
1163        Examples
1164        --------
1165        # embedded image data, works in qtconsole and notebook
1166        # when passed positionally, the first arg can be any of raw image data,
1167        # a URL, or a filename from which to load image data.
1168        # The result is always embedding image data for inline images.
1169        Image('http://www.google.fr/images/srpr/logo3w.png')
1170        Image('/path/to/image.jpg')
1171        Image(b'RAW_PNG_DATA...')
1172
1173        # Specifying Image(url=...) does not embed the image data,
1174        # it only generates `<img>` tag with a link to the source.
1175        # This will not work in the qtconsole or offline.
1176        Image(url='http://www.google.fr/images/srpr/logo3w.png')
1177
1178        """
1179        if isinstance(data, (Path, PurePath)):
1180            data = str(data)
1181
1182        if filename is not None:
1183            ext = self._find_ext(filename)
1184        elif url is not None:
1185            ext = self._find_ext(url)
1186        elif data is None:
1187            raise ValueError("No image data found. Expecting filename, url, or data.")
1188        elif isinstance(data, str) and (
1189            data.startswith('http') or _safe_exists(data)
1190        ):
1191            ext = self._find_ext(data)
1192        else:
1193            ext = None
1194
1195        if format is None:
1196            if ext is not None:
1197                if ext == u'jpg' or ext == u'jpeg':
1198                    format = self._FMT_JPEG
1199                elif ext == u'png':
1200                    format = self._FMT_PNG
1201                elif ext == u'gif':
1202                    format = self._FMT_GIF
1203                else:
1204                    format = ext.lower()
1205            elif isinstance(data, bytes):
1206                # infer image type from image data header,
1207                # only if format has not been specified.
1208                if data[:2] == _JPEG:
1209                    format = self._FMT_JPEG
1210
1211        # failed to detect format, default png
1212        if format is None:
1213            format = self._FMT_PNG
1214
1215        if format.lower() == 'jpg':
1216            # jpg->jpeg
1217            format = self._FMT_JPEG
1218
1219        self.format = format.lower()
1220        self.embed = embed if embed is not None else (url is None)
1221
1222        if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
1223            raise ValueError("Cannot embed the '%s' image format" % (self.format))
1224        if self.embed:
1225            self._mimetype = self._MIMETYPES.get(self.format)
1226
1227        self.width = width
1228        self.height = height
1229        self.retina = retina
1230        self.unconfined = unconfined
1231        super(Image, self).__init__(data=data, url=url, filename=filename,
1232                metadata=metadata)
1233
1234        if self.width is None and self.metadata.get('width', {}):
1235            self.width = metadata['width']
1236
1237        if self.height is None and self.metadata.get('height', {}):
1238            self.height = metadata['height']
1239
1240        if retina:
1241            self._retina_shape()
1242
1243
1244    def _retina_shape(self):
1245        """load pixel-doubled width and height from image data"""
1246        if not self.embed:
1247            return
1248        if self.format == self._FMT_PNG:
1249            w, h = _pngxy(self.data)
1250        elif self.format == self._FMT_JPEG:
1251            w, h = _jpegxy(self.data)
1252        elif self.format == self._FMT_GIF:
1253            w, h = _gifxy(self.data)
1254        else:
1255            # retina only supports png
1256            return
1257        self.width = w // 2
1258        self.height = h // 2
1259
1260    def reload(self):
1261        """Reload the raw data from file or URL."""
1262        if self.embed:
1263            super(Image,self).reload()
1264            if self.retina:
1265                self._retina_shape()
1266
1267    def _repr_html_(self):
1268        if not self.embed:
1269            width = height = klass = ''
1270            if self.width:
1271                width = ' width="%d"' % self.width
1272            if self.height:
1273                height = ' height="%d"' % self.height
1274            if self.unconfined:
1275                klass = ' class="unconfined"'
1276            return u'<img src="{url}"{width}{height}{klass}/>'.format(
1277                url=self.url,
1278                width=width,
1279                height=height,
1280                klass=klass,
1281            )
1282
1283    def _repr_mimebundle_(self, include=None, exclude=None):
1284        """Return the image as a mimebundle
1285
1286        Any new mimetype support should be implemented here.
1287        """
1288        if self.embed:
1289            mimetype = self._mimetype
1290            data, metadata = self._data_and_metadata(always_both=True)
1291            if metadata:
1292                metadata = {mimetype: metadata}
1293            return {mimetype: data}, metadata
1294        else:
1295            return {'text/html': self._repr_html_()}
1296
1297    def _data_and_metadata(self, always_both=False):
1298        """shortcut for returning metadata with shape information, if defined"""
1299        try:
1300            b64_data = b2a_base64(self.data).decode('ascii')
1301        except TypeError:
1302            raise FileNotFoundError(
1303                "No such file or directory: '%s'" % (self.data))
1304        md = {}
1305        if self.metadata:
1306            md.update(self.metadata)
1307        if self.width:
1308            md['width'] = self.width
1309        if self.height:
1310            md['height'] = self.height
1311        if self.unconfined:
1312            md['unconfined'] = self.unconfined
1313        if md or always_both:
1314            return b64_data, md
1315        else:
1316            return b64_data
1317
1318    def _repr_png_(self):
1319        if self.embed and self.format == self._FMT_PNG:
1320            return self._data_and_metadata()
1321
1322    def _repr_jpeg_(self):
1323        if self.embed and self.format == self._FMT_JPEG:
1324            return self._data_and_metadata()
1325
1326    def _find_ext(self, s):
1327        base, ext = splitext(s)
1328
1329        if not ext:
1330            return base
1331
1332        # `splitext` includes leading period, so we skip it
1333        return ext[1:].lower()
1334
1335
1336class Video(DisplayObject):
1337
1338    def __init__(self, data=None, url=None, filename=None, embed=False,
1339                 mimetype=None, width=None, height=None, html_attributes="controls"):
1340        """Create a video object given raw data or an URL.
1341
1342        When this object is returned by an input cell or passed to the
1343        display function, it will result in the video being displayed
1344        in the frontend.
1345
1346        Parameters
1347        ----------
1348        data : unicode, str or bytes
1349            The raw video data or a URL or filename to load the data from.
1350            Raw data will require passing ``embed=True``.
1351        url : unicode
1352            A URL for the video. If you specify ``url=``,
1353            the image data will not be embedded.
1354        filename : unicode
1355            Path to a local file containing the video.
1356            Will be interpreted as a local URL unless ``embed=True``.
1357        embed : bool
1358            Should the video be embedded using a data URI (True) or be
1359            loaded using a <video> tag (False).
1360
1361            Since videos are large, embedding them should be avoided, if possible.
1362            You must confirm embedding as your intention by passing ``embed=True``.
1363
1364            Local files can be displayed with URLs without embedding the content, via::
1365
1366                Video('./video.mp4')
1367
1368        mimetype: unicode
1369            Specify the mimetype for embedded videos.
1370            Default will be guessed from file extension, if available.
1371        width : int
1372            Width in pixels to which to constrain the video in HTML.
1373            If not supplied, defaults to the width of the video.
1374        height : int
1375            Height in pixels to which to constrain the video in html.
1376            If not supplied, defaults to the height of the video.
1377        html_attributes : str
1378            Attributes for the HTML ``<video>`` block.
1379            Default: ``"controls"`` to get video controls.
1380            Other examples: ``"controls muted"`` for muted video with controls,
1381            ``"loop autoplay"`` for looping autoplaying video without controls.
1382
1383        Examples
1384        --------
1385
1386        ::
1387
1388            Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1389            Video('path/to/video.mp4')
1390            Video('path/to/video.mp4', embed=True)
1391            Video('path/to/video.mp4', embed=True, html_attributes="controls muted autoplay")
1392            Video(b'raw-videodata', embed=True)
1393        """
1394        if isinstance(data, (Path, PurePath)):
1395            data = str(data)
1396
1397        if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
1398            url = data
1399            data = None
1400        elif os.path.exists(data):
1401            filename = data
1402            data = None
1403
1404        if data and not embed:
1405            msg = ''.join([
1406                "To embed videos, you must pass embed=True ",
1407                "(this may make your notebook files huge)\n",
1408                "Consider passing Video(url='...')",
1409            ])
1410            raise ValueError(msg)
1411
1412        self.mimetype = mimetype
1413        self.embed = embed
1414        self.width = width
1415        self.height = height
1416        self.html_attributes = html_attributes
1417        super(Video, self).__init__(data=data, url=url, filename=filename)
1418
1419    def _repr_html_(self):
1420        width = height = ''
1421        if self.width:
1422            width = ' width="%d"' % self.width
1423        if self.height:
1424            height = ' height="%d"' % self.height
1425
1426        # External URLs and potentially local files are not embedded into the
1427        # notebook output.
1428        if not self.embed:
1429            url = self.url if self.url is not None else self.filename
1430            output = """<video src="{0}" {1} {2} {3}>
1431      Your browser does not support the <code>video</code> element.
1432    </video>""".format(url, self.html_attributes, width, height)
1433            return output
1434
1435        # Embedded videos are base64-encoded.
1436        mimetype = self.mimetype
1437        if self.filename is not None:
1438            if not mimetype:
1439                mimetype, _ = mimetypes.guess_type(self.filename)
1440
1441            with open(self.filename, 'rb') as f:
1442                video = f.read()
1443        else:
1444            video = self.data
1445        if isinstance(video, str):
1446            # unicode input is already b64-encoded
1447            b64_video = video
1448        else:
1449            b64_video = b2a_base64(video).decode('ascii').rstrip()
1450
1451        output = """<video {0} {1} {2}>
1452 <source src="data:{3};base64,{4}" type="{3}">
1453 Your browser does not support the video tag.
1454 </video>""".format(self.html_attributes, width, height, mimetype, b64_video)
1455        return output
1456
1457    def reload(self):
1458        # TODO
1459        pass
1460
1461
1462def clear_output(wait=False):
1463    """Clear the output of the current cell receiving output.
1464
1465    Parameters
1466    ----------
1467    wait : bool [default: false]
1468        Wait to clear the output until new output is available to replace it."""
1469    from IPython.core.interactiveshell import InteractiveShell
1470    if InteractiveShell.initialized():
1471        InteractiveShell.instance().display_pub.clear_output(wait)
1472    else:
1473        print('\033[2K\r', end='')
1474        sys.stdout.flush()
1475        print('\033[2K\r', end='')
1476        sys.stderr.flush()
1477
1478
1479@skip_doctest
1480def set_matplotlib_formats(*formats, **kwargs):
1481    """
1482    .. deprecated:: 7.23
1483
1484       use `matplotlib_inline.backend_inline.set_matplotlib_formats()`
1485
1486    Select figure formats for the inline backend. Optionally pass quality for JPEG.
1487
1488    For example, this enables PNG and JPEG output with a JPEG quality of 90%::
1489
1490        In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
1491
1492    To set this in your config files use the following::
1493
1494        c.InlineBackend.figure_formats = {'png', 'jpeg'}
1495        c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
1496
1497    Parameters
1498    ----------
1499    *formats : strs
1500        One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
1501    **kwargs :
1502        Keyword args will be relayed to ``figure.canvas.print_figure``.
1503    """
1504    warnings.warn(
1505        "`set_matplotlib_formats` is deprecated since IPython 7.23, directly "
1506        "use `matplotlib_inline.backend_inline.set_matplotlib_formats()`",
1507        DeprecationWarning,
1508        stacklevel=2,
1509    )
1510
1511    from matplotlib_inline.backend_inline import (
1512        set_matplotlib_formats as set_matplotlib_formats_orig,
1513    )
1514
1515    set_matplotlib_formats_orig(*formats, **kwargs)
1516
1517@skip_doctest
1518def set_matplotlib_close(close=True):
1519    """
1520    .. deprecated:: 7.23
1521
1522        use `matplotlib_inline.backend_inline.set_matplotlib_close()`
1523
1524
1525    Set whether the inline backend closes all figures automatically or not.
1526
1527    By default, the inline backend used in the IPython Notebook will close all
1528    matplotlib figures automatically after each cell is run. This means that
1529    plots in different cells won't interfere. Sometimes, you may want to make
1530    a plot in one cell and then refine it in later cells. This can be accomplished
1531    by::
1532
1533        In [1]: set_matplotlib_close(False)
1534
1535    To set this in your config files use the following::
1536
1537        c.InlineBackend.close_figures = False
1538
1539    Parameters
1540    ----------
1541    close : bool
1542        Should all matplotlib figures be automatically closed after each cell is
1543        run?
1544    """
1545    warnings.warn(
1546        "`set_matplotlib_close` is deprecated since IPython 7.23, directly "
1547        "use `matplotlib_inline.backend_inline.set_matplotlib_close()`",
1548        DeprecationWarning,
1549        stacklevel=2,
1550    )
1551
1552    from matplotlib_inline.backend_inline import (
1553        set_matplotlib_close as set_matplotlib_close_orig,
1554    )
1555
1556    set_matplotlib_close_orig(close)
1557