found, adding one");
const termElem = document.createElement("py-terminal");
if (auto)
termElem.setAttribute("auto", "");
if (docked)
termElem.setAttribute("docked", "");
if (xterm)
termElem.setAttribute("xterm", "");
document.body.appendChild(termElem);
}
}
afterSetup(_interpreter) {
const PyTerminal = _interpreter.config.xterm ? make_PyTerminal_xterm(this.app) : make_PyTerminal_pre(this.app);
customElements.define("py-terminal", PyTerminal);
}
};
var PyTerminalBaseClass = class extends HTMLElement {
isAuto() {
return this.hasAttribute("auto");
}
isDocked() {
return this.hasAttribute("docked");
}
setupPosition(app) {
if (this.isAuto()) {
this.classList.add("py-terminal-hidden");
this.autoShowOnNextLine = true;
} else {
this.autoShowOnNextLine = false;
}
if (this.isDocked()) {
this.classList.add("py-terminal-docked");
}
logger7.info("Registering stdio listener");
app.registerStdioListener(this);
}
};
function make_PyTerminal_pre(app) {
class PyTerminalPre extends PyTerminalBaseClass {
connectedCallback() {
this.outElem = document.createElement("pre");
this.outElem.classList.add("py-terminal");
this.appendChild(this.outElem);
this.setupPosition(app);
}
// implementation of the Stdio interface
stdout_writeline(msg) {
this.outElem.innerText += msg + "\n";
if (this.isDocked()) {
this.scrollTop = this.scrollHeight;
}
if (this.autoShowOnNextLine) {
this.classList.remove("py-terminal-hidden");
this.autoShowOnNextLine = false;
}
}
stderr_writeline(msg) {
this.stdout_writeline(msg);
}
// end of the Stdio interface
}
return PyTerminalPre;
}
function make_PyTerminal_xterm(app) {
class PyTerminalXterm extends PyTerminalBaseClass {
constructor() {
super();
this._xterm_cdn_base_url = "https://cdn.jsdelivr.net/npm/xterm@5.1.0";
this.cachedStdOut = [];
this.cachedStdErr = [];
this._moduleResolved = false;
this.style.width = "100%";
this.style.height = "100%";
}
async connectedCallback() {
if (knownPyTerminalTags.has(this))
return;
knownPyTerminalTags.add(this);
this.outElem = document.createElement("div");
this.appendChild(this.outElem);
this.setupPosition(app);
this.xtermReady = this._setupXterm();
await this.xtermReady;
}
/**
* Fetch the xtermjs library from CDN an initialize it.
* @private
* @returns the associated xterm.js Terminal
*/
async _setupXterm() {
if (this.xterm == void 0) {
if (globalThis.Terminal == void 0) {
await import(this._xterm_cdn_base_url + "/lib/xterm.js");
const cssTag = document.createElement("link");
cssTag.type = "text/css";
cssTag.rel = "stylesheet";
cssTag.href = this._xterm_cdn_base_url + "/css/xterm.css";
document.head.appendChild(cssTag);
}
this.xterm = new Terminal({ screenReaderMode: true, cols: 80 });
if (!this.autoShowOnNextLine)
this.xterm.open(this);
this._moduleResolved = true;
this.cachedStdOut.forEach((value) => this.stdout_writeline(value));
this.cachedStdErr.forEach((value) => this.stderr_writeline(value));
} else {
this._moduleResolved = true;
}
return this.xterm;
}
// implementation of the Stdio interface
stdout_writeline(msg) {
if (this._moduleResolved) {
this.xterm.writeln(msg);
if (this.isDocked()) {
this.scrollTop = this.scrollHeight;
}
if (this.autoShowOnNextLine) {
this.classList.remove("py-terminal-hidden");
this.autoShowOnNextLine = false;
this.xterm.open(this);
}
} else {
this.cachedStdOut.push(msg);
}
}
stderr_writeline(msg) {
this.stdout_writeline(msg);
}
// end of the Stdio interface
}
return PyTerminalXterm;
}
// src/plugins/splashscreen.ts
var logger8 = getLogger("py-splashscreen");
var AUTOCLOSE_LOADER_DEPRECATED = `
The setting autoclose_loader is deprecated. Please use the
following instead:
<py-config>
[splashscreen]
autoclose = false
</py-config>
`;
var SplashscreenPlugin = class extends Plugin {
configure(config2) {
this.autoclose = true;
this.enabled = true;
if ("autoclose_loader" in config2) {
this.autoclose = config2.autoclose_loader;
showWarning(AUTOCLOSE_LOADER_DEPRECATED, "html");
}
if (config2.splashscreen) {
this.autoclose = config2.splashscreen.autoclose ?? true;
this.enabled = config2.splashscreen.enabled ?? true;
}
}
beforeLaunch(_config) {
if (!this.enabled) {
return;
}
logger8.info("add py-splashscreen");
customElements.define("py-splashscreen", PySplashscreen);
this.elem = document.createElement("py-splashscreen");
document.body.append(this.elem);
document.addEventListener("py-status-message", (e) => {
const msg = e.detail;
this.elem.log(msg);
});
}
afterStartup(_interpreter) {
if (this.autoclose && this.enabled) {
this.elem.close();
}
}
onUserError(_error) {
if (this.elem !== void 0 && this.enabled) {
this.elem.close();
}
}
};
var PySplashscreen = class extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.innerHTML = ``;
this.mount_name = this.id.split("-").join("_");
this.operation = $("#pyscript-operation", document);
this.details = $("#pyscript-operation-details", document);
}
log(msg) {
const newLog = document.createElement("p");
newLog.innerText = msg;
this.details.appendChild(newLog);
}
close() {
logger8.info("Closing");
this.remove();
}
};
// src/plugins/importmap.ts
var logger9 = getLogger("plugins/importmap");
var ImportmapPlugin = class extends Plugin {
async afterSetup(interpreter2) {
for (const node of $$("script[type='importmap']", document)) {
const importmap = (() => {
try {
return JSON.parse(node.textContent);
} catch (e) {
const error = e;
showWarning("Failed to parse import map: " + error.message);
}
})();
if (importmap?.imports == null)
continue;
for (const [name2, url] of Object.entries(importmap.imports)) {
if (typeof name2 != "string" || typeof url != "string")
continue;
let exports;
try {
exports = { ...await import(url) };
} catch {
logger9.warn(`failed to fetch '${url}' for '${name2}'`);
continue;
}
logger9.info("Registering JS module", name2);
await interpreter2._remote.registerJsModule(name2, exports);
}
}
}
};
// src/plugins/stdiodirector.ts
var StdioDirector = class extends Plugin {
constructor(stdio) {
super();
this._stdioMultiplexer = stdio;
}
/** Prior to a tag being evaluated, if that tag itself has
* an 'output' attribute, a new TargetedStdio object is created and added
* to the stdioMultiplexer to route sys.stdout and sys.stdout to the DOM object
* with that ID for the duration of the evaluation.
*
*/
beforePyScriptExec(options) {
if (options.pyScriptTag.hasAttribute("output")) {
const targeted_io = new TargetedStdio(options.pyScriptTag, "output", true, true);
options.pyScriptTag.stdout_manager = targeted_io;
this._stdioMultiplexer.addListener(targeted_io);
}
if (options.pyScriptTag.hasAttribute("stderr")) {
const targeted_io = new TargetedStdio(options.pyScriptTag, "stderr", false, true);
options.pyScriptTag.stderr_manager = targeted_io;
this._stdioMultiplexer.addListener(targeted_io);
}
}
/** After a tag is evaluated, if that tag has a 'stdout_manager'
* (presumably TargetedStdio, or some other future IO handler), it is removed.
*/
afterPyScriptExec(options) {
if (options.pyScriptTag.stdout_manager != null) {
this._stdioMultiplexer.removeListener(options.pyScriptTag.stdout_manager);
options.pyScriptTag.stdout_manager = null;
}
if (options.pyScriptTag.stderr_manager != null) {
this._stdioMultiplexer.removeListener(options.pyScriptTag.stderr_manager);
options.pyScriptTag.stderr_manager = null;
}
}
beforePyReplExec(options) {
if (options.pyReplTag.getAttribute("output-mode") != "append") {
options.outEl.innerHTML = "";
}
let output_targeted_io;
if (options.pyReplTag.hasAttribute("output")) {
output_targeted_io = new TargetedStdio(options.pyReplTag, "output", true, true);
} else {
output_targeted_io = new TargetedStdio(options.pyReplTag.outDiv, "id", true, true);
}
options.pyReplTag.stdout_manager = output_targeted_io;
this._stdioMultiplexer.addListener(output_targeted_io);
if (options.pyReplTag.hasAttribute("stderr")) {
const stderr_targeted_io = new TargetedStdio(options.pyReplTag, "stderr", false, true);
options.pyReplTag.stderr_manager = stderr_targeted_io;
this._stdioMultiplexer.addListener(stderr_targeted_io);
}
}
async afterPyReplExec(options) {
if (options.result !== void 0) {
const outputId = options.pyReplTag.getAttribute("output");
if (outputId) {
if ($("#" + outputId, document)) {
await pyDisplay(options.interpreter, options.result, { target: outputId });
} else {
createSingularWarning(`output = "${outputId}" does not match the id of any element on the page.`);
}
} else {
await pyDisplay(options.interpreter, options.result, { target: options.outEl.id });
}
}
if (options.pyReplTag.stdout_manager != null) {
this._stdioMultiplexer.removeListener(options.pyReplTag.stdout_manager);
options.pyReplTag.stdout_manager = null;
}
if (options.pyReplTag.stderr_manager != null) {
this._stdioMultiplexer.removeListener(options.pyReplTag.stderr_manager);
options.pyReplTag.stderr_manager = null;
}
}
};
// bundlePyscriptPythonPlugin:dummy
var dummy_default = { dirs: ["pyscript"], files: [["pyscript/_deprecated_globals.py", `from _pyscript_js import showWarning
class DeprecatedGlobal:
"""
Proxy for globals which are deprecated.
The intendend usage is as follows:
# in the global namespace
Element = pyscript.DeprecatedGlobal('Element', pyscript.Element, "...")
console = pyscript.DeprecatedGlobal('console', js.console, "...")
...
The proxy forwards __getattr__ and __call__ to the underlying object, and
emit a warning on the first usage.
This way users see a warning only if they actually access the top-level
name.
"""
def __init__(self, name, obj, message):
self.__name = name
self.__obj = obj
self.__message = message
self.__warning_already_shown = False
def __repr__(self):
return f""
def _show_warning(self, message):
"""
NOTE: this is overridden by unit tests
"""
showWarning(message, "html") # noqa: F821
def _show_warning_maybe(self):
if self.__warning_already_shown:
return
self._show_warning(self.__message)
self.__warning_already_shown = True
def __getattr__(self, attr):
self._show_warning_maybe()
return getattr(self.__obj, attr)
def __call__(self, *args, **kwargs):
self._show_warning_maybe()
return self.__obj(*args, **kwargs)
def __iter__(self):
self._show_warning_maybe()
return iter(self.__obj)
def __getitem__(self, key):
self._show_warning_maybe()
return self.__obj[key]
def __setitem__(self, key, value):
self._show_warning_maybe()
self.__obj[key] = value
`], ["pyscript/_event_handling.py", `import inspect
import js
from pyodide.ffi.wrappers import add_event_listener
def when(event_type=None, selector=None):
"""
Decorates a function and passes py-* events to the decorated function
The events might or not be an argument of the decorated function
"""
def decorator(func):
elements = js.document.querySelectorAll(selector)
sig = inspect.signature(func)
# Function doesn't receive events
if not sig.parameters:
def wrapper(*args, **kwargs):
func()
for el in elements:
add_event_listener(el, event_type, wrapper)
else:
for el in elements:
add_event_listener(el, event_type, func)
return func
return decorator
`], ["pyscript/_event_loop.py", `import asyncio
import contextvars
from collections.abc import Callable
from contextlib import contextmanager
from typing import Any
from js import setTimeout
from pyodide.ffi import create_once_callable
from pyodide.webloop import WebLoop
class PyscriptWebLoop(WebLoop):
def __init__(self):
super().__init__()
self._ready = False
self._usercode = False
self._deferred_handles = []
def call_later(
self,
delay: float,
callback: Callable[..., Any],
*args: Any,
context: contextvars.Context | None = None,
) -> asyncio.Handle:
"""Based on call_later from Pyodide's webloop
With some unneeded stuff removed and a mechanism for deferring tasks
scheduled from user code.
"""
if delay < 0:
raise ValueError("Can't schedule in the past")
h = asyncio.Handle(callback, args, self, context=context)
def run_handle():
if h.cancelled():
return
h._run()
if self._ready or not self._usercode:
setTimeout(create_once_callable(run_handle), delay * 1000)
else:
self._deferred_handles.append((run_handle, self.time() + delay))
return h
def _schedule_deferred_tasks(self):
asyncio._set_running_loop(self)
t = self.time()
for [run_handle, delay] in self._deferred_handles:
delay = delay - t
if delay < 0:
delay = 0
setTimeout(create_once_callable(run_handle), delay * 1000)
self._ready = True
self._deferred_handles = []
LOOP = None
def install_pyscript_loop():
global LOOP
LOOP = PyscriptWebLoop()
asyncio.set_event_loop(LOOP)
def schedule_deferred_tasks():
LOOP._schedule_deferred_tasks()
@contextmanager
def defer_user_asyncio():
LOOP._usercode = True
try:
yield
finally:
LOOP._usercode = False
def run_until_complete(f):
return LOOP.run_until_complete(f)
`], ["pyscript/_html.py", `from textwrap import dedent
import js
from _pyscript_js import deepQuerySelector
from . import _internal
from ._mime import format_mime as _format_mime
class HTML:
"""
Wrap a string so that display() can render it as plain HTML
"""
def __init__(self, html):
self._html = html
def _repr_html_(self):
return self._html
def write(element_id, value, append=False, exec_id=0):
"""Writes value to the element with id "element_id"""
Element(element_id).write(value=value, append=append)
js.console.warn(
dedent(
"""PyScript Deprecation Warning: PyScript.write is
marked as deprecated and will be removed sometime soon. Please, use
Element().write instead."""
)
)
def display(*values, target=None, append=True):
if target is None:
target = _internal.DISPLAY_TARGET
if target is None:
raise Exception(
"Implicit target not allowed here. Please use display(..., target=...)"
)
for v in values:
Element(target).write(v, append=append)
class Element:
def __init__(self, element_id, element=None):
self._id = element_id
self._element = element
@property
def id(self):
return self._id
@property
def element(self):
"""Return the dom element"""
if not self._element:
self._element = deepQuerySelector(f"#{self._id}")
return self._element
@property
def value(self):
return self.element.value
@property
def innerHtml(self):
return self.element.innerHTML
def write(self, value, append=False):
html, mime_type = _format_mime(value)
if html == "\\n":
return
if append:
child = js.document.createElement("div")
self.element.appendChild(child)
if append and self.element.children:
out_element = self.element.children[-1]
else:
out_element = self.element
if mime_type in ("application/javascript", "text/html"):
script_element = js.document.createRange().createContextualFragment(html)
out_element.appendChild(script_element)
else:
out_element.innerHTML = html
def clear(self):
if hasattr(self.element, "value"):
self.element.value = ""
else:
self.write("", append=False)
def select(self, query, from_content=False):
el = self.element
if from_content:
el = el.content
_el = el.querySelector(query)
if _el:
return Element(_el.id, _el)
else:
js.console.warn(f"WARNING: can't find element matching query {query}")
def clone(self, new_id=None, to=None):
if new_id is None:
new_id = self.element.id
clone = self.element.cloneNode(True)
clone.id = new_id
if to:
to.element.appendChild(clone)
# Inject it into the DOM
to.element.after(clone)
else:
# Inject it into the DOM
self.element.after(clone)
return Element(clone.id, clone)
def remove_class(self, classname):
classList = self.element.classList
if isinstance(classname, list):
classList.remove(*classname)
else:
classList.remove(classname)
def add_class(self, classname):
classList = self.element.classList
if isinstance(classname, list):
classList.add(*classname)
else:
self.element.classList.add(classname)
def add_classes(element, class_list):
classList = element.classList
classList.add(*class_list.split(" "))
def create(what, id_=None, classes=""):
element = js.document.createElement(what)
if id_:
element.id = id_
add_classes(element, classes)
return Element(id_, element)
`], ["pyscript/_internal.py", 'import ast\nfrom collections import namedtuple\nfrom contextlib import contextmanager\n\nfrom js import Object\nfrom pyodide.code import eval_code\nfrom pyodide.ffi import JsProxy, to_js\n\nfrom ._event_loop import (\n defer_user_asyncio,\n install_pyscript_loop,\n schedule_deferred_tasks,\n)\n\nVersionInfo = namedtuple("version_info", ("year", "month", "minor", "releaselevel"))\n\n\ndef set_version_info(version_from_interpreter: str):\n from . import __dict__ as pyscript_dict\n\n """Sets the __version__ and version_info properties from provided JSON data\n Args:\n version_from_interpreter (str): A "dotted" representation of the version:\n YYYY.MM.m(m).releaselevel\n Year, Month, and Minor should be integers; releaselevel can be any string\n """\n\n version_parts = version_from_interpreter.split(".")\n year = int(version_parts[0])\n month = int(version_parts[1])\n minor = int(version_parts[2])\n if len(version_parts) > 3:\n releaselevel = version_parts[3]\n else:\n releaselevel = ""\n\n version_info = VersionInfo(year, month, minor, releaselevel)\n\n pyscript_dict["__version__"] = version_from_interpreter\n pyscript_dict["version_info"] = version_info\n\n\nclass TopLevelAwaitFinder(ast.NodeVisitor):\n def is_source_top_level_await(self, source):\n self.async_found = False\n node = ast.parse(source)\n self.generic_visit(node)\n return self.async_found\n\n def visit_Await(self, node):\n self.async_found = True\n\n def visit_AsyncFor(self, node):\n self.async_found = True\n\n def visit_AsyncWith(self, node):\n self.async_found = True\n\n def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef):\n pass # Do not visit children of async function defs\n\n\ndef uses_top_level_await(source: str) -> bool:\n return TopLevelAwaitFinder().is_source_top_level_await(source)\n\n\nDISPLAY_TARGET = None\n\n\n@contextmanager\ndef display_target(target_id):\n global DISPLAY_TARGET\n DISPLAY_TARGET = target_id\n try:\n yield\n finally:\n DISPLAY_TARGET = None\n\n\ndef run_pyscript(code: str, id: str = None) -> JsProxy:\n """Execute user code inside context managers.\n\n Uses the __main__ global namespace.\n\n The output is wrapped inside a JavaScript object since an object is not\n thenable. This is so we do not accidentally `await` the result of the python\n execution, even if it\'s awaitable (Future, Task, etc.)\n\n Parameters\n ----------\n code :\n The code to run\n\n id :\n The id for the default display target (or None if no default display target).\n\n Returns\n -------\n A Js Object of the form {result: the_result}\n """\n import __main__\n\n with display_target(id), defer_user_asyncio():\n result = eval_code(code, globals=__main__.__dict__)\n\n return to_js({"result": result}, depth=1, dict_converter=Object.fromEntries)\n\n\n__all__ = [\n "set_version_info",\n "uses_top_level_await",\n "run_pyscript",\n "install_pyscript_loop",\n "schedule_deferred_tasks",\n]\n'], ["pyscript/_mime.py", `import base64
import html
import io
import re
from js import console
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):
# If the image value is using bytes we should convert it to base64
# otherwise it will return raw bytes and the browser will not be able to
# render it.
if isinstance(value, bytes):
value = base64.b64encode(value).decode("utf-8")
# This is the pattern of base64 strings
base64_pattern = re.compile(
r"^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$"
)
# If value doesn't match the base64 pattern we should encode it to base64
if len(value) > 0 and not base64_pattern.match(value):
value = base64.b64encode(value.encode("utf-8")).decode("utf-8")
data = f"data:{mime};charset=utf-8;base64,{value}"
attrs = " ".join(['{k}="{v}"' for k, v in meta.items()])
return f'
'
def identity(value, meta):
return value
MIME_RENDERERS = {
"text/plain": html.escape,
"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"