diff --git a/pyscriptjs/src/interpreter.ts b/pyscriptjs/src/interpreter.ts
index 48ddd62..d3c1aed 100644
--- a/pyscriptjs/src/interpreter.ts
+++ b/pyscriptjs/src/interpreter.ts
@@ -14,6 +14,87 @@ import io, base64, sys
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'
'
+
+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''
+}
+
+
+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:
loop = loop
@@ -24,23 +105,18 @@ class PyScript:
if append:
child = document.createElement('div');
element = document.querySelector(f'#{element_id}');
- if not element:
- return
exec_id = exec_id or element.childElementCount + 1
element_id = child.id = f"{element_id}-{exec_id}";
element.appendChild(child);
- if hasattr(value, "savefig"):
- console.log(f"FIGURE: {value}")
- buf = io.BytesIO()
- value.savefig(buf, format='png')
- buf.seek(0)
- img_str = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('UTF-8')
- document.getElementById(element_id).innerHTML = f'

'
- elif hasattr(value, "startswith") and value.startswith("data:image"):
- document.getElementById(element_id).innerHTML = f'
'
+ element = document.getElementById(element_id)
+ html, mime_type = format_mime(value)
+ console.log(mime_type, html)
+ if mime_type == 'application/javascript':
+ scriptEl = document.createRange().createContextualFragment(html)
+ element.children = [scriptEl]
else:
- document.getElementById(element_id).innerHTML = value;
+ element.innerHTML = html
@staticmethod
def run_until_complete(f):
@@ -109,7 +185,6 @@ class Element:
return Element(clone.id, clone)
-
def remove_class(self, classname):
if isinstance(classname, list):
for cl in classname:
@@ -277,7 +352,6 @@ class PyListTemplate:
"""Overwrite me to define logic"""
pass
-
class OutputCtxManager:
def __init__(self, out=None, output_to_console=True, append=True):
diff --git a/pyscriptjs/src/pyscript.py b/pyscriptjs/src/pyscript.py
index 81fb12d..06a61d9 100644
--- a/pyscriptjs/src/pyscript.py
+++ b/pyscriptjs/src/pyscript.py
@@ -1,9 +1,91 @@
from js import document, setInterval, console
+import micropip
import asyncio
-import io, base64
+import io, base64, sys
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'
'
+
+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''
+}
+
+
+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:
loop = loop
@@ -18,19 +100,14 @@ class PyScript:
element_id = child.id = f"{element_id}-{exec_id}";
element.appendChild(child);
- if hasattr(value, "savefig"):
- console.log(f"FIGURE: {value}")
- buf = io.BytesIO()
- value.savefig(buf, format='png')
- buf.seek(0)
- img_str = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('UTF-8')
- document.getElementById(element_id).innerHTML = f'
'
- elif hasattr(value, "startswith") and value.startswith("data:image"):
- console.log(f"DATA/IMAGE: {value}")
- document.getElementById(element_id).innerHTML = f'
'
+ element = document.getElementById(element_id)
+ html, mime_type = format_mime(value)
+ console.log(mime_type, html)
+ if mime_type == 'application/javascript':
+ scriptEl = document.createRange().createContextualFragment(html)
+ element.children = [scriptEl]
else:
- document.getElementById(element_id).innerHTML = repr(value);
- console.log(f"ELSE: {append} ==> {element_id} --> {value}")
+ element.innerHTML = html
@staticmethod
def run_until_complete(f):
@@ -86,5 +163,3 @@ class Element:
self.element.after(clone);
return Element(clone.id, clone)
-
-pyscript = PyScript()