# -*- coding: utf-8 -*- """Top-level display functions for displaying object in different formats.""" # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. from binascii import b2a_hex, b2a_base64, hexlify import json import mimetypes import os import struct import sys import warnings from copy import deepcopy from os.path import splitext from pathlib import Path, PurePath from IPython.utils.py3compat import cast_unicode from IPython.testing.skipdoctest import skip_doctest __all__ = ['display', 'display_pretty', 'display_html', 'display_markdown', 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json', 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject', 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'ProgressBar', 'JSON', 'GeoJSON', 'Javascript', 'Image', 'clear_output', 'set_matplotlib_formats', 'set_matplotlib_close', 'publish_display_data', 'update_display', 'DisplayHandle', 'Video'] #----------------------------------------------------------------------------- # utility functions #----------------------------------------------------------------------------- def _safe_exists(path): """Check path, but don't let exceptions raise""" try: return os.path.exists(path) except Exception: return False def _merge(d1, d2): """Like update, but merges sub-dicts instead of clobbering at the top level. Updates d1 in-place """ if not isinstance(d2, dict) or not isinstance(d1, dict): return d2 for key, value in d2.items(): d1[key] = _merge(d1.get(key), value) return d1 def _display_mimetype(mimetype, objs, raw=False, metadata=None): """internal implementation of all display_foo methods Parameters ---------- mimetype : str The mimetype to be published (e.g. 'image/png') *objs : object The Python objects to display, or if raw=True raw text data to display. raw : bool Are the data objects raw data or Python objects that need to be formatted before display? [default: False] metadata : dict (optional) Metadata to be associated with the specific mimetype output. """ if metadata: metadata = {mimetype: metadata} if raw: # turn list of pngdata into list of { 'image/png': pngdata } objs = [ {mimetype: obj} for obj in objs ] display(*objs, raw=raw, metadata=metadata, include=[mimetype]) #----------------------------------------------------------------------------- # Main functions #----------------------------------------------------------------------------- # use * to indicate transient is keyword-only def publish_display_data(data, metadata=None, source=None, *, transient=None, **kwargs): """Publish data and metadata to all frontends. See the ``display_data`` message in the messaging documentation for more details about this message type. Keys of data and metadata can be any mime-type. Parameters ---------- data : dict A dictionary having keys that are valid MIME types (like 'text/plain' or 'image/svg+xml') and values that are the data for that MIME type. The data itself must be a JSON'able data structure. Minimally all data should have the 'text/plain' data, which can be displayed by all frontends. If more than the plain text is given, it is up to the frontend to decide which representation to use. metadata : dict A dictionary for metadata related to the data. This can contain arbitrary key, value pairs that frontends can use to interpret the data. mime-type keys matching those in data can be used to specify metadata about particular representations. source : str, deprecated Unused. transient : dict, keyword-only A dictionary of transient data, such as display_id. """ from IPython.core.interactiveshell import InteractiveShell display_pub = InteractiveShell.instance().display_pub # only pass transient if supplied, # to avoid errors with older ipykernel. # TODO: We could check for ipykernel version and provide a detailed upgrade message. if transient: kwargs['transient'] = transient display_pub.publish( data=data, metadata=metadata, **kwargs ) def _new_id(): """Generate a new random text id with urandom""" return b2a_hex(os.urandom(16)).decode('ascii') def display(*objs, include=None, exclude=None, metadata=None, transient=None, display_id=None, **kwargs): """Display a Python object in all frontends. By default all representations will be computed and sent to the frontends. Frontends can decide which representation is used and how. In terminal IPython this will be similar to using :func:`print`, for use in richer frontends see Jupyter notebook examples with rich display logic. Parameters ---------- *objs : object The Python objects to display. raw : bool, optional Are the objects to be displayed already mimetype-keyed dicts of raw display data, or Python objects that need to be formatted before display? [default: False] include : list, tuple or set, optional A list of format type strings (MIME types) to include in the format data dict. If this is set *only* the format types included in this list will be computed. exclude : list, tuple or set, optional A list of format type strings (MIME types) to exclude in the format data dict. If this is set all format types will be computed, except for those included in this argument. metadata : dict, optional A dictionary of metadata to associate with the output. mime-type keys in this dictionary will be associated with the individual representation formats, if they exist. transient : dict, optional A dictionary of transient data to associate with the output. Data in this dict should not be persisted to files (e.g. notebooks). display_id : str, bool optional Set an id for the display. This id can be used for updating this display area later via update_display. If given as `True`, generate a new `display_id` clear : bool, optional Should the output area be cleared before displaying anything? If True, this will wait for additional output before clearing. [default: False] kwargs: additional keyword-args, optional Additional keyword-arguments are passed through to the display publisher. Returns ------- handle: DisplayHandle Returns a handle on updatable displays for use with :func:`update_display`, if `display_id` is given. Returns :any:`None` if no `display_id` is given (default). Examples -------- >>> class Json(object): ... def __init__(self, json): ... self.json = json ... def _repr_pretty_(self, pp, cycle): ... import json ... pp.text(json.dumps(self.json, indent=2)) ... def __repr__(self): ... return str(self.json) ... >>> d = Json({1:2, 3: {4:5}}) >>> print(d) {1: 2, 3: {4: 5}} >>> display(d) { "1": 2, "3": { "4": 5 } } >>> def int_formatter(integer, pp, cycle): ... pp.text('I'*integer) >>> plain = get_ipython().display_formatter.formatters['text/plain'] >>> plain.for_type(int, int_formatter) >>> display(7-5) II >>> del plain.type_printers[int] >>> display(7-5) 2 See Also -------- :func:`update_display` Notes ----- In Python, objects can declare their textual representation using the `__repr__` method. IPython expands on this idea and allows objects to declare other, rich representations including: - HTML - JSON - PNG - JPEG - SVG - LaTeX A single object can declare some or all of these representations; all are handled by IPython's display system. The main idea of the first approach is that you have to implement special display methods when you define your class, one for each representation you want to use. Here is a list of the names of the special methods and the values they must return: - `_repr_html_`: return raw HTML as a string, or a tuple (see below). - `_repr_json_`: return a JSONable dict, or a tuple (see below). - `_repr_jpeg_`: return raw JPEG data, or a tuple (see below). - `_repr_png_`: return raw PNG data, or a tuple (see below). - `_repr_svg_`: return raw SVG data as a string, or a tuple (see below). - `_repr_latex_`: return LaTeX commands in a string surrounded by "$", or a tuple (see below). - `_repr_mimebundle_`: return a full mimebundle containing the mapping from all mimetypes to data. Use this for any mime-type not listed above. The above functions may also return the object's metadata alonside the data. If the metadata is available, the functions will return a tuple containing the data and metadata, in that order. If there is no metadata available, then the functions will return the data only. When you are directly writing your own classes, you can adapt them for display in IPython by following the above approach. But in practice, you often need to work with existing classes that you can't easily modify. You can refer to the documentation on integrating with the display system in order to register custom formatters for already existing types (:ref:`integrating_rich_display`). .. versionadded:: 5.4 display available without import .. versionadded:: 6.1 display available without import Since IPython 5.4 and 6.1 :func:`display` is automatically made available to the user without import. If you are using display in a document that might be used in a pure python context or with older version of IPython, use the following import at the top of your file:: from IPython.display import display """ from IPython.core.interactiveshell import InteractiveShell if not InteractiveShell.initialized(): # Directly print objects. print(*objs) return raw = kwargs.pop("raw", False) clear = kwargs.pop("clear", False) if transient is None: transient = {} if metadata is None: metadata={} if display_id: if display_id is True: display_id = _new_id() transient['display_id'] = display_id if kwargs.get('update') and 'display_id' not in transient: raise TypeError('display_id required for update_display') if transient: kwargs['transient'] = transient if not objs and display_id: # if given no objects, but still a request for a display_id, # we assume the user wants to insert an empty output that # can be updated later objs = [{}] raw = True if not raw: format = InteractiveShell.instance().display_formatter.format if clear: clear_output(wait=True) for obj in objs: if raw: publish_display_data(data=obj, metadata=metadata, **kwargs) else: format_dict, md_dict = format(obj, include=include, exclude=exclude) if not format_dict: # nothing to display (e.g. _ipython_display_ took over) continue if metadata: # kwarg-specified metadata gets precedence _merge(md_dict, metadata) publish_display_data(data=format_dict, metadata=md_dict, **kwargs) if display_id: return DisplayHandle(display_id) # use * for keyword-only display_id arg def update_display(obj, *, display_id, **kwargs): """Update an existing display by id Parameters ---------- obj: The object with which to update the display display_id: keyword-only The id of the display to update See Also -------- :func:`display` """ kwargs['update'] = True display(obj, display_id=display_id, **kwargs) class DisplayHandle(object): """A handle on an updatable display Call `.update(obj)` to display a new object. Call `.display(obj`) to add a new instance of this display, and update existing instances. See Also -------- :func:`display`, :func:`update_display` """ def __init__(self, display_id=None): if display_id is None: display_id = _new_id() self.display_id = display_id def __repr__(self): return "<%s display_id=%s>" % (self.__class__.__name__, self.display_id) def display(self, obj, **kwargs): """Make a new display with my id, updating existing instances. Parameters ---------- obj: object to display **kwargs: additional keyword arguments passed to display """ display(obj, display_id=self.display_id, **kwargs) def update(self, obj, **kwargs): """Update existing displays with my id Parameters ---------- obj: object to display **kwargs: additional keyword arguments passed to update_display """ update_display(obj, display_id=self.display_id, **kwargs) def display_pretty(*objs, **kwargs): """Display the pretty (default) representation of an object. Parameters ---------- *objs : object The Python objects to display, or if raw=True raw text data to display. raw : bool Are the data objects raw data or Python objects that need to be formatted before display? [default: False] metadata : dict (optional) Metadata to be associated with the specific mimetype output. """ _display_mimetype('text/plain', objs, **kwargs) def display_html(*objs, **kwargs): """Display the HTML representation of an object. Note: If raw=False and the object does not have a HTML representation, no HTML will be shown. Parameters ---------- *objs : object The Python objects to display, or if raw=True raw HTML data to display. raw : bool Are the data objects raw data or Python objects that need to be formatted before display? [default: False] metadata : dict (optional) Metadata to be associated with the specific mimetype output. """ _display_mimetype('text/html', objs, **kwargs) def display_markdown(*objs, **kwargs): """Displays the Markdown representation of an object. Parameters ---------- *objs : object The Python objects to display, or if raw=True raw markdown data to display. raw : bool Are the data objects raw data or Python objects that need to be formatted before display? [default: False] metadata : dict (optional) Metadata to be associated with the specific mimetype output. """ _display_mimetype('text/markdown', objs, **kwargs) def display_svg(*objs, **kwargs): """Display the SVG representation of an object. Parameters ---------- *objs : object The Python objects to display, or if raw=True raw svg data to display. raw : bool Are the data objects raw data or Python objects that need to be formatted before display? [default: False] metadata : dict (optional) Metadata to be associated with the specific mimetype output. """ _display_mimetype('image/svg+xml', objs, **kwargs) def display_png(*objs, **kwargs): """Display the PNG representation of an object. Parameters ---------- *objs : object The Python objects to display, or if raw=True raw png data to display. raw : bool Are the data objects raw data or Python objects that need to be formatted before display? [default: False] metadata : dict (optional) Metadata to be associated with the specific mimetype output. """ _display_mimetype('image/png', objs, **kwargs) def display_jpeg(*objs, **kwargs): """Display the JPEG representation of an object. Parameters ---------- *objs : object The Python objects to display, or if raw=True raw JPEG data to display. raw : bool Are the data objects raw data or Python objects that need to be formatted before display? [default: False] metadata : dict (optional) Metadata to be associated with the specific mimetype output. """ _display_mimetype('image/jpeg', objs, **kwargs) def display_latex(*objs, **kwargs): """Display the LaTeX representation of an object. Parameters ---------- *objs : object The Python objects to display, or if raw=True raw latex data to display. raw : bool Are the data objects raw data or Python objects that need to be formatted before display? [default: False] metadata : dict (optional) Metadata to be associated with the specific mimetype output. """ _display_mimetype('text/latex', objs, **kwargs) def display_json(*objs, **kwargs): """Display the JSON representation of an object. Note that not many frontends support displaying JSON. Parameters ---------- *objs : object The Python objects to display, or if raw=True raw json data to display. raw : bool Are the data objects raw data or Python objects that need to be formatted before display? [default: False] metadata : dict (optional) Metadata to be associated with the specific mimetype output. """ _display_mimetype('application/json', objs, **kwargs) def display_javascript(*objs, **kwargs): """Display the Javascript representation of an object. Parameters ---------- *objs : object The Python objects to display, or if raw=True raw javascript data to display. raw : bool Are the data objects raw data or Python objects that need to be formatted before display? [default: False] metadata : dict (optional) Metadata to be associated with the specific mimetype output. """ _display_mimetype('application/javascript', objs, **kwargs) def display_pdf(*objs, **kwargs): """Display the PDF representation of an object. Parameters ---------- *objs : object The Python objects to display, or if raw=True raw javascript data to display. raw : bool Are the data objects raw data or Python objects that need to be formatted before display? [default: False] metadata : dict (optional) Metadata to be associated with the specific mimetype output. """ _display_mimetype('application/pdf', objs, **kwargs) #----------------------------------------------------------------------------- # Smart classes #----------------------------------------------------------------------------- class DisplayObject(object): """An object that wraps data to be displayed.""" _read_flags = 'r' _show_mem_addr = False metadata = None def __init__(self, data=None, url=None, filename=None, metadata=None): """Create a display object given raw data. When this object is returned by an expression or passed to the display function, it will result in the data being displayed in the frontend. The MIME type of the data should match the subclasses used, so the Png subclass should be used for 'image/png' data. If the data is a URL, the data will first be downloaded and then displayed. If Parameters ---------- data : unicode, str or bytes The raw data or a URL or file to load the data from url : unicode A URL to download the data from. filename : unicode Path to a local file to load the data from. metadata : dict Dict of metadata associated to be the object when displayed """ if isinstance(data, (Path, PurePath)): data = str(data) if data is not None and isinstance(data, str): if data.startswith('http') and url is None: url = data filename = None data = None elif _safe_exists(data) and filename is None: url = None filename = data data = None self.url = url self.filename = filename # because of @data.setter methods in # subclasses ensure url and filename are set # before assigning to self.data self.data = data if metadata is not None: self.metadata = metadata elif self.metadata is None: self.metadata = {} self.reload() self._check_data() def __repr__(self): if not self._show_mem_addr: cls = self.__class__ r = "<%s.%s object>" % (cls.__module__, cls.__name__) else: r = super(DisplayObject, self).__repr__() return r def _check_data(self): """Override in subclasses if there's something to check.""" pass def _data_and_metadata(self): """shortcut for returning metadata with shape information, if defined""" if self.metadata: return self.data, deepcopy(self.metadata) else: return self.data def reload(self): """Reload the raw data from file or URL.""" if self.filename is not None: with open(self.filename, self._read_flags) as f: self.data = f.read() elif self.url is not None: # Deferred import from urllib.request import urlopen response = urlopen(self.url) data = response.read() # extract encoding from header, if there is one: encoding = None if 'content-type' in response.headers: for sub in response.headers['content-type'].split(';'): sub = sub.strip() if sub.startswith('charset'): encoding = sub.split('=')[-1].strip() break if 'content-encoding' in response.headers: # TODO: do deflate? if 'gzip' in response.headers['content-encoding']: import gzip from io import BytesIO with gzip.open(BytesIO(data), 'rt', encoding=encoding) as fp: encoding = None data = fp.read() # decode data, if an encoding was specified # We only touch self.data once since # subclasses such as SVG have @data.setter methods # that transform self.data into ... well svg. if encoding: self.data = data.decode(encoding, 'replace') else: self.data = data class TextDisplayObject(DisplayObject): """Validate that display data is text""" def _check_data(self): if self.data is not None and not isinstance(self.data, str): raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data)) class Pretty(TextDisplayObject): def _repr_pretty_(self, pp, cycle): return pp.text(self.data) class HTML(TextDisplayObject): def __init__(self, data=None, url=None, filename=None, metadata=None): def warn(): if not data: return False # # Avoid calling lower() on the entire data, because it could be a # long string and we're only interested in its beginning and end. # prefix = data[:10].lower() suffix = data[-10:].lower() return prefix.startswith("