Commit 8f8ce269 authored by Timothy J. Baek's avatar Timothy J. Baek
Browse files

refac: migrated to pyodide from pyscript

parent 0a909215
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
"js-sha256": "^0.10.1", "js-sha256": "^0.10.1",
"katex": "^0.16.9", "katex": "^0.16.9",
"marked": "^9.1.0", "marked": "^9.1.0",
"pyodide": "^0.25.1", "pyodide": "^0.26.0-alpha.4",
"svelte-sonner": "^0.3.19", "svelte-sonner": "^0.3.19",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"uuid": "^9.0.1" "uuid": "^9.0.1"
...@@ -6277,12 +6277,15 @@ ...@@ -6277,12 +6277,15 @@
} }
}, },
"node_modules/pyodide": { "node_modules/pyodide": {
"version": "0.25.1", "version": "0.26.0-alpha.4",
"resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.25.1.tgz", "resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.26.0-alpha.4.tgz",
"integrity": "sha512-y0nJ/fLA3bxD2iZRzvVTbP2O+wp4Ewm2wThfV4HF0BytQ6hsoqTJFLNY4usLOcCVBrK8TTWqFqrmsVPzHe4rsw==", "integrity": "sha512-Ixuczq99DwhQlE+Bt0RaS6Ln9MHSZOkbU6iN8azwaeorjHtr7ukaxh+FeTxViFrp2y+ITyKgmcobY+JnBPcULw==",
"dependencies": { "dependencies": {
"base-64": "^1.0.0", "base-64": "^1.0.0",
"ws": "^8.5.0" "ws": "^8.5.0"
},
"engines": {
"node": ">=18.0.0"
} }
}, },
"node_modules/qs": { "node_modules/qs": {
......
mkdir -p ./static/pyodide
cp ./node_modules/pyodide/pyodide* ./static/pyodide/
cp ./node_modules/pyodide/python_stdlib.zip ./static/pyodide/
\ No newline at end of file
cp -R ./node_modules/@pyscript/core/dist ./static/pyscript
# mkdir -p ./static/micropython
# cp -R ./node_modules/@micropython/micropython-webassembly-pyscript/micropython.* ./static/micropython
mkdir -p ./static/pyodide
cp ./node_modules/pyodide/pyodide* ./static/pyodide/
cp ./node_modules/pyodide/python_stdlib.zip ./static/pyodide/
\ No newline at end of file
function execute(id, text) {
// pyscript
let div = document.createElement('div');
let html = `
<py-script type="mpy">
${text}
</py-script>
`;
div.innerHTML = html;
const pyScript = div.firstElementChild;
try {
document.body.appendChild(pyScript);
setTimeout(() => {
document.body.removeChild(pyScript);
}, 0);
} catch (error) {
console.error('Python error:');
console.error(error);
}
}
...@@ -13,9 +13,6 @@ ...@@ -13,9 +13,6 @@
href="/opensearch.xml" href="/opensearch.xml"
/> />
<script type="module" src="/pyscript/core.js"></script>
<link rel="stylesheet" href="/pyscript/core.css" />
<script> <script>
// On page load or when changing themes, best to add inline in `head` to avoid FOUC // On page load or when changing themes, best to add inline in `head` to avoid FOUC
(() => { (() => {
...@@ -58,11 +55,6 @@ ...@@ -58,11 +55,6 @@
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">
<py-config> interpreter = "/pyodide/pyodide.mjs" </py-config>
<script type="py">
print('pyscript:loaded')
</script>
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>
<div <div
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import { copyToClipboard } from '$lib/utils'; import { copyToClipboard } from '$lib/utils';
import hljs from 'highlight.js'; import hljs from 'highlight.js';
import 'highlight.js/styles/github-dark.min.css'; import 'highlight.js/styles/github-dark.min.css';
import { loadPyodide } from 'pyodide';
import { tick } from 'svelte'; import { tick } from 'svelte';
export let id = ''; export let id = '';
...@@ -10,6 +11,11 @@ ...@@ -10,6 +11,11 @@
export let code = ''; export let code = '';
let executed = false; let executed = false;
let stdout = null;
let stderr = null;
let result = null;
let copied = false; let copied = false;
const copyCode = async () => { const copyCode = async () => {
...@@ -131,72 +137,35 @@ ...@@ -131,72 +137,35 @@
return false; return false;
}; };
const executePython = async (text) => { const executePython = async (code) => {
executed = true; executed = true;
await tick(); let pyodide = await loadPyodide({
const outputDiv = document.getElementById(`code-output-${id}`); indexURL: '/pyodide/',
stderr: (text) => {
console.log('An error occured:', text);
if (stderr) {
stderr += `${text}\n`;
} else {
stderr = `${text}\n`;
}
},
stdout: (text) => {
console.log('Python output:', text);
if (stdout) {
stdout += `${text}\n`;
} else {
stdout = `${text}\n`;
}
}
});
if (outputDiv) { result = pyodide.runPython(code);
outputDiv.innerText = 'Running...';
}
text = text console.log(result);
.split('\n') console.log(stderr);
.map((line, index) => (index === 0 ? line : ' ' + line)) console.log(stdout);
.join('\n');
// pyscript
let div = document.createElement('div');
let html = `
<py-script type="py" worker>
import js
import sys
import io
# Create a StringIO object to capture the output
output_capture = io.StringIO()
# Save the current standard output
original_stdout = sys.stdout
# Replace the standard output with the StringIO object
sys.stdout = output_capture
try:
${text}
except Exception as e:
# Capture any errors and write them to the output capture
print(f"Error: {e}", file=output_capture)
# Restore the original standard output
sys.stdout = original_stdout
# Retrieve the captured output
captured_output = "[NO OUTPUT]"
captured_output = output_capture.getvalue()
# Print the captured output
print(captured_output)
def display_message():
output_div = js.document.getElementById("code-output-${id}")
output_div.innerText = captured_output
display_message()
</py-script>`;
div.innerHTML = html;
const pyScript = div.firstElementChild;
try {
document.body.appendChild(pyScript);
setTimeout(() => {
document.body.removeChild(pyScript);
}, 0);
} catch (error) {
console.error('Python error:');
console.error(error);
}
}; };
$: highlightedCode = code ? hljs.highlightAuto(code, hljs.getLanguage(lang)?.aliases).value : ''; $: highlightedCode = code ? hljs.highlightAuto(code, hljs.getLanguage(lang)?.aliases).value : '';
...@@ -234,7 +203,17 @@ display_message() ...@@ -234,7 +203,17 @@ display_message()
{#if executed} {#if executed}
<div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg"> <div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div> <div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
<div id="code-output-{id}" class="text-sm" /> <div class="text-sm">
{#if stdout}
{stdout}
{:else if result}
{result}
{:else if stderr}
{stderr}
{:else}
Running...
{/if}
</div>
</div> </div>
{/if} {/if}
</div> </div>
......
...@@ -6,15 +6,15 @@ import { getLiteLLMModels } from '$lib/apis/litellm'; ...@@ -6,15 +6,15 @@ import { getLiteLLMModels } from '$lib/apis/litellm';
export const getModels = async (token: string) => { export const getModels = async (token: string) => {
let models = await Promise.all([ let models = await Promise.all([
await getOllamaModels(token).catch((error) => { getOllamaModels(token).catch((error) => {
console.log(error); console.log(error);
return null; return null;
}), }),
await getOpenAIModels(token).catch((error) => { // getOpenAIModels(token).catch((error) => {
console.log(error); // console.log(error);
return null; // return null;
}), // }),
await getLiteLLMModels(token).catch((error) => { getLiteLLMModels(token).catch((error) => {
console.log(error); console.log(error);
return null; return null;
}) })
......
{
"name": "pyodide",
"version": "0.25.1",
"description": "The Pyodide JavaScript package",
"keywords": [
"python",
"webassembly"
],
"homepage": "https://github.com/pyodide/pyodide",
"repository": {
"type": "git",
"url": "https://github.com/pyodide/pyodide"
},
"bugs": {
"url": "https://github.com/pyodide/pyodide/issues"
},
"license": "Apache-2.0",
"devDependencies": {
"@types/assert": "^1.5.6",
"@types/expect": "^24.3.0",
"@types/mocha": "^9.1.0",
"@types/node": "^20.8.4",
"@types/ws": "^8.5.3",
"chai": "^4.3.6",
"chai-as-promised": "^7.1.1",
"cross-env": "^7.0.3",
"dts-bundle-generator": "^8.1.1",
"error-stack-parser": "^2.1.4",
"esbuild": "^0.17.12",
"express": "^4.17.3",
"mocha": "^9.0.2",
"npm-run-all": "^4.1.5",
"nyc": "^15.1.0",
"prettier": "^2.2.1",
"ts-mocha": "^9.0.2",
"tsd": "^0.24.1",
"typedoc": "^0.25.1",
"typescript": "^4.6.4",
"wabt": "^1.0.32"
},
"main": "pyodide.js",
"exports": {
".": {
"require": "./pyodide.js",
"import": "./pyodide.mjs",
"types": "./pyodide.d.ts"
},
"./ffi": {
"types": "./ffi.d.ts"
},
"./pyodide.asm.wasm": "./pyodide.asm.wasm",
"./pyodide.asm.js": "./pyodide.asm.js",
"./python_stdlib.zip": "./python_stdlib.zip",
"./pyodide.mjs": "./pyodide.mjs",
"./pyodide.js": "./pyodide.js",
"./package.json": "./package.json",
"./pyodide-lock.json": "./pyodide-lock.json"
},
"files": [
"pyodide.asm.js",
"pyodide.asm.wasm",
"python_stdlib.zip",
"pyodide.mjs",
"pyodide.js.map",
"pyodide.mjs.map",
"pyodide.d.ts",
"ffi.d.ts",
"pyodide-lock.json",
"console.html"
],
"browser": {
"child_process": false,
"crypto": false,
"fs": false,
"fs/promises": false,
"path": false,
"url": false,
"vm": false,
"ws": false
},
"scripts": {
"build": "tsc --noEmit && node esbuild.config.mjs",
"test": "npm-run-all test:*",
"test:unit": "cross-env TEST_NODE=1 ts-mocha --node-option=experimental-loader=./test/loader.mjs --node-option=experimental-wasm-stack-switching -p tsconfig.test.json test/unit/**/*.test.*",
"test:node": "cross-env TEST_NODE=1 mocha test/integration/**/*.test.js",
"test:browser": "mocha test/integration/**/*.test.js",
"tsc": "tsc --noEmit",
"coverage": "cross-env TEST_NODE=1 npm-run-all coverage:*",
"coverage:build": "nyc npm run test:node"
},
"mocha": {
"bail": false,
"timeout": 30000,
"full-trace": true,
"inline-diffs": true,
"check-leaks": false,
"global": [
"pyodide",
"page",
"chai"
]
},
"nyc": {
"reporter": [
"html",
"text-summary"
],
"include": [
"*.ts"
],
"all": true,
"clean": true,
"cache": false,
"instrument": false,
"checkCoverage": true,
"statements": 95,
"functions": 95,
"branches": 80,
"lines": 95
},
"tsd": {
"compilerOptions": {
"lib": [
"ES2017",
"DOM"
]
}
},
"dependencies": {
"base-64": "^1.0.0",
"ws": "^8.5.0"
},
"types": "./pyodide.d.ts"
}
This diff is collapsed.
This diff is collapsed.
...@@ -653,10 +653,36 @@ declare class PyCallableMethods { ...@@ -653,10 +653,36 @@ declare class PyCallableMethods {
*/ */
call(thisArg: any, ...jsargs: any): any; call(thisArg: any, ...jsargs: any): any;
/** /**
* Call the function with key word arguments. The last argument must be an * Call the function with keyword arguments. The last argument must be an
* object with the keyword arguments. * object with the keyword arguments.
*/ */
callKwargs(...jsargs: any): any; callKwargs(...jsargs: any): any;
/**
* Call the function in a "relaxed" manner. Any extra arguments will be
* ignored. This matches the behavior of JavaScript functions more accurately.
*
* Any extra arguments will be ignored. This matches the behavior of
* JavaScript functions more accurately. Missing arguments are **NOT** filled
* with `None`. If too few arguments are passed, this will still raise a
* TypeError.
*
* This uses :py:func:`pyodide.code.relaxed_call`.
*/
callRelaxed(...jsargs: any): any;
/**
* Call the function with keyword arguments in a "relaxed" manner. The last
* argument must be an object with the keyword arguments. Any extra arguments
* will be ignored. This matches the behavior of JavaScript functions more
* accurately.
*
* Missing arguments are **NOT** filled with `None`. If too few arguments are
* passed, this will still raise a TypeError. Also, if the same argument is
* passed as both a keyword argument and a positional argument, it will raise
* an error.
*
* This uses :py:func:`pyodide.code.relaxed_call`.
*/
callKwargsRelaxed(...jsargs: any): any;
/** /**
* Call the function with stack switching enabled. Functions called this way * Call the function with stack switching enabled. Functions called this way
* can use * can use
...@@ -911,7 +937,7 @@ interface CanvasInterface { ...@@ -911,7 +937,7 @@ interface CanvasInterface {
declare class PythonError extends Error { declare class PythonError extends Error {
/** /**
* The address of the error we are wrapping. We may later compare this * The address of the error we are wrapping. We may later compare this
* against sys.last_value. * against sys.last_exc.
* WARNING: we don't own a reference to this pointer, dereferencing it * WARNING: we don't own a reference to this pointer, dereferencing it
* may be a use-after-free error! * may be a use-after-free error!
* @private * @private
...@@ -1128,36 +1154,6 @@ declare class PyodideAPI { ...@@ -1128,36 +1154,6 @@ declare class PyodideAPI {
locals?: PyProxy; locals?: PyProxy;
filename?: string; filename?: string;
}): Promise<any>; }): Promise<any>;
/**
* Runs a Python code string like :js:func:`pyodide.runPython` but with stack
* switching enabled. Code executed in this way can use
* :py:meth:`PyodideFuture.syncify() <pyodide.webloop.PyodideFuture.syncify>`
* to block until a :py:class:`~asyncio.Future` or :js:class:`Promise` is
* resolved. Only works in runtimes with JS Promise Integration enabled.
*
* .. admonition:: Experimental
* :class: warning
*
* This feature is not yet stable.
*
* @experimental
* @param code The Python code to run
* @param options
* @param options.globals An optional Python dictionary to use as the globals.
* Defaults to :js:attr:`pyodide.globals`.
* @param options.locals An optional Python dictionary to use as the locals.
* Defaults to the same as ``globals``.
* @param options.filename An optional string to use as the file name.
* Defaults to ``"<exec>"``. If a custom file name is given, the
* traceback for any exception that is thrown will show source lines
* (unless the given file name starts with ``<`` and ends with ``>``).
* @returns The result of the Python code translated to JavaScript.
*/
static runPythonSyncifying(code: string, options?: {
globals?: PyProxy;
locals?: PyProxy;
filename?: string;
}): Promise<any>;
/** /**
* Registers the JavaScript object ``module`` as a JavaScript module named * Registers the JavaScript object ``module`` as a JavaScript module named
* ``name``. This module can then be imported from Python using the standard * ``name``. This module can then be imported from Python using the standard
...@@ -1232,30 +1228,25 @@ declare class PyodideAPI { ...@@ -1232,30 +1228,25 @@ declare class PyodideAPI {
/** /**
* Imports a module and returns it. * Imports a module and returns it.
* *
* .. admonition:: Warning * If `name` has no dot in it, then `pyimport(name)` is approximately
* :class: warning * equivalent to:
* * ```js
* This function has a completely different behavior than the old removed pyimport function! * pyodide.runPython(`import ${name}; ${name}`)
* * ```
* ``pyimport`` is roughly equivalent to: * except that `name` is not introduced into the Python global namespace. If
* * the name has one or more dots in it, say it is of the form `path.name`
* .. code-block:: js * where `name` has no dots but path may have zero or more dots. Then it is
* * approximately the same as:
* pyodide.runPython(`import ${pkgname}; ${pkgname}`); * ```js
* * pyodide.runPython(`from ${path} import ${name}; ${name}`);
* except that the global namespace will not change. * ```
*
* Example:
*
* .. code-block:: js
*
* let sysmodule = pyodide.pyimport("sys");
* let recursionLimit = sysmodule.getrecursionlimit();
* *
* @param mod_name The name of the module to import * @param mod_name The name of the module to import
* @returns A PyProxy for the imported module *
* @example
* pyodide.pyimport("math.comb")(4, 2) // returns 4 choose 2 = 6
*/ */
static pyimport(mod_name: string): PyProxy; static pyimport(mod_name: string): any;
/** /**
* Unpack an archive into a target directory. * Unpack an archive into a target directory.
* *
...@@ -1277,14 +1268,26 @@ declare class PyodideAPI { ...@@ -1277,14 +1268,26 @@ declare class PyodideAPI {
}): void; }): void;
/** /**
* Mounts a :js:class:`FileSystemDirectoryHandle` into the target directory. * Mounts a :js:class:`FileSystemDirectoryHandle` into the target directory.
* Currently it's only possible to acquire a
* :js:class:`FileSystemDirectoryHandle` in Chrome.
* *
* @param path The absolute path in the Emscripten file system to mount the * @param path The absolute path in the Emscripten file system to mount the
* native directory. If the directory does not exist, it will be created. If it * native directory. If the directory does not exist, it will be created. If
* does exist, it must be empty. * it does exist, it must be empty.
* @param fileSystemHandle A handle returned by :js:func:`navigator.storage.getDirectory() <getDirectory>` * @param fileSystemHandle A handle returned by
* or :js:func:`window.showDirectoryPicker() <showDirectoryPicker>`. * :js:func:`navigator.storage.getDirectory() <getDirectory>` or
* :js:func:`window.showDirectoryPicker() <showDirectoryPicker>`.
*/ */
static mountNativeFS(path: string, fileSystemHandle: FileSystemDirectoryHandle): Promise<NativeFS>; static mountNativeFS(path: string, fileSystemHandle: FileSystemDirectoryHandle): Promise<NativeFS>;
/**
* Mounts a host directory into Pyodide file system. Only works in node.
*
* @param emscriptenPath The absolute path in the Emscripten file system to
* mount the native directory. If the directory does not exist, it will be
* created. If it does exist, it must be empty.
* @param hostPath The host path to mount. It must be a directory that exists.
*/
static mountNodeFS(emscriptenPath: string, hostPath: string): void;
/** /**
* Tell Pyodide about Comlink. * Tell Pyodide about Comlink.
* Necessary to enable importing Comlink proxies into Python. * Necessary to enable importing Comlink proxies into Python.
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment