For a long time we have been encoding rich displays into strings. Our past approaches in 2018-06-19-String-Node-Transformer.ipynb are incorrect. The code of the smart strings is create better distances, this should not require any changes to the input.
This notebook explores the get_ipython().display_formatter.mimebundle_formatter
to create representations
for certains strings like "graphviz"
, "yaml"
, "iframes"
, or "images"
.
These epigrams suggest it is okay to do silly things with strings. We'll use them for good though.
Effectively any idea that creates an input transformer to modify a display is wrong.
ip=get_ipython()
from graphviz import Source
import IPython; from IPython.display import *
from mimetypes import guess_type; guess = lambda x: guess_type(x)[0]
from abc import ABCMeta
from IPython.utils.capture import capture_output as capture
from collections import UserList
import base64
import vdom; from vdom import div, img; from vdom.svg import iframe
__all__ = 'Row', 'Column'
from pathlib import Path
from toolz.curried import excepts
Caller
will iter
ate over the callable
s, the iter
ation stops when a not None
value is returned.
class Caller(ABCMeta):
callable = set()
def __call__(self, object: str, result=None):
for callable in self.callable:
value = callable(object)
if value is None: continue
return value
StringConditions
is a callable
object that IPython
will use to te$t string conditions to customize their display
s.
class StringConditions(metaclass=Caller): ...
ip = get_ipython()
ip.display_formatter
contains the rules for printing rich displays in IPython
. Individual display rules may be set on ip.display_formatter.formatters
.
In this notebook, we are going to use ip.display_formatter.mimebundle_formatter
to compose the display data ourselves.
def load_ipython_extension(ip): ip.display_formatter.mimebundle_formatter.for_type(str, StringConditions)
__name__ == '__main__' and load_ipython_extension(get_ipython())
def unload_ipython_extension(ip): ip.display_formatter.mimebundle_formatter.type_printers.pop(str)
ip.display_formatter.mimebundle_formatter.for_type
provides the machinery to customize output display payloads.
def isgraphviz(str):
if str.lstrip('di').startswith('graph '): return {
'text/html': __import__('graphviz').Source(str)._repr_svg_()
}, {}
StringConditions.callable.add(isgraphviz)
isembed
takes a str\
ing that .startswith
"http"
& shows it as an IFrame
.
def isembed(str):
type = guess(str) or ''
if excepts(OSError, Path(str).is_file)() or str.startswith('http'):
if type.startswith('image') and not type.endswith('svg'):
return {'text/html': img(src=str)._repr_html_()}, {}
return {'text/html':
iframe(src=str, style=dict(width='100%', height="400px"))._repr_html_()}, {}
StringConditions.callable.add(isembed)
vdom
¶vdom
recently added a vdom.VDOM._repr_html_
method for static html views.
Our flexbox view will have an element
, row
, and column
. vdom.VDOM
objects do not permit raw html, to create elements from raw html we format strings.
element = div('%s', style={'flex': '1'})._repr_html_()
row = div('%s', style={'display': 'flex', 'flex-direction': 'row'})._repr_html_()
column = div('%s', style={'display': 'flex', 'flex-direction': 'column'})._repr_html_()
class Element:
def __init__(self, data=None): self.data = data
def _repr_html_(self):
with capture() as object: display(self.dom%self.data)
if object.outputs: return self.outputs[0].data, {}
raise AttributeError('_repr_html_')
Container
provides the Row._repr_html_ and Column._repr_html_
flexbox views.
class Container(UserList):
def _repr_html_(self, body=""):
for object in self:
with capture() as output: display(object)
output = output.outputs and output.outputs[0].data or {}
for key in list(output):
if key.startswith('image') and not key.endswith('xml'):
encoded = output[key]
if isinstance(encoded, bytes):
encoded = base64.b64encode(encoded).decode('utf-8')
output['text/html'] = img(src=f"data:image/{key.split('/', 1)[1]};base64,{encoded}")._repr_html_()
body += element%output.get('text/html', output.get('text/plain', str(object)))
return {Row: row, Column: column}[type(self)]%body
def __iter__(self):
for object in super().__iter__(): yield ({Row, Column} - {type(self)}).pop()(object) if isinstance(object, list) else object
class Row(Container): "A flexbox row"
class Column(Container): "A flexbox column"
yaml
inputs¶ def yaml(str): """Convenience function to return the yaml value"""; return __import__('yaml').safe_load(__import__('io').StringIO(str))
def isyaml(str):
if str.startswith('- '): return {'text/html': Row(yaml(str))._repr_html_()}, {}
StringConditions.callable.add(isyaml)
triggers = [
lambda str: str.startswith("- "),
lambda str: str.startswith('http') or excepts(OSError, Path(str).is_file)(),
lambda str: str.lstrip('di').startswith('graph ')
]