Moves Python code out of interpreter file (#207)

* make copy of .py files part of build process

* move code out ofinterpreter file and make it download and load code during initialization

* fix double ; in interpreter

* remove debugging print

* update dependencies

* fix project name and version

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* lint

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* change fmt-py

* lint

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* remove extra content

* define missing strict type

* create build folder if doesn't exist

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Fabio Pliger
2022-05-05 23:12:54 -05:00
committed by GitHub
parent 88a0dd38de
commit b7d748c96a
14 changed files with 405 additions and 538 deletions

View File

@@ -56,7 +56,7 @@ fmt: fmt-py fmt-ts
@echo "Format completed"
.PHONY: fmt-check
fmt-check: fmt-ts-check fmt-py
fmt-check: fmt-ts-check fmt-py-check
@echo "Format check completed"
.PHONY: fmt-ts
@@ -69,7 +69,8 @@ fmt-ts-check:
.PHONY: fmt-py
fmt-py:
$(conda_run) black -l 88 .
$(conda_run) black --skip-string-normalization .
$(conda_run) isort --profile black .
.PHONY: fmt-py-check
fmt-py-check:

View File

@@ -9,3 +9,4 @@ dependencies:
- black
- isort
- codespell
- pre-commit

View File

@@ -153,7 +153,6 @@ def micrograd_demo(*args, **kwargs):
plt.ylim(yy.min(), yy.max())
finish = datetime.datetime.now()
# print(f"It took {(finish-start).seconds} seconds to run this code.")
print_div(f"It took {(finish-start).seconds} seconds to run this code.")
plt

View File

@@ -145,9 +145,9 @@
}
},
"@codemirror/lang-python": {
"version": "0.19.4",
"resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-0.19.4.tgz",
"integrity": "sha512-eSH1JXbf0D30y+f3nWy/+bTLAIV8RmcQbbVD8DsBxkxOHMVKcILgxFRHCovba8YEMtmq45I1DoWcNt1CeKnrYQ==",
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-0.19.5.tgz",
"integrity": "sha512-MQf7t0k6+i9KCzlFCI8EY+jjwyXLy5AwjmXsMyMCMbOw/97j70jFZYrs7Mm7RJakNE2rypWhnLGlyBTSYMqR5g==",
"requires": {
"@codemirror/highlight": "^0.19.7",
"@codemirror/language": "^0.19.0",
@@ -404,9 +404,9 @@
}
},
"@rollup/plugin-typescript": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.3.1.tgz",
"integrity": "sha512-84rExe3ICUBXzqNX48WZV2Jp3OddjTMX97O2Py6D1KJaGSwWp0mDHXj+bCGNJqWHIEKDIT2U0sDjhP4czKi6cA==",
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.3.2.tgz",
"integrity": "sha512-MtgyR5LNHZr3GyN0tM7gNO9D0CS+Y+vflS4v/PHmrX17JCkHUYKvQ5jN5o3cz1YKllM3duXUqu3yOHwMPUxhDg==",
"dev": true,
"requires": {
"@rollup/pluginutils": "^3.1.0",
@@ -683,13 +683,13 @@
"dev": true
},
"autoprefixer": {
"version": "10.4.4",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.4.tgz",
"integrity": "sha512-Tm8JxsB286VweiZ5F0anmbyGiNI3v3wGv3mz9W+cxEDYB/6jbnj6GM9H9mK3wIL8ftgl+C07Lcwb8PG5PCCPzA==",
"version": "10.4.7",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.7.tgz",
"integrity": "sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA==",
"dev": true,
"requires": {
"browserslist": "^4.20.2",
"caniuse-lite": "^1.0.30001317",
"browserslist": "^4.20.3",
"caniuse-lite": "^1.0.30001335",
"fraction.js": "^4.2.0",
"normalize-range": "^0.1.2",
"picocolors": "^1.0.0",
@@ -728,15 +728,15 @@
}
},
"browserslist": {
"version": "4.20.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz",
"integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==",
"version": "4.20.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz",
"integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==",
"dev": true,
"requires": {
"caniuse-lite": "^1.0.30001317",
"electron-to-chromium": "^1.4.84",
"caniuse-lite": "^1.0.30001332",
"electron-to-chromium": "^1.4.118",
"escalade": "^3.1.1",
"node-releases": "^2.0.2",
"node-releases": "^2.0.3",
"picocolors": "^1.0.0"
}
},
@@ -777,9 +777,9 @@
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001323",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001323.tgz",
"integrity": "sha512-e4BF2RlCVELKx8+RmklSEIVub1TWrmdhvA5kEUueummz1XyySW0DVk+3x9HyhU9MuWTa2BhqLgEuEmUwASAdCA==",
"version": "1.0.30001335",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001335.tgz",
"integrity": "sha512-ddP1Tgm7z2iIxu6QTtbZUv6HJxSaV/PZeSrWFZtbY4JZ69tOeNhBCl3HyRQgeNZKE5AOn1kpV7fhljigy0Ty3w==",
"dev": true
},
"chalk": {
@@ -810,9 +810,9 @@
}
},
"codemirror": {
"version": "5.65.2",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.2.tgz",
"integrity": "sha512-SZM4Zq7XEC8Fhroqe3LxbEEX1zUPWH1wMr5zxiBuiUF64iYOUH/JI88v4tBag8MiBS8B8gRv8O1pPXGYXQ4ErA=="
"version": "5.65.3",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.3.tgz",
"integrity": "sha512-kCC0iwGZOVZXHEKW3NDTObvM7pTIyowjty4BUqeREROc/3I6bWbgZDA3fGDwlA+rbgRjvnRnfqs9SfXynel1AQ=="
},
"color": {
"version": "4.2.1",
@@ -1019,9 +1019,9 @@
}
},
"electron-to-chromium": {
"version": "1.4.103",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.103.tgz",
"integrity": "sha512-c/uKWR1Z/W30Wy/sx3dkZoj4BijbXX85QKWu9jJfjho3LBAXNEGAEW3oWiGb+dotA6C6BzCTxL2/aLes7jlUeg==",
"version": "1.4.134",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.134.tgz",
"integrity": "sha512-OdD7M2no4Mi8PopfvoOuNcwYDJ2mNFxaBfurA6okG3fLBaMcFah9S+si84FhX+FIWLKkdaiHfl4A+5ep/gOVrg==",
"dev": true
},
"error-ex": {
@@ -1832,9 +1832,9 @@
"dev": true
},
"nanoid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.2.tgz",
"integrity": "sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==",
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true
},
"natural-compare": {
@@ -1853,9 +1853,9 @@
}
},
"node-releases": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz",
"integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.4.tgz",
"integrity": "sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==",
"dev": true
},
"normalize-path": {
@@ -1969,12 +1969,12 @@
"dev": true
},
"postcss": {
"version": "8.4.12",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.12.tgz",
"integrity": "sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==",
"version": "8.4.13",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.13.tgz",
"integrity": "sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==",
"dev": true,
"requires": {
"nanoid": "^3.3.1",
"nanoid": "^3.3.3",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
@@ -2179,9 +2179,9 @@
}
},
"rollup": {
"version": "2.70.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.70.1.tgz",
"integrity": "sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA==",
"version": "2.71.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.71.1.tgz",
"integrity": "sha512-lMZk3XfUBGjrrZQpvPSoXcZSfKcJ2Bgn+Z0L1MoW2V8Wh7BVM+LOBJTPo16yul2MwL59cXedzW1ruq3rCjSRgw==",
"dev": true,
"requires": {
"fsevents": "~2.3.2"
@@ -2480,9 +2480,9 @@
"dev": true
},
"svelte": {
"version": "3.46.6",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.46.6.tgz",
"integrity": "sha512-o9nNft/OzCz/9kJpmWa1S52GAM+huCjPIsNWydYmgei74ZWlOA9/hN9+Z12INdklghu31seEXZMRHhS1+8DETw=="
"version": "3.48.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.48.0.tgz",
"integrity": "sha512-fN2YRm/bGumvjUpu6yI3BpvZnpIm9I6A7HR4oUNYd7ggYyIwSA/BX7DJ+UXXffLp6XNcUijyLvttbPVCYa/3xQ=="
},
"svelte-check": {
"version": "1.6.0",
@@ -2558,9 +2558,9 @@
"integrity": "sha512-0bnbMGbsE1LUnlioDcf27tl2O8kjuXlTXMXzIxC7LoIOWmqn0D+zd539HfLiQbdLuOHGTaynwN9V+4ehhEu1Jw=="
},
"svelte-preprocess": {
"version": "4.10.4",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.4.tgz",
"integrity": "sha512-fuwol0N4UoHsNQolLFbMqWivqcJ9N0vfWO9IuPAiX/5okfoGXURyJ6nECbuEIv0nU3M8Xe2I1ONNje2buk7l6A==",
"version": "4.10.6",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.6.tgz",
"integrity": "sha512-I2SV1w/AveMvgIQlUF/ZOO3PYVnhxfcpNyGt8pxpUVhPfyfL/CZBkkw/KPfuFix5FJ9TnnNYMhACK3DtSaYVVQ==",
"dev": true,
"requires": {
"@types/pug": "^2.0.4",
@@ -2751,9 +2751,9 @@
"integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g=="
},
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
"dev": true
},
"tsutils": {
@@ -2789,9 +2789,9 @@
"dev": true
},
"typescript": {
"version": "4.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz",
"integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==",
"version": "4.6.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz",
"integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==",
"dev": true
},
"universalify": {

View File

@@ -1,6 +1,6 @@
{
"name": "svelte-app",
"version": "1.0.0",
"name": "pyscript",
"version": "0.0.1",
"scripts": {
"build": "NODE_ENV=production rollup -c",
"dev": "rollup -c -w",
@@ -15,36 +15,36 @@
"devDependencies": {
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-node-resolve": "^11.0.0",
"@rollup/plugin-typescript": "^8.1.0",
"@rollup/plugin-typescript": "^8.3.2",
"@tsconfig/svelte": "^1.0.0",
"@typescript-eslint/eslint-plugin": "^5.20.0",
"@typescript-eslint/parser": "^5.20.0",
"autoprefixer": "^10.2.3",
"autoprefixer": "^10.4.7",
"eslint": "^8.14.0",
"eslint-plugin-svelte3": "^3.4.1",
"postcss": "^8.2.4",
"postcss": "^8.4.13",
"prettier": "^2.6.2",
"prettier-plugin-svelte": "^2.7.0",
"rollup": "^2.3.4",
"rollup": "^2.71.1",
"rollup-plugin-css-only": "^3.1.0",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-serve": "^1.1.0",
"rollup-plugin-svelte": "^7.0.0",
"rollup-plugin-terser": "^7.0.0",
"svelte": "^3.0.0",
"svelte": "^3.48.0",
"svelte-check": "^1.0.0",
"svelte-preprocess": "^4.6.3",
"svelte-preprocess": "^4.10.6",
"tailwindcss": "^2.0.2",
"tslib": "^2.0.0",
"typescript": "^4.1.3"
"tslib": "^2.4.0",
"typescript": "^4.6.4"
},
"dependencies": {
"@codemirror/basic-setup": "^0.19.1",
"@codemirror/lang-python": "^0.19.4",
"@codemirror/lang-python": "^0.19.5",
"@codemirror/state": "^0.19.9",
"@codemirror/theme-one-dark": "^0.19.1",
"@fortawesome/free-solid-svg-icons": "^6.0.0",
"codemirror": "^5.65.2",
"codemirror": "^5.65.3",
"js-yaml": "^4.1.0",
"sirv-cli": "^1.0.0",
"svelte-fa": "^2.4.0",

View File

@@ -6,7 +6,43 @@ import { terser } from "rollup-plugin-terser";
import sveltePreprocess from "svelte-preprocess";
import typescript from "@rollup/plugin-typescript";
import css from "rollup-plugin-css-only";
import serve from 'rollup-plugin-serve'
import serve from 'rollup-plugin-serve';
import path from "path";
import fs from "fs";
function copyPythonFiles(from, to, overwrite = false) {
return {
name: 'copy-files',
generateBundle() {
const log = msg => console.log('\x1b[36m%s\x1b[0m', msg)
log(`copy files: ${from}${to}`)
// create folder if it doesn't exist
if (!fs.existsSync(to)){
log(`Destination folder ${to} doesn't exist. Creating...`)
fs.mkdirSync(to);
}
fs.readdirSync(from).forEach(file => {
const fromFile = `${from}/${file}`
const toFile = `${to}/${file}`
if (fromFile.endsWith(`.py`)){
log(`----> ${fromFile}${toFile}`)
if (fs.existsSync(toFile) && !overwrite){
log(`skipping ${fromFile}${toFile}`)
return
}else{
fs.copyFileSync(
path.resolve(fromFile),
path.resolve(toFile)
);
}
}
})
}
}
}
const production = !process.env.ROLLUP_WATCH || (process.env.NODE_ENV === "production");
@@ -68,6 +104,8 @@ export default {
sourceMap: !production,
inlineSources: !production,
}),
// Copy all the python files from source to the build folder
copyPythonFiles("./src/", "./examples/build", true),
!production && serve(),
!production && livereload("public"),
production && terser(),

View File

@@ -1,14 +1,7 @@
<script lang="ts">
import Tailwind from './Tailwind.svelte';
import { loadInterpreter } from './interpreter';
import {
initializers,
loadedEnvironments,
mode,
postInitializers,
pyodideLoaded,
scriptsQueue,
} from './stores';
import { initializers, loadedEnvironments, mode, postInitializers, pyodideLoaded, scriptsQueue } from './stores';
let pyodideReadyPromise;

View File

@@ -6,7 +6,6 @@ let environments;
let currentMode;
let Element;
pyodideLoaded.subscribe(value => {
runtime = value;
});
@@ -14,7 +13,6 @@ loadedEnvironments.subscribe(value => {
environments = value;
});
mode.subscribe(value => {
currentMode = value;
});
@@ -147,7 +145,7 @@ export class BaseEvalElement extends HTMLElement {
const out = Element(this.errorElement.id);
addClasses(this.errorElement, ['bg-red-200', 'p-2']);
out.write.callKwargs(err, { append : true});
out.write.callKwargs(err, { append: true });
this.errorElement.hidden = false;
this.errorElement.style.display = 'block';
}
@@ -203,8 +201,8 @@ function createWidget(name: string, code: string, klass: string) {
// });
// }, 2000);
pyodideLoaded.subscribe(value => {
console.log("RUNTIME READY", value)
if ("runPythonAsync" in value){
console.log('RUNTIME READY', value);
if ('runPythonAsync' in value) {
runtime = value;
setTimeout(() => {
this.eval(this.code).then(() => {

View File

@@ -52,11 +52,10 @@ export class PyBox extends HTMLElement {
this.widths = [...this.widths, ...[`w-1/${mainDiv.childNodes.length}`]];
}
this.widths.forEach((width, index)=>{
this.widths.forEach((width, index) => {
const node: ChildNode = mainDiv.childNodes[index];
addClasses(node as HTMLElement, [width, 'mx-1'])
})
addClasses(node as HTMLElement, [width, 'mx-1']);
});
this.appendChild(mainDiv);
console.log('py-box connected');

View File

@@ -9,7 +9,7 @@ let runtime;
pyodideLoaded.subscribe(value => {
runtime = value;
console.log("RUNTIME READY")
console.log('RUNTIME READY');
});
export class PyEnv extends HTMLElement {
@@ -33,7 +33,7 @@ export class PyEnv extends HTMLElement {
this.innerHTML = '';
const env = [];
const paths = [];
const paths: string[] = [];
this.environment = jsyaml.load(this.code);
if (this.environment === undefined) return;
@@ -54,8 +54,8 @@ export class PyEnv extends HTMLElement {
}
async function loadPaths() {
const pyodide = await pyodideReadyPromise;
for (const singleFile of paths) {
console.log(`loading ${singleFile}`);
await loadFromFile(singleFile, runtime);
}
console.log('paths loaded');

View File

@@ -50,7 +50,7 @@ export class PyRepl extends BaseEvalElement {
// add an extra div where we can attach the codemirror editor
this.editorNode = document.createElement('div');
addClasses(this.editorNode, ["editor-box", "border", "border-gray-300", "group", "relative"]);
addClasses(this.editorNode, ['editor-box', 'border', 'border-gray-300', 'group', 'relative']);
this.shadow.appendChild(this.wrapper);
}
@@ -64,60 +64,61 @@ export class PyRepl extends BaseEvalElement {
languageConf.of(python()),
keymap.of([
...defaultKeymap,
{ key: "Ctrl-Enter", run: createCmdHandler(this) },
{ key: "Shift-Enter", run: createCmdHandler(this) }
])
{ key: 'Ctrl-Enter', run: createCmdHandler(this) },
{ key: 'Shift-Enter', run: createCmdHandler(this) },
]),
];
const customTheme = EditorView.theme({
'&.cm-focused .cm-editor': { outline: '0px' },
'.cm-scroller': { lineHeight: 2.5 },
'.cm-activeLine': { backgroundColor: '#fff' },
'.cm-content': { padding: 0, backgroundColor: '#f5f5f5' },
'&.cm-focused .cm-content': { border: '1px solid #1876d2' }
'&.cm-focused .cm-editor': { outline: '0px' },
'.cm-scroller': { lineHeight: 2.5 },
'.cm-activeLine': { backgroundColor: '#fff' },
'.cm-content': { padding: 0, backgroundColor: '#f5f5f5' },
'&.cm-focused .cm-content': { border: '1px solid #1876d2' },
});
if (!this.hasAttribute('theme')) {
this.theme = this.getAttribute('theme');
if (this.theme == 'dark'){
extensions.push(oneDarkTheme);
}
extensions.push(customTheme);
this.theme = this.getAttribute('theme');
if (this.theme == 'dark') {
extensions.push(oneDarkTheme);
}
extensions.push(customTheme);
}
const startState = EditorState.create({
doc: this.code.trim(),
extensions: extensions
doc: this.code.trim(),
extensions: extensions,
});
this.editor = new EditorView({
state: startState,
parent: this.editorNode
state: startState,
parent: this.editorNode,
});
const mainDiv = document.createElement('div');
addClasses(mainDiv, ["parentBox", "flex", "flex-col", "mt-2", "mx-8", "relative"])
addClasses(mainDiv, ['parentBox', 'flex', 'flex-col', 'mt-2', 'mx-8', 'relative']);
// add Editor to main PyScript div
mainDiv.appendChild(this.editorNode);
// Play Button
this.btnRun = document.createElement('button');
this.btnRun.innerHTML = '<svg id="" class="svelte-fa svelte-ps5qeg" style="height:20px;width:20px;vertical-align:-.125em;transform-origin:center;overflow:visible;color:green" viewBox="0 0 384 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(192 256)" transform-origin="96 0"><g transform="translate(0,0) scale(1,1)"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z" fill="currentColor" transform="translate(-192 -256)"></path></g></g></svg>';
addClasses(this.btnRun, ["absolute", "right-1", "bottom-3", "opacity-0", "group-hover:opacity-100"]);
this.btnRun.innerHTML =
'<svg id="" class="svelte-fa svelte-ps5qeg" style="height:20px;width:20px;vertical-align:-.125em;transform-origin:center;overflow:visible;color:green" viewBox="0 0 384 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(192 256)" transform-origin="96 0"><g transform="translate(0,0) scale(1,1)"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z" fill="currentColor" transform="translate(-192 -256)"></path></g></g></svg>';
addClasses(this.btnRun, ['absolute', 'right-1', 'bottom-3', 'opacity-0', 'group-hover:opacity-100']);
this.editorNode.appendChild(this.btnRun);
this.btnRun.onclick = wrap(this);
function wrap(el: any){
function evaluatePython() {
el.evaluate();
}
return evaluatePython;
function wrap(el: any) {
function evaluatePython() {
el.evaluate();
}
return evaluatePython;
}
if (!this.id) {
console.log(
'WARNING: <pyrepl> define with an id. <pyrepl> should always have an id. More than one <pyrepl> on a page won\'t work otherwise!',
"WARNING: <pyrepl> define with an id. <pyrepl> should always have an id. More than one <pyrepl> on a page won't work otherwise!",
);
}

View File

@@ -25,13 +25,11 @@ mode.subscribe(value => {
currentMode = value;
});
// TODO: use type declaractions
type PyodideInterface = {
registerJsModule(name: string, module: object): void;
};
export class PyScript extends BaseEvalElement {
constructor() {
super();

View File

@@ -3,416 +3,6 @@ import { getLastPath } from './utils';
let pyodideReadyPromise;
let pyodide;
const additional_definitions = `
from js import document, setInterval, console, setTimeout
import micropip
import time
import asyncio
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'data:{mime};charset=utf-8;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 print_method == '__repr__':
return repr(obj)
elif 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)()
elif print_method == '_repr_mimebundle_':
return {}, {}
return None
def format_mime(obj):
"""
Formats object using _repr_x_ methods.
"""
if isinstance(obj, str):
return obj, 'text/plain'
mimebundle = eval_formatter(obj, '_repr_mimebundle_')
if isinstance(mimebundle, tuple):
format_dict, md_dict = mimebundle
else:
format_dict = mimebundle
md_dict = {}
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
@staticmethod
def write(element_id, value, append=False, exec_id=0):
"""Writes value to the element with id "element_id"""
console.log(f"APPENDING: {append} ==> {element_id} --> {value}")
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);
element = document.getElementById(element_id)
html, mime_type = format_mime(value)
if mime_type in ('application/javascript', 'text/html'):
scriptEl = document.createRange().createContextualFragment(html)
element.appendChild(scriptEl)
else:
element.innerHTML = html
@staticmethod
def run_until_complete(f):
p = loop.run_until_complete(f)
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 = document.querySelector(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):
console.log(f"Element.write: {value} --> {append}")
# TODO: it should be the opposite... pyscript.write should use the Element.write
# so we can consolidate on how we write depending on the element type
pyscript.write(self._id, value, append=append)
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:
console.log(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
self.element.after(clone);
return Element(clone.id, clone)
def remove_class(self, classname):
if isinstance(classname, list):
for cl in classname:
self.remove_class(cl)
else:
self.element.classList.remove(classname)
def add_class(self, classname):
self.element.classList.add(classname)
def add_classes(element, class_list):
for klass in class_list.split(' '):
element.classList.add(klass)
def create(what, id_=None, classes=''):
element = document.createElement(what)
if id_:
element.id = id_
add_classes(element, classes)
return Element(id_, element)
class PyWidgetTheme:
def __init__(self, main_style_classes):
self.main_style_classes = main_style_classes
def theme_it(self, widget):
for klass in self.main_style_classes.split(' '):
widget.classList.add(klass)
class PyItemTemplate(Element):
label_fields = None
def __init__(self, data, labels=None, state_key=None, parent=None):
self.data = data
self.register_parent(parent)
if not labels:
labels = list(self.data.keys())
self.labels = labels
self.state_key = state_key
super().__init__(self._id)
def register_parent(self, parent):
self._parent = parent
if parent:
self._id = f"{self._parent._id}-c-{len(self._parent._children)}"
self.data['id'] = self._id
else:
self._id = None
def create(self):
console.log('creating section')
new_child = create('section', self._id, "task bg-white my-1")
console.log('creating values')
console.log('creating innerHtml')
new_child._element.innerHTML = f"""
<label for="flex items-center p-2 ">
<input class="mr-2" type="checkbox" class="task-check">
<p class="m-0 inline">{self.render_content()}</p>
</label>
"""
console.log('returning')
return new_child
def on_click(self, evt):
pass
def pre_append(self):
pass
def post_append(self):
self.element.click = self.on_click
self.element.onclick = self.on_click
self._post_append()
def _post_append(self):
pass
def strike(self, value, extra=None):
if value:
self.add_class("line-through")
else:
self.remove_class("line-through")
def render_content(self):
return ' - '.join([self.data[f] for f in self.labels])
class PyListTemplate:
theme = PyWidgetTheme("flex flex-col-reverse mt-8 mx-8")
item_class = PyItemTemplate
def __init__(self, parent):
self.parent = parent
self._children = []
self._id = self.parent.id
@property
def children(self):
return self._children
@property
def data(self):
return [c.data for c in self._children]
def render_children(self):
out = []
binds = {}
for i, c in enumerate(self._children):
txt = c.element.innerHTML
rnd = str(time.time()).replace(".", "")[-5:]
new_id = f"{c.element.id}-{i}-{rnd}"
binds[new_id] = c.element.id
txt = txt.replace(">", f" id='{new_id}'>")
print(txt)
def foo(evt):
console.log(evt)
evtEl = evt.srcElement
srcEl = Element(binds[evtEl.id])
srcEl.element.onclick()
evtEl.classList = srcEl.element.classList
for new_id, old_id in binds.items():
Element(new_id).element.onclick = foo
def connect(self):
self.md = main_div = document.createElement('div')
main_div.id = self._id + "-list-tasks-container"
if self.theme:
self.theme.theme_it(main_div)
self.parent.appendChild(main_div)
def add(self, *args, **kws):
if not isinstance(args[0], self.item_class):
child = self.item_class(*args, **kws)
else:
child = args[0]
child.register_parent(self)
return self._add(child)
def _add(self, child_elem):
console.log("appending child", child_elem.element)
self.pre_child_append(child_elem)
child_elem.pre_append()
self._children.append(child_elem)
self.md.appendChild(child_elem.create().element)
child_elem.post_append()
self.child_appended(child_elem)
return child_elem
def pre_child_append(self, child):
pass
def child_appended(self, child):
"""Overwrite me to define logic"""
pass
class OutputCtxManager:
def __init__(self, out=None, output_to_console=True, append=True):
self._out = out
self._prev = out
self.output_to_console = output_to_console
self._append = append
def change(self, out=None, err=None, output_to_console=True, append=True):
self._prev = self._out
self._out = out
self.output_to_console = output_to_console
self._append = append
console.log("----> changed out to", self._out, self._append)
def revert(self):
console.log("----> reverted")
self._out = self._prev
def write(self, txt):
console.log('writing to', self._out, txt, self._append)
if self._out:
pyscript.write(self._out, txt, append=self._append)
if self.output_to_console:
console.log(self._out, txt)
class OutputManager:
def __init__(self, out=None, err=None, output_to_console=True, append=True):
sys.stdout = self._out_manager = OutputCtxManager(out, output_to_console, append)
sys.stderr = self._err_manager = OutputCtxManager(err, output_to_console, append)
self.output_to_console = output_to_console
self._append = append
def change(self, out=None, err=None, output_to_console=True, append=True):
self._out_manager.change(out, output_to_console, append)
sys.stdout = self._out_manager
self._err_manager.change(err, output_to_console, append)
sys.stderr = self._err_manager
self.output_to_console = output_to_console
self.append = append
def revert(self):
self._out_manager.revert()
self._err_manager.revert()
sys.stdout = self._out_manager
sys.stderr = self._err_manager
console.log("----> reverted")
pyscript = PyScript()
output_manager = OutputManager()
`;
const loadInterpreter = async function (): Promise<any> {
console.log('creating pyodide runtime');
// eslint-disable-next-line
@@ -426,10 +16,17 @@ const loadInterpreter = async function (): Promise<any> {
// now that we loaded, add additional convenience functions
console.log('loading micropip');
await pyodide.loadPackage('micropip');
console.log('loading pyscript module');
console.log('creating additional definitions');
const output = pyodide.runPython(additional_definitions);
console.log('loading pyscript...');
// let's get the full path of where PyScript is running from so we can load the pyscript.py
// file from the same location
const loadedScript: HTMLScriptElement = document.querySelector(`script[src$='pyscript.js']`);
const scriptPath = loadedScript.src.substr(0, loadedScript.src.lastIndexOf('/'));
await pyodide.runPythonAsync(await (await fetch(`${scriptPath}/pyscript.py`)).text());
console.log(scriptPath);
console.log('done setting up environment');
return pyodide;
};
@@ -445,7 +42,7 @@ const loadFromFile = async function (s: string, runtime: any): Promise<any> {
await runtime.runPythonAsync(
`
from pyodide.http import pyfetch
from js import console
response = await pyfetch("` +
s +
`")

View File

@@ -1,8 +1,11 @@
import asyncio
import base64
import io
import sys
import time
from js import console, document
import micropip
from js import console, document, setInterval, setTimeout
loop = asyncio.get_event_loop()
@@ -135,6 +138,10 @@ class Element:
self._id = element_id
self._element = element
@property
def id(self):
return self._id
@property
def element(self):
"""Return the dom element"""
@@ -142,6 +149,14 @@ class Element:
self._element = document.querySelector(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):
console.log(f"Element.write: {value} --> {append}")
# TODO: it should be the opposite... pyscript.write should use the Element.write
@@ -179,3 +194,230 @@ class Element:
self.element.after(clone)
return Element(clone.id, clone)
def remove_class(self, classname):
if isinstance(classname, list):
for cl in classname:
self.remove_class(cl)
else:
self.element.classList.remove(classname)
def add_class(self, classname):
self.element.classList.add(classname)
def add_classes(element, class_list):
for klass in class_list.split(" "):
element.classList.add(klass)
def create(what, id_=None, classes=""):
element = document.createElement(what)
if id_:
element.id = id_
add_classes(element, classes)
return Element(id_, element)
class PyWidgetTheme:
def __init__(self, main_style_classes):
self.main_style_classes = main_style_classes
def theme_it(self, widget):
for klass in self.main_style_classes.split(" "):
widget.classList.add(klass)
class PyItemTemplate(Element):
label_fields = None
def __init__(self, data, labels=None, state_key=None, parent=None):
self.data = data
self.register_parent(parent)
if not labels:
labels = list(self.data.keys())
self.labels = labels
self.state_key = state_key
super().__init__(self._id)
def register_parent(self, parent):
self._parent = parent
if parent:
self._id = f"{self._parent._id}-c-{len(self._parent._children)}"
self.data["id"] = self._id
else:
self._id = None
def create(self):
console.log("creating section")
new_child = create("section", self._id, "task bg-white my-1")
console.log("creating values")
console.log("creating innerHtml")
new_child._element.innerHTML = f"""
<label for="flex items-center p-2 ">
<input class="mr-2" type="checkbox" class="task-check">
<p class="m-0 inline">{self.render_content()}</p>
</label>
"""
console.log("returning")
return new_child
def on_click(self, evt):
pass
def pre_append(self):
pass
def post_append(self):
self.element.click = self.on_click
self.element.onclick = self.on_click
self._post_append()
def _post_append(self):
pass
def strike(self, value, extra=None):
if value:
self.add_class("line-through")
else:
self.remove_class("line-through")
def render_content(self):
return " - ".join([self.data[f] for f in self.labels])
class PyListTemplate:
theme = PyWidgetTheme("flex flex-col-reverse mt-8 mx-8")
item_class = PyItemTemplate
def __init__(self, parent):
self.parent = parent
self._children = []
self._id = self.parent.id
@property
def children(self):
return self._children
@property
def data(self):
return [c.data for c in self._children]
def render_children(self):
out = []
binds = {}
for i, c in enumerate(self._children):
txt = c.element.innerHTML
rnd = str(time.time()).replace(".", "")[-5:]
new_id = f"{c.element.id}-{i}-{rnd}"
binds[new_id] = c.element.id
txt = txt.replace(">", f" id='{new_id}'>")
print(txt)
def foo(evt):
console.log(evt)
evtEl = evt.srcElement
srcEl = Element(binds[evtEl.id])
srcEl.element.onclick()
evtEl.classList = srcEl.element.classList
for new_id, old_id in binds.items():
Element(new_id).element.onclick = foo
def connect(self):
self.md = main_div = document.createElement("div")
main_div.id = self._id + "-list-tasks-container"
if self.theme:
self.theme.theme_it(main_div)
self.parent.appendChild(main_div)
def add(self, *args, **kws):
if not isinstance(args[0], self.item_class):
child = self.item_class(*args, **kws)
else:
child = args[0]
child.register_parent(self)
return self._add(child)
def _add(self, child_elem):
console.log("appending child", child_elem.element)
self.pre_child_append(child_elem)
child_elem.pre_append()
self._children.append(child_elem)
self.md.appendChild(child_elem.create().element)
child_elem.post_append()
self.child_appended(child_elem)
return child_elem
def pre_child_append(self, child):
pass
def child_appended(self, child):
"""Overwrite me to define logic"""
pass
class OutputCtxManager:
def __init__(self, out=None, output_to_console=True, append=True):
self._out = out
self._prev = out
self.output_to_console = output_to_console
self._append = append
def change(self, out=None, err=None, output_to_console=True, append=True):
self._prev = self._out
self._out = out
self.output_to_console = output_to_console
self._append = append
console.log("----> changed out to", self._out, self._append)
def revert(self):
console.log("----> reverted")
self._out = self._prev
def write(self, txt):
console.log("writing to", self._out, txt, self._append)
if self._out:
pyscript.write(self._out, txt, append=self._append)
if self.output_to_console:
console.log(self._out, txt)
class OutputManager:
def __init__(self, out=None, err=None, output_to_console=True, append=True):
sys.stdout = self._out_manager = OutputCtxManager(
out, output_to_console, append
)
sys.stderr = self._err_manager = OutputCtxManager(
err, output_to_console, append
)
self.output_to_console = output_to_console
self._append = append
def change(self, out=None, err=None, output_to_console=True, append=True):
self._out_manager.change(out, output_to_console, append)
sys.stdout = self._out_manager
self._err_manager.change(err, output_to_console, append)
sys.stderr = self._err_manager
self.output_to_console = output_to_console
self.append = append
def revert(self):
self._out_manager.revert()
self._err_manager.revert()
sys.stdout = self._out_manager
sys.stderr = self._err_manager
console.log("----> reverted")
pyscript = PyScript()
output_manager = OutputManager()