@pyscript/core 0.1.17 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core.js +3 -3
- package/dist/core.js.map +1 -1
- package/docs/README.md +32 -6
- package/package.json +3 -3
- package/src/core.js +6 -1
- package/src/stdlib/pyscript.js +3 -2
- package/types/stdlib/pyscript.d.ts +1 -0
package/docs/README.md
CHANGED
@@ -14,7 +14,7 @@ Accordingly, this is the bare minimum required output to bootstrap *PyScript Nex
|
|
14
14
|
|
15
15
|
```html
|
16
16
|
<!-- Option 1: based on esm.sh which in turns is jsdlvr -->
|
17
|
-
<script type="module" src="https://
|
17
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/@pyscript/core"></script>
|
18
18
|
|
19
19
|
<!-- Option 2: based on unpkg.com -->
|
20
20
|
<script type="module" src="https://unpkg.com/@pyscript/core"></script>
|
@@ -28,22 +28,24 @@ If no `<script type="py">` or `<py-script>` tag is present, it is still possible
|
|
28
28
|
|
29
29
|
```html
|
30
30
|
<script type="module">
|
31
|
-
import { PyWorker } from "https://
|
31
|
+
import { PyWorker } from "https://cdn.jsdelivr.net/npm/@pyscript/core";
|
32
32
|
|
33
33
|
const worker = PyWorker("./code.py", { config: "./config.toml" /* optional */ });
|
34
34
|
</script>
|
35
35
|
```
|
36
36
|
|
37
|
+
Alternatively, it is possible to specify a `worker` attribute to either run embedded code or the provided `src` file.
|
38
|
+
|
37
39
|
#### CSS
|
38
40
|
|
39
41
|
If you are planning to use either `<py-config>` or `<py-script>` tags on the page, where latter case is usually better off with `<script type="py">` instead, you can also use CDNs to land our custom CSS:
|
40
42
|
|
41
43
|
```html
|
42
44
|
<!-- Option 1: based on esm.sh which in turns is jsdlvr -->
|
43
|
-
<link rel="stylesheet" href="https://
|
45
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@pyscript/core/dist/core.css">
|
44
46
|
|
45
47
|
<!-- Option 2: based on unpkg.com -->
|
46
|
-
<link rel="stylesheet" href="https://unpkg.com/@pyscript/core/css">
|
48
|
+
<link rel="stylesheet" href="https://unpkg.com/@pyscript/core/dist/core.css">
|
47
49
|
|
48
50
|
<!-- Option X: any CDN that uses npmjs registry should work -->
|
49
51
|
```
|
@@ -60,6 +62,30 @@ Once again, if you use `<script type="py">` instead, you won't need CSS unless y
|
|
60
62
|
</script>
|
61
63
|
```
|
62
64
|
|
65
|
+
#### HTML Example
|
66
|
+
|
67
|
+
This is a complete reference to bootstrap *PyScript* in a HTML document.
|
68
|
+
|
69
|
+
```html
|
70
|
+
<!doctype html>
|
71
|
+
<html lang="en">
|
72
|
+
<head>
|
73
|
+
<title>PyScript Next</title>
|
74
|
+
<meta charset="UTF-8">
|
75
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
76
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@pyscript/core/dist/core.css">
|
77
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/@pyscript/core"></script>
|
78
|
+
</head>
|
79
|
+
<body>
|
80
|
+
<script type="py">
|
81
|
+
from pyscript import document
|
82
|
+
|
83
|
+
document.body.textContent = "PyScript Next"
|
84
|
+
</script>
|
85
|
+
</body>
|
86
|
+
</html>
|
87
|
+
```
|
88
|
+
|
63
89
|
|
64
90
|
## Tag attributes API
|
65
91
|
|
@@ -81,7 +107,7 @@ The module itself is currently exporting the following utilities:
|
|
81
107
|
* **hooks**, which allows plugins to define *ASAP* callbacks or strings that should be executed either in the main thread or the worker before, or after, the code has been executed.
|
82
108
|
|
83
109
|
```js
|
84
|
-
import { hooks } from "https://
|
110
|
+
import { hooks } from "https://cdn.jsdelivr.net/npm/@pyscript/core";
|
85
111
|
|
86
112
|
// example
|
87
113
|
hooks.onInterpreterReady.add((utils, element) => {
|
@@ -146,7 +172,7 @@ The commonly shared utilities are:
|
|
146
172
|
|
147
173
|
```html
|
148
174
|
<script type="module">
|
149
|
-
import { PyWorker } from "https://
|
175
|
+
import { PyWorker } from "https://cdn.jsdelivr.net/npm/@pyscript/core";
|
150
176
|
|
151
177
|
const worker = PyWorker("./worker.py");
|
152
178
|
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@pyscript/core",
|
3
|
-
"version": "0.1.
|
3
|
+
"version": "0.1.18",
|
4
4
|
"type": "module",
|
5
5
|
"description": "PyScript",
|
6
6
|
"module": "./index.js",
|
@@ -32,12 +32,12 @@
|
|
32
32
|
"dependencies": {
|
33
33
|
"@ungap/with-resolvers": "^0.1.0",
|
34
34
|
"basic-devtools": "^0.1.6",
|
35
|
-
"polyscript": "^0.3.
|
35
|
+
"polyscript": "^0.3.6"
|
36
36
|
},
|
37
37
|
"devDependencies": {
|
38
38
|
"@rollup/plugin-node-resolve": "^15.2.1",
|
39
39
|
"@rollup/plugin-terser": "^0.4.3",
|
40
|
-
"rollup": "^3.29.
|
40
|
+
"rollup": "^3.29.1",
|
41
41
|
"rollup-plugin-postcss": "^4.0.2",
|
42
42
|
"rollup-plugin-string": "^3.0.0",
|
43
43
|
"static-handler": "^0.4.2",
|
package/src/core.js
CHANGED
@@ -11,7 +11,7 @@ import plugins from "./plugins.js";
|
|
11
11
|
// TODO: this is not strictly polyscript related but handy ... not sure
|
12
12
|
// we should factor this utility out a part but this works anyway.
|
13
13
|
import { queryTarget } from "../node_modules/polyscript/esm/script-handler.js";
|
14
|
-
import { dedent } from "../node_modules/polyscript/esm/utils.js";
|
14
|
+
import { dedent, dispatch } from "../node_modules/polyscript/esm/utils.js";
|
15
15
|
import { Hook } from "../node_modules/polyscript/esm/worker/hooks.js";
|
16
16
|
|
17
17
|
import { robustFetch as fetch } from "./fetch.js";
|
@@ -193,6 +193,7 @@ define("py", {
|
|
193
193
|
// before regular stuff happens in here
|
194
194
|
for (const callback of hooks.onInterpreterReady)
|
195
195
|
callback(pyodide, element);
|
196
|
+
|
196
197
|
if (isScript(element)) {
|
197
198
|
const {
|
198
199
|
attributes: { async: isAsync, target },
|
@@ -213,6 +214,8 @@ define("py", {
|
|
213
214
|
// document.currentScript.target if needed
|
214
215
|
defineProperty(element, "target", { value: show });
|
215
216
|
|
217
|
+
// notify before the code runs
|
218
|
+
dispatch(element, "py");
|
216
219
|
pyodide[`run${isAsync ? "Async" : ""}`](
|
217
220
|
await fetchSource(element, pyodide.io, true),
|
218
221
|
);
|
@@ -245,6 +248,8 @@ class PyScriptElement extends HTMLElement {
|
|
245
248
|
const runner = this.hasAttribute("async") ? runAsync : run;
|
246
249
|
this.srcCode = await fetchSource(this, io, !this.childElementCount);
|
247
250
|
this.replaceChildren();
|
251
|
+
// notify before the code runs
|
252
|
+
dispatch(this, "py");
|
248
253
|
runner(this.srcCode);
|
249
254
|
this.style.display = "block";
|
250
255
|
}
|
package/src/stdlib/pyscript.js
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
export default {
|
3
3
|
"_pyscript": {
|
4
4
|
"__init__.py": "import js as window\n\nIS_WORKER = not hasattr(window, \"document\")\n\nif IS_WORKER:\n from polyscript import xworker as _xworker\n\n window = _xworker.window\n document = window.document\n sync = _xworker.sync\nelse:\n document = window.document\n",
|
5
|
-
"display.py": "import base64\nimport html\nimport io\nimport re\n\nfrom . import document, window\n\n_MIME_METHODS = {\n \"__repr__\": \"text/plain\",\n \"_repr_html_\": \"text/html\",\n \"_repr_markdown_\": \"text/markdown\",\n \"_repr_svg_\": \"image/svg+xml\",\n \"_repr_png_\": \"image/png\",\n \"_repr_pdf_\": \"application/pdf\",\n \"_repr_jpeg_\": \"image/jpeg\",\n \"_repr_latex\": \"text/latex\",\n \"_repr_json_\": \"application/json\",\n \"_repr_javascript_\": \"application/javascript\",\n \"savefig\": \"image/png\",\n}\n\n\ndef _render_image(mime, value, meta):\n # If the image value is using bytes we should convert it to base64\n # otherwise it will return raw bytes and the browser will not be able to\n # render it.\n if isinstance(value, bytes):\n value = base64.b64encode(value).decode(\"utf-8\")\n\n # This is the pattern of base64 strings\n base64_pattern = re.compile(\n r\"^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$\"\n )\n # If value doesn't match the base64 pattern we should encode it to base64\n if len(value) > 0 and not base64_pattern.match(value):\n value = base64.b64encode(value.encode(\"utf-8\")).decode(\"utf-8\")\n\n data = f\"data:{mime};charset=utf-8;base64,{value}\"\n attrs = \" \".join(['{k}=\"{v}\"' for k, v in meta.items()])\n return f'<img src=\"{data}\" {attrs}></img>'\n\n\ndef _identity(value, meta):\n return value\n\n\n_MIME_RENDERERS = {\n \"text/plain\": html.escape,\n \"text/html\": _identity,\n \"image/png\": lambda value, meta: _render_image(\"image/png\", value, meta),\n \"image/jpeg\": lambda value, meta: _render_image(\"image/jpeg\", value, meta),\n \"image/svg+xml\": _identity,\n \"application/json\": _identity,\n \"application/javascript\": lambda value, meta: f\"<script>{value}<\\\\/script>\",\n}\n\n\ndef _eval_formatter(obj, print_method):\n \"\"\"\n Evaluates a formatter method.\n \"\"\"\n if print_method == \"__repr__\":\n return repr(obj)\n elif hasattr(obj, print_method):\n if print_method == \"savefig\":\n buf = io.BytesIO()\n obj.savefig(buf, format=\"png\")\n buf.seek(0)\n return base64.b64encode(buf.read()).decode(\"utf-8\")\n return getattr(obj, print_method)()\n elif print_method == \"_repr_mimebundle_\":\n return {}, {}\n return None\n\n\ndef _format_mime(obj):\n \"\"\"\n Formats object using _repr_x_ methods.\n \"\"\"\n if isinstance(obj, str):\n return html.escape(obj), \"text/plain\"\n\n mimebundle = _eval_formatter(obj, \"_repr_mimebundle_\")\n if isinstance(mimebundle, tuple):\n format_dict, _ = mimebundle\n else:\n format_dict = mimebundle\n\n output, not_available = None, []\n for method, mime_type in reversed(_MIME_METHODS.items()):\n if mime_type in format_dict:\n output = format_dict[mime_type]\n else:\n output = _eval_formatter(obj, method)\n\n if output is None:\n continue\n elif mime_type not in _MIME_RENDERERS:\n not_available.append(mime_type)\n continue\n break\n if output is None:\n if not_available:\n window.console.warn(\n f\"Rendered object requested unavailable MIME renderers: {not_available}\"\n )\n output = repr(output)\n mime_type = \"text/plain\"\n elif isinstance(output, tuple):\n output, meta = output\n else:\n meta = {}\n return _MIME_RENDERERS[mime_type](output, meta), mime_type\n\n\ndef _write(element, value, append=False):\n html, mime_type = _format_mime(value)\n if html == \"\\\\n\":\n return\n\n if append:\n out_element = document.createElement(\"div\")\n element.append(out_element)\n else:\n out_element = element.lastElementChild\n if out_element is None:\n out_element = element\n\n if mime_type in (\"application/javascript\", \"text/html\"):\n script_element = document.createRange().createContextualFragment(html)\n out_element.append(script_element)\n else:\n out_element.innerHTML = html\n\n\ndef display(*values, target=None, append=True):\n element = document.getElementById(target)\n for v in values:\n _write(element, v, append=append)\n"
|
5
|
+
"display.py": "import base64\nimport html\nimport io\nimport re\n\nfrom . import document, window\n\n_MIME_METHODS = {\n \"__repr__\": \"text/plain\",\n \"_repr_html_\": \"text/html\",\n \"_repr_markdown_\": \"text/markdown\",\n \"_repr_svg_\": \"image/svg+xml\",\n \"_repr_png_\": \"image/png\",\n \"_repr_pdf_\": \"application/pdf\",\n \"_repr_jpeg_\": \"image/jpeg\",\n \"_repr_latex\": \"text/latex\",\n \"_repr_json_\": \"application/json\",\n \"_repr_javascript_\": \"application/javascript\",\n \"savefig\": \"image/png\",\n}\n\n\ndef _render_image(mime, value, meta):\n # If the image value is using bytes we should convert it to base64\n # otherwise it will return raw bytes and the browser will not be able to\n # render it.\n if isinstance(value, bytes):\n value = base64.b64encode(value).decode(\"utf-8\")\n\n # This is the pattern of base64 strings\n base64_pattern = re.compile(\n r\"^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$\"\n )\n # If value doesn't match the base64 pattern we should encode it to base64\n if len(value) > 0 and not base64_pattern.match(value):\n value = base64.b64encode(value.encode(\"utf-8\")).decode(\"utf-8\")\n\n data = f\"data:{mime};charset=utf-8;base64,{value}\"\n attrs = \" \".join(['{k}=\"{v}\"' for k, v in meta.items()])\n return f'<img src=\"{data}\" {attrs}></img>'\n\n\ndef _identity(value, meta):\n return value\n\n\n_MIME_RENDERERS = {\n \"text/plain\": html.escape,\n \"text/html\": _identity,\n \"image/png\": lambda value, meta: _render_image(\"image/png\", value, meta),\n \"image/jpeg\": lambda value, meta: _render_image(\"image/jpeg\", value, meta),\n \"image/svg+xml\": _identity,\n \"application/json\": _identity,\n \"application/javascript\": lambda value, meta: f\"<script>{value}<\\\\/script>\",\n}\n\n\nclass HTML:\n \"\"\"\n Wrap a string so that display() can render it as plain HTML\n \"\"\"\n\n def __init__(self, html):\n self._html = html\n\n def _repr_html_(self):\n return self._html\n\n\ndef _eval_formatter(obj, print_method):\n \"\"\"\n Evaluates a formatter method.\n \"\"\"\n if print_method == \"__repr__\":\n return repr(obj)\n elif hasattr(obj, print_method):\n if print_method == \"savefig\":\n buf = io.BytesIO()\n obj.savefig(buf, format=\"png\")\n buf.seek(0)\n return base64.b64encode(buf.read()).decode(\"utf-8\")\n return getattr(obj, print_method)()\n elif print_method == \"_repr_mimebundle_\":\n return {}, {}\n return None\n\n\ndef _format_mime(obj):\n \"\"\"\n Formats object using _repr_x_ methods.\n \"\"\"\n if isinstance(obj, str):\n return html.escape(obj), \"text/plain\"\n\n mimebundle = _eval_formatter(obj, \"_repr_mimebundle_\")\n if isinstance(mimebundle, tuple):\n format_dict, _ = mimebundle\n else:\n format_dict = mimebundle\n\n output, not_available = None, []\n for method, mime_type in reversed(_MIME_METHODS.items()):\n if mime_type in format_dict:\n output = format_dict[mime_type]\n else:\n output = _eval_formatter(obj, method)\n\n if output is None:\n continue\n elif mime_type not in _MIME_RENDERERS:\n not_available.append(mime_type)\n continue\n break\n if output is None:\n if not_available:\n window.console.warn(\n f\"Rendered object requested unavailable MIME renderers: {not_available}\"\n )\n output = repr(output)\n mime_type = \"text/plain\"\n elif isinstance(output, tuple):\n output, meta = output\n else:\n meta = {}\n return _MIME_RENDERERS[mime_type](output, meta), mime_type\n\n\ndef _write(element, value, append=False):\n html, mime_type = _format_mime(value)\n if html == \"\\\\n\":\n return\n\n if append:\n out_element = document.createElement(\"div\")\n element.append(out_element)\n else:\n out_element = element.lastElementChild\n if out_element is None:\n out_element = element\n\n if mime_type in (\"application/javascript\", \"text/html\"):\n script_element = document.createRange().createContextualFragment(html)\n out_element.append(script_element)\n else:\n out_element.innerHTML = html\n\n\ndef display(*values, target=None, append=True):\n element = document.getElementById(target)\n for v in values:\n _write(element, v, append=append)\n",
|
6
|
+
"event_handling.py": "import inspect\n\nfrom pyodide.ffi.wrappers import add_event_listener\nfrom pyscript import document\n\n\ndef when(event_type=None, selector=None):\n \"\"\"\n Decorates a function and passes py-* events to the decorated function\n The events might or not be an argument of the decorated function\n \"\"\"\n\n def decorator(func):\n elements = document.querySelectorAll(selector)\n sig = inspect.signature(func)\n # Function doesn't receive events\n if not sig.parameters:\n\n def wrapper(*args, **kwargs):\n func()\n\n for el in elements:\n add_event_listener(el, event_type, wrapper)\n else:\n for el in elements:\n add_event_listener(el, event_type, func)\n return func\n\n return decorator\n"
|
6
7
|
},
|
7
|
-
"pyscript.py": "# export only what we want to expose as `pyscript` module\n# but not what is WORKER/MAIN dependent\nfrom _pyscript import window, document, IS_WORKER\nfrom _pyscript.display import display as _display\n\n# this part is needed to disambiguate between MAIN and WORKER\nif IS_WORKER:\n # in workers the display does not have a default ID\n # but there is a sync utility from xworker\n import polyscript as _polyscript\n from _pyscript import sync\n\n def current_target():\n return _polyscript.target\n\nelse:\n # in MAIN both PyWorker and current element target exist\n # so these are both exposed and the display will use,\n # if not specified otherwise, such current element target\n import _pyscript_js\n\n PyWorker = _pyscript_js.PyWorker\n\n def current_target():\n return _pyscript_js.target\n\n\n# the display provides a handy default target either in MAIN or WORKER\ndef display(*values, target=None, append=True):\n if target is None:\n target = current_target()\n\n return _display(*values, target=target, append=append)\n"
|
8
|
+
"pyscript.py": "# export only what we want to expose as `pyscript` module\n# but not what is WORKER/MAIN dependent\nfrom _pyscript import window, document, IS_WORKER\nfrom _pyscript.display import HTML, display as _display\nfrom _pyscript.event_handling import when\n\n# this part is needed to disambiguate between MAIN and WORKER\nif IS_WORKER:\n # in workers the display does not have a default ID\n # but there is a sync utility from xworker\n import polyscript as _polyscript\n from _pyscript import sync\n\n def current_target():\n return _polyscript.target\n\nelse:\n # in MAIN both PyWorker and current element target exist\n # so these are both exposed and the display will use,\n # if not specified otherwise, such current element target\n import _pyscript_js\n\n PyWorker = _pyscript_js.PyWorker\n\n def current_target():\n return _pyscript_js.target\n\n\n# the display provides a handy default target either in MAIN or WORKER\ndef display(*values, target=None, append=True):\n if target is None:\n target = current_target()\n\n return _display(*values, target=target, append=append)\n"
|
8
9
|
};
|