1"""Display rendered graph as SVG in Jupyter Notebooks and QtConsole.""" 
    2 
    3from collections.abc import Iterable, Mapping, Set 
    4from typing import Final 
    5 
    6from . import piping 
    7 
    8__all__ = ['JUPYTER_FORMATS', 
    9           'SUPPORTED_JUPYTER_FORMATS', 'DEFAULT_JUPYTER_FORMAT', 
    10           'get_jupyter_format_mimetype', 
    11           'JupyterIntegration'] 
    12 
    13_IMAGE_JPEG = 'image/jpeg' 
    14 
    15JUPYTER_FORMATS: Final[Mapping[str, str]] = {'jpeg': _IMAGE_JPEG, 
    16                                             'jpg': _IMAGE_JPEG, 
    17                                             'png': 'image/png', 
    18                                             'svg': 'image/svg+xml'} 
    19 
    20SUPPORTED_JUPYTER_FORMATS: Final[Set[str]] = set(JUPYTER_FORMATS) 
    21 
    22DEFAULT_JUPYTER_FORMAT: Final = next(_ for _ in SUPPORTED_JUPYTER_FORMATS 
    23                                     if _ == 'svg') 
    24 
    25MIME_TYPES: Final[Mapping[str, str]] = {'image/jpeg': '_repr_image_jpeg', 
    26                                        'image/png': '_repr_image_png', 
    27                                        'image/svg+xml': '_repr_image_svg_xml'} 
    28 
    29assert MIME_TYPES.keys() == set(JUPYTER_FORMATS.values()) 
    30 
    31SVG_ENCODING: Final = 'utf-8' 
    32 
    33 
    34def get_jupyter_format_mimetype(jupyter_format: str) -> str: 
    35    try: 
    36        return JUPYTER_FORMATS[jupyter_format] 
    37    except KeyError: 
    38        raise ValueError(f'unknown jupyter_format: {jupyter_format!r}' 
    39                         f' (must be one of {sorted(JUPYTER_FORMATS)})') 
    40 
    41 
    42def get_jupyter_mimetype_format(mimetype: str) -> str: 
    43    if mimetype not in MIME_TYPES: 
    44        raise ValueError(f'unsupported mimetype: {mimetype!r}' 
    45                         f' (must be one of {sorted(MIME_TYPES)})') 
    46 
    47    assert mimetype in JUPYTER_FORMATS.values() 
    48 
    49    for format, jupyter_mimetype in JUPYTER_FORMATS.items(): 
    50        if jupyter_mimetype == mimetype: 
    51            return format 
    52 
    53    raise RuntimeError  # pragma: no cover 
    54 
    55 
    56class JupyterIntegration(piping.Pipe): 
    57    """Display rendered graph as SVG in Jupyter Notebooks and QtConsole.""" 
    58 
    59    _jupyter_mimetype = get_jupyter_format_mimetype(DEFAULT_JUPYTER_FORMAT) 
    60 
    61    def _repr_mimebundle_(self, 
    62                          include: Iterable[str] | None = None, 
    63                          exclude: Iterable[str] | None = None, 
    64                          **_) -> dict[str, bytes | str]: 
    65        r"""Return the rendered graph as IPython mimebundle. 
    66 
    67        Args: 
    68            include: Iterable of mimetypes to include in the result. 
    69                If not given or ``None``: ``['image/sxg+xml']``. 
    70            exclude: Iterable of minetypes to exclude from the result. 
    71                Overrides ``include``. 
    72 
    73        Returns: 
    74            Mapping from mimetypes to data. 
    75 
    76        Example: 
    77            >>> doctest_mark_exe() 
    78            >>> import graphviz 
    79            >>> dot = graphviz.Graph() 
    80            >>> dot._repr_mimebundle_()  # doctest: +ELLIPSIS 
    81            {'image/svg+xml': '<?xml version=... 
    82            >>> dot._repr_mimebundle_(include=['image/png'])  # doctest: +ELLIPSIS 
    83            {'image/png': b'\x89PNG... 
    84            >>> dot._repr_mimebundle_(include=[]) 
    85            {} 
    86            >>> dot._repr_mimebundle_(include=['image/svg+xml', 'image/jpeg'], 
    87            ...                       exclude=['image/svg+xml'])  # doctest: +ELLIPSIS 
    88            {'image/jpeg': b'\xff... 
    89            >>> list(dot._repr_mimebundle_(include=['image/png', 'image/jpeg'])) 
    90            ['image/jpeg', 'image/png'] 
    91 
    92        See also: 
    93            IPython documentation: 
    94            - https://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html#functions 
    95            - https://ipython.readthedocs.io/en/stable/config/integrating.html#MyObject._repr_mimebundle_  # noqa: E501 
    96            - https://nbviewer.org/github/ipython/ipython/blob/master/examples/IPython%20Kernel/Custom%20Display%20Logic.ipynb#Custom-Mimetypes-with-_repr_mimebundle_  # noqa: E501 
    97        """ 
    98        include = set(include) if include is not None else {self._jupyter_mimetype} 
    99        include -= set(exclude or []) 
    100        return {mimetype: getattr(self, method_name)() 
    101                for mimetype, method_name in MIME_TYPES.items() 
    102                if mimetype in include} 
    103 
    104    def _repr_image_jpeg(self) -> bytes: 
    105        """Return the rendered graph as JPEG bytes.""" 
    106        return self.pipe(format='jpeg') 
    107 
    108    def _repr_image_png(self) -> bytes: 
    109        """Return the rendered graph as PNG bytes.""" 
    110        return self.pipe(format='png') 
    111 
    112    def _repr_image_svg_xml(self) -> str: 
    113        """Return the rendered graph as SVG string.""" 
    114        return self.pipe(format='svg', encoding=SVG_ENCODING)