mirror of
https://github.com/pyscript/pyscript.git
synced 2022-05-01 19:47:48 +03:00
Add MIME renderer support
This commit is contained in:
@@ -14,6 +14,87 @@ import io, base64, sys
|
|||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
MIME_METHODS = {
|
||||||
|
'__repr__': 'text/plain',
|
||||||
|
'_repr_html_': 'text/html',
|
||||||
|
'_repr_markdown_': 'text/markdown',
|
||||||
|
'_repr_svg_': 'image/svg+xml',
|
||||||
|
'_repr_png_': 'image/png',
|
||||||
|
'repr_pdf_': 'application/pdf',
|
||||||
|
'_repr_jpeg_': 'image/jpeg',
|
||||||
|
'_repr_latex': 'text/latex',
|
||||||
|
'_repr_json_': 'application/json',
|
||||||
|
'_repr_javascript_': 'application/javascript',
|
||||||
|
'savefig': 'image/png'
|
||||||
|
}
|
||||||
|
|
||||||
|
def render_image(mime, value, meta):
|
||||||
|
data = f'{mime};base64,{value}'
|
||||||
|
attrs = ' '.join(['{k}="{v}"' for k, v in meta.items()])
|
||||||
|
return f'<img src="{data}" {attrs}</img>'
|
||||||
|
|
||||||
|
def identity(value, meta):
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
MIME_RENDERERS = {
|
||||||
|
'text/plain': identity,
|
||||||
|
'text/html' : identity,
|
||||||
|
'image/png' : lambda value, meta: render_image('image/png', value, meta),
|
||||||
|
'image/jpeg': lambda value, meta: render_image('image/jpeg', value, meta),
|
||||||
|
'image/svg+xml': identity,
|
||||||
|
'application/json': identity,
|
||||||
|
'application/javascript': lambda value, meta: f'<script>{value}</script>'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def eval_formatter(obj, print_method):
|
||||||
|
"""
|
||||||
|
Evaluates a formatter method.
|
||||||
|
"""
|
||||||
|
if hasattr(obj, print_method):
|
||||||
|
if print_method == 'savefig':
|
||||||
|
buf = io.BytesIO()
|
||||||
|
obj.savefig(buf, format='png')
|
||||||
|
buf.seek(0)
|
||||||
|
return base64.b64encode(buf.read()).decode('utf-8')
|
||||||
|
return getattr(obj, print_method)()
|
||||||
|
if print_method == '_repr_mimebundle_':
|
||||||
|
return {}, {}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def format_mime(obj):
|
||||||
|
"""
|
||||||
|
Formats object using _repr_x_ methods.
|
||||||
|
"""
|
||||||
|
format_dict, md_dict = eval_formatter(obj, '_repr_mimebundle_')
|
||||||
|
|
||||||
|
output, not_available = None, []
|
||||||
|
for method, mime_type in reversed(MIME_METHODS.items()):
|
||||||
|
if mime_type in format_dict:
|
||||||
|
output = format_dict[mime_type]
|
||||||
|
else:
|
||||||
|
output = eval_formatter(obj, method)
|
||||||
|
|
||||||
|
if output is None:
|
||||||
|
continue
|
||||||
|
elif mime_type not in MIME_RENDERERS:
|
||||||
|
not_available.append(mime_type)
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
if output is None:
|
||||||
|
if not_available:
|
||||||
|
console.warning(f'Rendered object requested unavailable MIME renderers: {not_available}')
|
||||||
|
output = repr(output)
|
||||||
|
mime_type = 'text/plain'
|
||||||
|
elif isinstance(output, tuple):
|
||||||
|
output, meta = output
|
||||||
|
else:
|
||||||
|
meta = {}
|
||||||
|
return MIME_RENDERERS[mime_type](output, meta), mime_type
|
||||||
|
|
||||||
|
|
||||||
class PyScript:
|
class PyScript:
|
||||||
loop = loop
|
loop = loop
|
||||||
|
|
||||||
@@ -24,23 +105,18 @@ class PyScript:
|
|||||||
if append:
|
if append:
|
||||||
child = document.createElement('div');
|
child = document.createElement('div');
|
||||||
element = document.querySelector(f'#{element_id}');
|
element = document.querySelector(f'#{element_id}');
|
||||||
if not element:
|
|
||||||
return
|
|
||||||
exec_id = exec_id or element.childElementCount + 1
|
exec_id = exec_id or element.childElementCount + 1
|
||||||
element_id = child.id = f"{element_id}-{exec_id}";
|
element_id = child.id = f"{element_id}-{exec_id}";
|
||||||
element.appendChild(child);
|
element.appendChild(child);
|
||||||
|
|
||||||
if hasattr(value, "savefig"):
|
element = document.getElementById(element_id)
|
||||||
console.log(f"FIGURE: {value}")
|
html, mime_type = format_mime(value)
|
||||||
buf = io.BytesIO()
|
console.log(mime_type, html)
|
||||||
value.savefig(buf, format='png')
|
if mime_type == 'application/javascript':
|
||||||
buf.seek(0)
|
scriptEl = document.createRange().createContextualFragment(html)
|
||||||
img_str = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('UTF-8')
|
element.children = [scriptEl]
|
||||||
document.getElementById(element_id).innerHTML = f'<div><img id="plt" src="{img_str}"/></div>'
|
|
||||||
elif hasattr(value, "startswith") and value.startswith("data:image"):
|
|
||||||
document.getElementById(element_id).innerHTML = f'<div><img id="plt" src="{value}"/></div>'
|
|
||||||
else:
|
else:
|
||||||
document.getElementById(element_id).innerHTML = value;
|
element.innerHTML = html
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def run_until_complete(f):
|
def run_until_complete(f):
|
||||||
@@ -109,7 +185,6 @@ class Element:
|
|||||||
|
|
||||||
return Element(clone.id, clone)
|
return Element(clone.id, clone)
|
||||||
|
|
||||||
|
|
||||||
def remove_class(self, classname):
|
def remove_class(self, classname):
|
||||||
if isinstance(classname, list):
|
if isinstance(classname, list):
|
||||||
for cl in classname:
|
for cl in classname:
|
||||||
@@ -278,7 +353,6 @@ class PyListTemplate:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class OutputCtxManager:
|
class OutputCtxManager:
|
||||||
def __init__(self, out=None, output_to_console=True, append=True):
|
def __init__(self, out=None, output_to_console=True, append=True):
|
||||||
self._out = out
|
self._out = out
|
||||||
|
|||||||
@@ -1,9 +1,91 @@
|
|||||||
from js import document, setInterval, console
|
from js import document, setInterval, console
|
||||||
|
import micropip
|
||||||
import asyncio
|
import asyncio
|
||||||
import io, base64
|
import io, base64, sys
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
MIME_METHODS = {
|
||||||
|
'__repr__': 'text/plain',
|
||||||
|
'_repr_html_': 'text/html',
|
||||||
|
'_repr_markdown_': 'text/markdown',
|
||||||
|
'_repr_svg_': 'image/svg+xml',
|
||||||
|
'_repr_png_': 'image/png',
|
||||||
|
'repr_pdf_': 'application/pdf',
|
||||||
|
'_repr_jpeg_': 'image/jpeg',
|
||||||
|
'_repr_latex': 'text/latex',
|
||||||
|
'_repr_json_': 'application/json',
|
||||||
|
'_repr_javascript_': 'application/javascript',
|
||||||
|
'savefig': 'image/png'
|
||||||
|
}
|
||||||
|
|
||||||
|
def render_image(mime, value, meta):
|
||||||
|
data = f'{mime};base64,{value}'
|
||||||
|
attrs = ' '.join(['{k}="{v}"' for k, v in meta.items()])
|
||||||
|
return f'<img src="{data}" {attrs}</img>'
|
||||||
|
|
||||||
|
def identity(value, meta):
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
MIME_RENDERERS = {
|
||||||
|
'text/plain': identity,
|
||||||
|
'text/html' : identity,
|
||||||
|
'image/png' : lambda value, meta: render_image('image/png', value, meta),
|
||||||
|
'image/jpeg': lambda value, meta: render_image('image/jpeg', value, meta),
|
||||||
|
'image/svg+xml': identity,
|
||||||
|
'application/json': identity,
|
||||||
|
'application/javascript': lambda value, meta: f'<script>{value}</script>'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def eval_formatter(obj, print_method):
|
||||||
|
"""
|
||||||
|
Evaluates a formatter method.
|
||||||
|
"""
|
||||||
|
if hasattr(obj, print_method):
|
||||||
|
if print_method == 'savefig':
|
||||||
|
buf = io.BytesIO()
|
||||||
|
obj.savefig(buf, format='png')
|
||||||
|
buf.seek(0)
|
||||||
|
return base64.b64encode(buf.read()).decode('utf-8')
|
||||||
|
return getattr(obj, print_method)()
|
||||||
|
if print_method == '_repr_mimebundle_':
|
||||||
|
return {}, {}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def format_mime(obj):
|
||||||
|
"""
|
||||||
|
Formats object using _repr_x_ methods.
|
||||||
|
"""
|
||||||
|
format_dict, md_dict = eval_formatter(obj, '_repr_mimebundle_')
|
||||||
|
|
||||||
|
output, not_available = None, []
|
||||||
|
for method, mime_type in reversed(MIME_METHODS.items()):
|
||||||
|
if mime_type in format_dict:
|
||||||
|
output = format_dict[mime_type]
|
||||||
|
else:
|
||||||
|
output = eval_formatter(obj, method)
|
||||||
|
|
||||||
|
if output is None:
|
||||||
|
continue
|
||||||
|
elif mime_type not in MIME_RENDERERS:
|
||||||
|
not_available.append(mime_type)
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
if output is None:
|
||||||
|
if not_available:
|
||||||
|
console.warning(f'Rendered object requested unavailable MIME renderers: {not_available}')
|
||||||
|
output = repr(output)
|
||||||
|
mime_type = 'text/plain'
|
||||||
|
elif isinstance(output, tuple):
|
||||||
|
output, meta = output
|
||||||
|
else:
|
||||||
|
meta = {}
|
||||||
|
return MIME_RENDERERS[mime_type](output, meta), mime_type
|
||||||
|
|
||||||
|
|
||||||
class PyScript:
|
class PyScript:
|
||||||
loop = loop
|
loop = loop
|
||||||
|
|
||||||
@@ -18,19 +100,14 @@ class PyScript:
|
|||||||
element_id = child.id = f"{element_id}-{exec_id}";
|
element_id = child.id = f"{element_id}-{exec_id}";
|
||||||
element.appendChild(child);
|
element.appendChild(child);
|
||||||
|
|
||||||
if hasattr(value, "savefig"):
|
element = document.getElementById(element_id)
|
||||||
console.log(f"FIGURE: {value}")
|
html, mime_type = format_mime(value)
|
||||||
buf = io.BytesIO()
|
console.log(mime_type, html)
|
||||||
value.savefig(buf, format='png')
|
if mime_type == 'application/javascript':
|
||||||
buf.seek(0)
|
scriptEl = document.createRange().createContextualFragment(html)
|
||||||
img_str = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('UTF-8')
|
element.children = [scriptEl]
|
||||||
document.getElementById(element_id).innerHTML = f'<div><img id="plt" src="{img_str}"/></div>'
|
|
||||||
elif hasattr(value, "startswith") and value.startswith("data:image"):
|
|
||||||
console.log(f"DATA/IMAGE: {value}")
|
|
||||||
document.getElementById(element_id).innerHTML = f'<div><img id="plt" src="{value}"/></div>'
|
|
||||||
else:
|
else:
|
||||||
document.getElementById(element_id).innerHTML = repr(value);
|
element.innerHTML = html
|
||||||
console.log(f"ELSE: {append} ==> {element_id} --> {value}")
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def run_until_complete(f):
|
def run_until_complete(f):
|
||||||
@@ -86,5 +163,3 @@ class Element:
|
|||||||
self.element.after(clone);
|
self.element.after(clone);
|
||||||
|
|
||||||
return Element(clone.id, clone)
|
return Element(clone.id, clone)
|
||||||
|
|
||||||
pyscript = PyScript()
|
|
||||||
|
|||||||
Reference in New Issue
Block a user