mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
Merge remote-tracking branch 'origin/main' into antocuni/py-terminal
This commit is contained in:
12
pyscript.core/package-lock.json
generated
12
pyscript.core/package-lock.json
generated
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "@pyscript/core",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.7",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@pyscript/core",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.7",
|
||||
"license": "APACHE-2.0",
|
||||
"dependencies": {
|
||||
"@ungap/with-resolvers": "^0.1.0",
|
||||
"basic-devtools": "^0.1.6",
|
||||
"polyscript": "^0.4.8",
|
||||
"polyscript": "^0.4.11",
|
||||
"type-checked-collections": "^0.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -1782,9 +1782,9 @@
|
||||
"integrity": "sha512-yyVAOFKTAElc7KdLt2+UKGExNYwYb/Y/WE9i+1ezCQsJE8gbKSjewfpRqK2nQgZ4d4hhAAGgDCOcIZVilqE5UA=="
|
||||
},
|
||||
"node_modules/polyscript": {
|
||||
"version": "0.4.8",
|
||||
"resolved": "https://registry.npmjs.org/polyscript/-/polyscript-0.4.8.tgz",
|
||||
"integrity": "sha512-YlgjdMeEnv/i6WOqkh7gc52iSPY1l/psA+egu7z1GNrjwq6udw4WuQPz3rHRbaFhTUdYsVulLd8SBugjbVH6sQ==",
|
||||
"version": "0.4.11",
|
||||
"resolved": "https://registry.npmjs.org/polyscript/-/polyscript-0.4.11.tgz",
|
||||
"integrity": "sha512-wNvCUJp003OR/Q9C0eZJ84MHYeJiMtPTt1pqtsRQ0odRV/M1b3qVQ23oD5DAjq1weXQv1EdfpILwFOpw6VnirA==",
|
||||
"dependencies": {
|
||||
"@ungap/structured-clone": "^1.2.0",
|
||||
"@ungap/with-resolvers": "^0.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pyscript/core",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.7",
|
||||
"type": "module",
|
||||
"description": "PyScript",
|
||||
"module": "./index.js",
|
||||
@@ -33,7 +33,7 @@
|
||||
"dependencies": {
|
||||
"@ungap/with-resolvers": "^0.1.0",
|
||||
"basic-devtools": "^0.1.6",
|
||||
"polyscript": "^0.4.8",
|
||||
"polyscript": "^0.4.11",
|
||||
"type-checked-collections": "^0.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,62 +1,17 @@
|
||||
import TYPES from "./types.js";
|
||||
import hooks from "./hooks.js";
|
||||
|
||||
const DONE = "py:all-done";
|
||||
|
||||
const {
|
||||
onAfterRun,
|
||||
onAfterRunAsync,
|
||||
codeAfterRunWorker,
|
||||
codeAfterRunWorkerAsync,
|
||||
} = hooks;
|
||||
|
||||
const waitForIt = [];
|
||||
const codes = [];
|
||||
|
||||
const codeFor = (element) => {
|
||||
const isAsync = element.hasAttribute("async");
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
const type = `${DONE}:${waitForIt.push(promise)}`;
|
||||
|
||||
// resolve each promise once notified
|
||||
addEventListener(type, resolve, { once: true });
|
||||
|
||||
if (element.hasAttribute("worker")) {
|
||||
const code = `
|
||||
from pyscript import window as _w
|
||||
_w.dispatchEvent(_w.Event.new("${type}"))
|
||||
`;
|
||||
if (isAsync) codeAfterRunWorkerAsync.add(code);
|
||||
else codeAfterRunWorker.add(code);
|
||||
return code;
|
||||
for (const [TYPE] of TYPES) {
|
||||
const selectors = [`script[type="${TYPE}"]`, `${TYPE}-script`];
|
||||
for (const element of document.querySelectorAll(selectors.join(","))) {
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
waitForIt.push(promise);
|
||||
element.addEventListener(`${TYPE}:done`, resolve, { once: true });
|
||||
}
|
||||
|
||||
// dispatch only once the ready element is the same
|
||||
const code = (_, el) => {
|
||||
if (el === element) dispatchEvent(new Event(type));
|
||||
};
|
||||
|
||||
if (isAsync) onAfterRunAsync.add(code);
|
||||
else onAfterRun.add(code);
|
||||
return code;
|
||||
};
|
||||
|
||||
const selector = [];
|
||||
for (const [TYPE] of TYPES)
|
||||
selector.push(`script[type="${TYPE}"]`, `${TYPE}-script`);
|
||||
|
||||
// loop over all known scripts and elements
|
||||
for (const element of document.querySelectorAll(selector.join(",")))
|
||||
codes.push(codeFor(element));
|
||||
}
|
||||
|
||||
// wait for all the things then cleanup
|
||||
Promise.all(waitForIt).then(() => {
|
||||
// cleanup unnecessary hooks
|
||||
for (const code of codes) {
|
||||
onAfterRun.delete(code);
|
||||
onAfterRunAsync.delete(code);
|
||||
codeAfterRunWorker.delete(code);
|
||||
codeAfterRunWorkerAsync.delete(code);
|
||||
}
|
||||
dispatchEvent(new Event(DONE));
|
||||
dispatchEvent(new Event("py:all-done"));
|
||||
});
|
||||
|
||||
@@ -50,7 +50,7 @@ const syntaxError = (type, url, { message }) => {
|
||||
const configs = new Map();
|
||||
|
||||
for (const [TYPE] of TYPES) {
|
||||
/** @type {Promise<any> | undefined} A Promise wrapping any plugins which should be loaded. */
|
||||
/** @type {Promise<[...any]>} A Promise wrapping any plugins which should be loaded. */
|
||||
let plugins;
|
||||
|
||||
/** @type {any} The PyScript configuration parsed from the JSON or TOML object*. May be any of the return types of JSON.parse() or toml-j0.4's parse() ( {number | string | boolean | null | object | Array} ) */
|
||||
@@ -119,7 +119,7 @@ for (const [TYPE] of TYPES) {
|
||||
}
|
||||
|
||||
// assign plugins as Promise.all only if needed
|
||||
if (toBeAwaited.length) plugins = Promise.all(toBeAwaited);
|
||||
plugins = Promise.all(toBeAwaited);
|
||||
|
||||
configs.set(TYPE, { config: parsed, plugins, error });
|
||||
}
|
||||
|
||||
@@ -100,6 +100,11 @@ const exportedConfig = {};
|
||||
export { exportedConfig as config, hooks };
|
||||
|
||||
for (const [TYPE, interpreter] of TYPES) {
|
||||
const dispatchDone = (element, isAsync, result) => {
|
||||
if (isAsync) result.then(() => dispatch(element, TYPE, "done"));
|
||||
else dispatch(element, TYPE, "done");
|
||||
};
|
||||
|
||||
const { config, plugins, error } = configs.get(TYPE);
|
||||
|
||||
// create a unique identifier when/if needed
|
||||
@@ -133,155 +138,162 @@ for (const [TYPE, interpreter] of TYPES) {
|
||||
// define the module as both `<script type="py">` and `<py-script>`
|
||||
// but only if the config didn't throw an error
|
||||
if (!error) {
|
||||
// possible early errors sent by polyscript
|
||||
const errors = new Map();
|
||||
// ensure plugins are bootstrapped already before custom type definition
|
||||
// NOTE: we cannot top-level await in here as plugins import other utilities
|
||||
// from core.js itself so that custom definition should not be blocking.
|
||||
plugins.then(() => {
|
||||
// possible early errors sent by polyscript
|
||||
const errors = new Map();
|
||||
|
||||
define(TYPE, {
|
||||
config,
|
||||
interpreter,
|
||||
env: `${TYPE}-script`,
|
||||
version: config?.interpreter,
|
||||
onerror(error, element) {
|
||||
errors.set(element, error);
|
||||
},
|
||||
...workerHooks,
|
||||
onWorkerReady(_, xworker) {
|
||||
assign(xworker.sync, sync);
|
||||
for (const callback of hooks.onWorkerReady)
|
||||
callback(_, xworker);
|
||||
},
|
||||
onBeforeRun(wrap, element) {
|
||||
currentElement = element;
|
||||
bootstrapNodeAndPlugins(wrap, element, before, "onBeforeRun");
|
||||
},
|
||||
onBeforeRunAsync(wrap, element) {
|
||||
currentElement = element;
|
||||
bootstrapNodeAndPlugins(
|
||||
wrap,
|
||||
element,
|
||||
before,
|
||||
"onBeforeRunAsync",
|
||||
);
|
||||
},
|
||||
onAfterRun(wrap, element) {
|
||||
bootstrapNodeAndPlugins(wrap, element, after, "onAfterRun");
|
||||
},
|
||||
onAfterRunAsync(wrap, element) {
|
||||
bootstrapNodeAndPlugins(
|
||||
wrap,
|
||||
element,
|
||||
after,
|
||||
"onAfterRunAsync",
|
||||
);
|
||||
},
|
||||
async onInterpreterReady(wrap, element) {
|
||||
if (shouldRegister) {
|
||||
shouldRegister = false;
|
||||
registerModule(wrap);
|
||||
}
|
||||
|
||||
// ensure plugins are bootstrapped already
|
||||
if (plugins) await plugins;
|
||||
|
||||
// allows plugins to do whatever they want with the element
|
||||
// before regular stuff happens in here
|
||||
for (const callback of hooks.onInterpreterReady)
|
||||
callback(wrap, element);
|
||||
|
||||
// now that all possible plugins are configured,
|
||||
// bail out if polyscript encountered an error
|
||||
if (errors.has(element)) {
|
||||
let { message } = errors.get(element);
|
||||
errors.delete(element);
|
||||
const clone = message === INVALID_CONTENT;
|
||||
message = `(${ErrorCode.CONFLICTING_CODE}) ${message} for `;
|
||||
message += element.cloneNode(clone).outerHTML;
|
||||
wrap.io.stderr(message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isScript(element)) {
|
||||
const {
|
||||
attributes: { async: isAsync, target },
|
||||
} = element;
|
||||
const hasTarget = !!target?.value;
|
||||
const show = hasTarget
|
||||
? queryTarget(element, target.value)
|
||||
: document.createElement("script-py");
|
||||
|
||||
if (!hasTarget) {
|
||||
const { head, body } = document;
|
||||
if (head.contains(element)) body.append(show);
|
||||
else element.after(show);
|
||||
}
|
||||
if (!show.id) show.id = getID();
|
||||
|
||||
// allows the code to retrieve the target element via
|
||||
// document.currentScript.target if needed
|
||||
defineProperty(element, "target", { value: show });
|
||||
|
||||
// notify before the code runs
|
||||
dispatch(element, TYPE);
|
||||
wrap[`run${isAsync ? "Async" : ""}`](
|
||||
await fetchSource(element, wrap.io, true),
|
||||
define(TYPE, {
|
||||
config,
|
||||
interpreter,
|
||||
env: `${TYPE}-script`,
|
||||
version: config?.interpreter,
|
||||
onerror(error, element) {
|
||||
errors.set(element, error);
|
||||
},
|
||||
...workerHooks,
|
||||
onWorkerReady(_, xworker) {
|
||||
assign(xworker.sync, sync);
|
||||
for (const callback of hooks.onWorkerReady)
|
||||
callback(_, xworker);
|
||||
},
|
||||
onBeforeRun(wrap, element) {
|
||||
currentElement = element;
|
||||
bootstrapNodeAndPlugins(
|
||||
wrap,
|
||||
element,
|
||||
before,
|
||||
"onBeforeRun",
|
||||
);
|
||||
} else {
|
||||
// resolve PyScriptElement to allow connectedCallback
|
||||
element._wrap.resolve(wrap);
|
||||
}
|
||||
console.debug("[pyscript/main] PyScript Ready");
|
||||
},
|
||||
},
|
||||
onBeforeRunAsync(wrap, element) {
|
||||
currentElement = element;
|
||||
bootstrapNodeAndPlugins(
|
||||
wrap,
|
||||
element,
|
||||
before,
|
||||
"onBeforeRunAsync",
|
||||
);
|
||||
},
|
||||
onAfterRun(wrap, element) {
|
||||
bootstrapNodeAndPlugins(wrap, element, after, "onAfterRun");
|
||||
},
|
||||
onAfterRunAsync(wrap, element) {
|
||||
bootstrapNodeAndPlugins(
|
||||
wrap,
|
||||
element,
|
||||
after,
|
||||
"onAfterRunAsync",
|
||||
);
|
||||
},
|
||||
async onInterpreterReady(wrap, element) {
|
||||
if (shouldRegister) {
|
||||
shouldRegister = false;
|
||||
registerModule(wrap);
|
||||
}
|
||||
|
||||
// allows plugins to do whatever they want with the element
|
||||
// before regular stuff happens in here
|
||||
for (const callback of hooks.onInterpreterReady)
|
||||
callback(wrap, element);
|
||||
|
||||
// now that all possible plugins are configured,
|
||||
// bail out if polyscript encountered an error
|
||||
if (errors.has(element)) {
|
||||
let { message } = errors.get(element);
|
||||
errors.delete(element);
|
||||
const clone = message === INVALID_CONTENT;
|
||||
message = `(${ErrorCode.CONFLICTING_CODE}) ${message} for `;
|
||||
message += element.cloneNode(clone).outerHTML;
|
||||
wrap.io.stderr(message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isScript(element)) {
|
||||
const {
|
||||
attributes: { async: isAsync, target },
|
||||
} = element;
|
||||
const hasTarget = !!target?.value;
|
||||
const show = hasTarget
|
||||
? queryTarget(element, target.value)
|
||||
: document.createElement("script-py");
|
||||
|
||||
if (!hasTarget) {
|
||||
const { head, body } = document;
|
||||
if (head.contains(element)) body.append(show);
|
||||
else element.after(show);
|
||||
}
|
||||
if (!show.id) show.id = getID();
|
||||
|
||||
// allows the code to retrieve the target element via
|
||||
// document.currentScript.target if needed
|
||||
defineProperty(element, "target", { value: show });
|
||||
|
||||
// notify before the code runs
|
||||
dispatch(element, TYPE, "ready");
|
||||
dispatchDone(
|
||||
element,
|
||||
isAsync,
|
||||
wrap[`run${isAsync ? "Async" : ""}`](
|
||||
await fetchSource(element, wrap.io, true),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// resolve PyScriptElement to allow connectedCallback
|
||||
element._wrap.resolve(wrap);
|
||||
}
|
||||
console.debug("[pyscript/main] PyScript Ready");
|
||||
},
|
||||
});
|
||||
|
||||
customElements.define(
|
||||
`${TYPE}-script`,
|
||||
class extends HTMLElement {
|
||||
constructor() {
|
||||
assign(super(), {
|
||||
_wrap: Promise.withResolvers(),
|
||||
srcCode: "",
|
||||
executed: false,
|
||||
});
|
||||
}
|
||||
get id() {
|
||||
return super.id || (super.id = getID());
|
||||
}
|
||||
set id(value) {
|
||||
super.id = value;
|
||||
}
|
||||
async connectedCallback() {
|
||||
if (!this.executed) {
|
||||
this.executed = true;
|
||||
const isAsync = this.hasAttribute("async");
|
||||
const { io, run, runAsync } = await this._wrap
|
||||
.promise;
|
||||
this.srcCode = await fetchSource(
|
||||
this,
|
||||
io,
|
||||
!this.childElementCount,
|
||||
);
|
||||
this.replaceChildren();
|
||||
this.style.display = "block";
|
||||
dispatch(this, TYPE, "ready");
|
||||
dispatchDone(
|
||||
this,
|
||||
isAsync,
|
||||
(isAsync ? runAsync : run)(this.srcCode),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
class PyScriptElement extends HTMLElement {
|
||||
constructor() {
|
||||
assign(super(), {
|
||||
_wrap: Promise.withResolvers(),
|
||||
srcCode: "",
|
||||
executed: false,
|
||||
});
|
||||
}
|
||||
get _pyodide() {
|
||||
// TODO: deprecate this hidden attribute already
|
||||
// currently used by integration tests
|
||||
return this._wrap;
|
||||
}
|
||||
get id() {
|
||||
return super.id || (super.id = getID());
|
||||
}
|
||||
set id(value) {
|
||||
super.id = value;
|
||||
}
|
||||
async connectedCallback() {
|
||||
if (!this.executed) {
|
||||
this.executed = true;
|
||||
const { io, run, runAsync } = await this._wrap.promise;
|
||||
const runner = this.hasAttribute("async") ? runAsync : run;
|
||||
this.srcCode = await fetchSource(
|
||||
this,
|
||||
io,
|
||||
!this.childElementCount,
|
||||
);
|
||||
this.replaceChildren();
|
||||
// notify before the code runs
|
||||
dispatch(this, TYPE);
|
||||
runner(this.srcCode);
|
||||
this.style.display = "block";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// define py-script only if the config didn't throw an error
|
||||
if (!error) customElements.define(`${TYPE}-script`, PyScriptElement);
|
||||
|
||||
// export the used config without allowing leaks through it
|
||||
exportedConfig[TYPE] = structuredClone(config);
|
||||
}
|
||||
|
||||
// TBD: I think manual worker cases are interesting in pyodide only
|
||||
// so for the time being we should be fine with this export.
|
||||
|
||||
/**
|
||||
* A `Worker` facade able to bootstrap on the worker thread only a PyScript module.
|
||||
* @param {string} file the python file to run ina worker.
|
||||
@@ -295,8 +307,8 @@ export function PyWorker(file, options) {
|
||||
// and as `pyodide` is the only default interpreter that can deal with
|
||||
// all the features we need to deliver pyscript out there.
|
||||
const xworker = XWorker.call(new Hook(null, workerHooks), file, {
|
||||
...options,
|
||||
type: "pyodide",
|
||||
...options,
|
||||
});
|
||||
assign(xworker.sync, sync);
|
||||
return xworker;
|
||||
|
||||
@@ -29,8 +29,15 @@
|
||||
# pyscript.magic_js. This is the blessed way to access them from pyscript,
|
||||
# as it works transparently in both the main thread and worker cases.
|
||||
|
||||
from pyscript.magic_js import RUNNING_IN_WORKER, window, document, sync
|
||||
from pyscript.display import HTML, display
|
||||
from pyscript.magic_js import (
|
||||
RUNNING_IN_WORKER,
|
||||
PyWorker,
|
||||
current_target,
|
||||
document,
|
||||
sync,
|
||||
window,
|
||||
)
|
||||
|
||||
try:
|
||||
from pyscript.event_handling import when
|
||||
@@ -38,6 +45,5 @@ except:
|
||||
from pyscript.util import NotSupported
|
||||
|
||||
when = NotSupported(
|
||||
"pyscript.when",
|
||||
"pyscript.when currently not available with this interpreter"
|
||||
"pyscript.when", "pyscript.when currently not available with this interpreter"
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import html
|
||||
import io
|
||||
import re
|
||||
|
||||
from pyscript.magic_js import document, window, current_target
|
||||
from pyscript.magic_js import current_target, document, window
|
||||
|
||||
_MIME_METHODS = {
|
||||
"__repr__": "text/plain",
|
||||
@@ -148,14 +148,30 @@ def _write(element, value, append=False):
|
||||
def display(*values, target=None, append=True):
|
||||
if target is None:
|
||||
target = current_target()
|
||||
elif not isinstance(target, str):
|
||||
raise TypeError(f"target must be str or None, not {target.__class__.__name__}")
|
||||
elif target == "":
|
||||
raise ValueError("Cannot have an empty target")
|
||||
elif target.startswith("#"):
|
||||
# note: here target is str and not None!
|
||||
# align with @when behavior
|
||||
target = target[1:]
|
||||
|
||||
element = document.getElementById(target)
|
||||
|
||||
# If target cannot be found on the page, a ValueError is raised
|
||||
if element is None:
|
||||
raise ValueError(
|
||||
f"Invalid selector with id={target}. Cannot be found in the page."
|
||||
)
|
||||
|
||||
# if element is a <script type="py">, it has a 'target' attribute which
|
||||
# points to the visual element holding the displayed values. In that case,
|
||||
# use that.
|
||||
if element.tagName == 'SCRIPT' and hasattr(element, 'target'):
|
||||
if element.tagName == "SCRIPT" and hasattr(element, "target"):
|
||||
element = element.target
|
||||
|
||||
for v in values:
|
||||
if not append:
|
||||
element.replaceChildren()
|
||||
_write(element, v, append=append)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from pyscript.util import NotSupported
|
||||
import js as globalThis
|
||||
from pyscript.util import NotSupported
|
||||
|
||||
RUNNING_IN_WORKER = not hasattr(globalThis, "document")
|
||||
|
||||
@@ -7,8 +7,9 @@ if RUNNING_IN_WORKER:
|
||||
import polyscript
|
||||
|
||||
PyWorker = NotSupported(
|
||||
'pyscript.PyWorker',
|
||||
'pyscript.PyWorker works only when running in the main thread')
|
||||
"pyscript.PyWorker",
|
||||
"pyscript.PyWorker works only when running in the main thread",
|
||||
)
|
||||
window = polyscript.xworker.window
|
||||
document = window.document
|
||||
sync = polyscript.xworker.sync
|
||||
@@ -21,11 +22,12 @@ if RUNNING_IN_WORKER:
|
||||
else:
|
||||
import _pyscript
|
||||
from _pyscript import PyWorker
|
||||
|
||||
window = globalThis
|
||||
document = globalThis.document
|
||||
sync = NotSupported(
|
||||
'pyscript.sync',
|
||||
'pyscript.sync works only when running in a worker')
|
||||
"pyscript.sync", "pyscript.sync works only when running in a worker"
|
||||
)
|
||||
|
||||
# in MAIN the current element target exist, just use it
|
||||
def current_target():
|
||||
|
||||
30
pyscript.core/test/display.html
Normal file
30
pyscript.core/test/display.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>PyScript Next</title>
|
||||
<script>
|
||||
addEventListener("py:all-done", ({ type }) => console.log(type));
|
||||
</script>
|
||||
<link rel="stylesheet" href="../dist/core.css">
|
||||
<script type="module" src="../dist/core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py" worker async>
|
||||
from pyscript import display
|
||||
display('hello 1')
|
||||
|
||||
import js
|
||||
import time
|
||||
js.console.log('sleeping...')
|
||||
time.sleep(2)
|
||||
js.console.log('...done')
|
||||
</script>
|
||||
<p>hello 2</p>
|
||||
<script type="py" worker async>
|
||||
from pyscript import display
|
||||
display('hello 3')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,8 +1,9 @@
|
||||
import random
|
||||
from datetime import datetime as dt
|
||||
|
||||
from pyscript import display
|
||||
from pyweb import pydom
|
||||
from pyweb.base import when
|
||||
from datetime import datetime as dt
|
||||
|
||||
|
||||
@when("click", "#just-a-button")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
from pyscript import document, when
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from pyscript import document, when
|
||||
from pyweb import pydom
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
###### magic monkey patching ######
|
||||
import sys
|
||||
import builtins
|
||||
from pyscript import sync
|
||||
import sys
|
||||
|
||||
from pyodide.code import eval_code
|
||||
from pyscript import sync
|
||||
|
||||
sys.stdout = sync
|
||||
builtins.input = sync.readline
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from pyscript import display, sync
|
||||
|
||||
import a
|
||||
from pyscript import display, sync
|
||||
|
||||
display("Hello World", target="test", append=True)
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ def pytest_configure(config):
|
||||
--no-fake-server, but because of how pytest works, they are available only
|
||||
if this is the "root conftest" for the test session.
|
||||
|
||||
This means that if you are in the pyscriptjs directory:
|
||||
This means that if you are in the pyscript.core directory:
|
||||
|
||||
$ py.test # does NOT work
|
||||
$ py.test tests/integration/ # works
|
||||
@@ -70,10 +70,9 @@ def pytest_configure(config):
|
||||
"""
|
||||
if not hasattr(config.option, "dev"):
|
||||
msg = """
|
||||
Running a bare "pytest" command from the pyscriptjs directory
|
||||
Running a bare "pytest" command from the pyscript.core directory
|
||||
is not supported. Please use one of the following commands:
|
||||
- pytest tests/integration
|
||||
- pytest tests/py-unit
|
||||
- pytest tests/*
|
||||
- cd tests/integration; pytest
|
||||
"""
|
||||
|
||||
@@ -2,10 +2,20 @@ import re
|
||||
|
||||
import pytest
|
||||
|
||||
from .support import PyScriptTest, skip_worker, only_main
|
||||
from .support import PyScriptTest, only_main, skip_worker
|
||||
|
||||
|
||||
class TestBasic(PyScriptTest):
|
||||
def test_pyscript_exports(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<script type="py">
|
||||
from pyscript import RUNNING_IN_WORKER, PyWorker, window, document, sync, current_target
|
||||
</script>
|
||||
"""
|
||||
)
|
||||
assert self.console.error.lines == []
|
||||
|
||||
def test_script_py_hello(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
@@ -96,10 +106,6 @@ class TestBasic(PyScriptTest):
|
||||
assert "hello pyscript" in self.console.log.lines
|
||||
self.check_py_errors("Exception: this is an error")
|
||||
#
|
||||
# check that we sent the traceback to the console
|
||||
tb_lines = self.console.error.lines[-1].splitlines()
|
||||
assert tb_lines[0] == "PythonError: Traceback (most recent call last):"
|
||||
#
|
||||
# check that we show the traceback in the page. Note that here we
|
||||
# display the "raw" python traceback, without the "[pyexec] Python
|
||||
# exception:" line (which is useful in the console, but not for the
|
||||
@@ -128,10 +134,6 @@ class TestBasic(PyScriptTest):
|
||||
|
||||
self.check_py_errors("Exception: this is an error inside handler")
|
||||
|
||||
## error in console
|
||||
tb_lines = self.console.error.lines[-1].splitlines()
|
||||
assert tb_lines[0] == "PythonError: Traceback (most recent call last):"
|
||||
|
||||
## error in DOM
|
||||
tb_lines = self.page.locator(".py-error").inner_text().splitlines()
|
||||
assert tb_lines[0] == "Traceback (most recent call last):"
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
################################################################################
|
||||
|
||||
import base64
|
||||
import html
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import html
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from PIL import Image
|
||||
|
||||
from .support import (
|
||||
PageErrors,
|
||||
PyScriptTest,
|
||||
filter_inner_text,
|
||||
filter_page_content,
|
||||
wait_for_render,
|
||||
skip_worker,
|
||||
only_main,
|
||||
skip_worker,
|
||||
wait_for_render,
|
||||
)
|
||||
|
||||
DISPLAY_OUTPUT_ID_PATTERN = r'script-py[id^="py-"]'
|
||||
@@ -72,6 +73,67 @@ class TestDisplay(PyScriptTest):
|
||||
mydiv = self.page.locator("#mydiv")
|
||||
assert mydiv.inner_text() == "hello world"
|
||||
|
||||
def test_target_parameter_with_sharp(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<script type="py">
|
||||
from pyscript import display
|
||||
display('hello world', target="#mydiv")
|
||||
</script>
|
||||
<div id="mydiv"></div>
|
||||
"""
|
||||
)
|
||||
mydiv = self.page.locator("#mydiv")
|
||||
assert mydiv.inner_text() == "hello world"
|
||||
|
||||
def test_non_existing_id_target_raises_value_error(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<script type="py">
|
||||
from pyscript import display
|
||||
display('hello world', target="non-existing")
|
||||
</script>
|
||||
"""
|
||||
)
|
||||
error_msg = (
|
||||
f"Invalid selector with id=non-existing. Cannot be found in the page."
|
||||
)
|
||||
self.check_py_errors(f"ValueError: {error_msg}")
|
||||
|
||||
def test_empty_string_target_raises_value_error(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<script type="py">
|
||||
from pyscript import display
|
||||
display('hello world', target="")
|
||||
</script>
|
||||
"""
|
||||
)
|
||||
self.check_py_errors(f"ValueError: Cannot have an empty target")
|
||||
|
||||
def test_non_string_target_values_raise_typerror(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<script type="py">
|
||||
from pyscript import display
|
||||
display("hello False", target=False)
|
||||
</script>
|
||||
"""
|
||||
)
|
||||
error_msg = f"target must be str or None, not bool"
|
||||
self.check_py_errors(f"TypeError: {error_msg}")
|
||||
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<script type="py">
|
||||
from pyscript import display
|
||||
display("hello False", target=123)
|
||||
</script>
|
||||
"""
|
||||
)
|
||||
error_msg = f"target must be str or None, not int"
|
||||
self.check_py_errors(f"TypeError: {error_msg}")
|
||||
|
||||
@skip_worker("NEXT: display(target=...) does not work")
|
||||
def test_tag_target_attribute(self):
|
||||
self.pyscript_run(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import pytest
|
||||
|
||||
from .support import PyScriptTest, filter_inner_text, only_main
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ from .support import ROOT, PyScriptTest, wait_for_render, with_execution_thread
|
||||
reason="SKIPPING EXAMPLES: these should be moved elsewhere and updated"
|
||||
)
|
||||
@with_execution_thread(None)
|
||||
@pytest.mark.usefixtures("chdir")
|
||||
class TestExamples(PyScriptTest):
|
||||
"""
|
||||
Each example requires the same three tests:
|
||||
@@ -26,11 +25,6 @@ class TestExamples(PyScriptTest):
|
||||
- Testing that the page contains appropriate content after rendering
|
||||
"""
|
||||
|
||||
@pytest.fixture()
|
||||
def chdir(self):
|
||||
# make sure that the http server serves from the right directory
|
||||
ROOT.join("pyscriptjs").chdir()
|
||||
|
||||
def test_hello_world(self):
|
||||
self.goto("examples/hello_world.html")
|
||||
self.wait_for_pyscript()
|
||||
|
||||
Reference in New Issue
Block a user