├── README.md ├── icons ├── icon-128.png ├── icon-16.png ├── icon-32.png ├── icon-48.png └── icon-64.png ├── manifest.json ├── popup.css ├── popup.html ├── popup.png └── runtime ├── distutils.tar ├── micropip-0.1-py3-none-any.whl ├── packaging-21.3-py3-none-any.whl ├── pyodide.asm.data ├── pyodide.asm.js ├── pyodide.asm.wasm ├── pyodide.js ├── pyodide_py.tar ├── pyparsing-3.0.9-py3-none-any.whl ├── pyscript.css ├── pyscript.js ├── repodata.json ├── setup.py └── setup.sh /README.md: -------------------------------------------------------------------------------- 1 | # pyscript-local-runtime 2 | 3 | This repository provides a framework for running PyScript and all its runtime dependencies locally e.g. to create a Chrome extension using Python, or an offline web app using PyScript without relying on internet access. 4 | 5 | PyScript and its dependency Pyodide continue to evolve but this demo is frozen at Pyodide v0.21.3. You're strongly advised to check out the [latest version of PyScript](https://pyscript.net/) and there are numerous PyScript tutorials online for learning more about its capabilities - that's not the focus of this Demo! 6 | 7 | All the files you need to run PyScript (at v0.21.3) are in the `/runtime` directory and you can also download them on POSIX using wget... 8 | ```shell 9 | cd runtime 10 | source setup.sh 11 | ``` 12 | 13 | or on Windows using the helper script supplied: 14 | ``` 15 | cd runtime 16 | python setup.py 17 | ``` 18 | 19 | ## **EXAMPLE CHROME EXTENSION** 20 | ![](popup.png) 21 | 22 | This example Chrome Extension launches a Popup when clicked, renders the time using Python's `datetime` module, and provides a Python REPL session for you to play with directly inside the extension. 23 | 24 | To use this extension directly, first clone it to your local machine then 25 | [follow this tutorial](https://medium.com/p/6c6b0e2e1573) to load the unpacked extension into Chrome and pin it to your Extensions toolbar. 26 | 27 | * Icons are in the `/icons` folder. 28 | * PyScript/Pyodide files to run this offline or as part of an extension are in the `/runtime` folder. 29 | * Other magic required for Chrome to recognise this as an extension is in `manifest.json`. 30 | * `popup.css` is a super-simple Cascading Style Sheet which defines the appearance of the Popup - in this case simply the width, height, and background colour. 31 | * `popup.html` defines the page content of the Popup. It includes some simple boiler-plate HTML as well as some examples of Python code which runs under the tags `` and/or ``. 32 | * `popup.html` must include a `` block as follows: 33 | 34 | ```html 35 | 36 | [[runtimes]] 37 | src = "runtime/pyodide.js" 38 | name = "pyodide-0.21.3" 39 | lang = "python" 40 | 41 | ``` 42 | 43 | * `popup.html` must also include two lines to load PyScript and its default stylesheet into the extension: 44 | 45 | ```html 46 | 47 | 48 | ``` 49 | 50 | Further information about getting started with Chrome Extensions is available [here](https://developer.chrome.com/docs/extensions/mv3/getstarted/). 51 | 52 | All the best, 53 | Pete 54 | 55 | https://github.com/PFython 56 | 57 | ## **CREDITS** 58 | 59 | A big "Thank You" to https://github.com/tedpatrick (Engineering Manager at Anaconda) for pointing me in the right direction. 60 | 61 | If this code helps you save time and focus on more important things, please feel free to to show your appreciation by starring the repository on Github. 62 | 63 | I'd also be delighted if you wanted to: 64 | 65 | Buy Me A Coffee 66 | 67 | -------------------------------------------------------------------------------- /icons/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PFython/pyscript-local-runtime/f5b5efb0c33cadeff8716532f24a6daac636ff7c/icons/icon-128.png -------------------------------------------------------------------------------- /icons/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PFython/pyscript-local-runtime/f5b5efb0c33cadeff8716532f24a6daac636ff7c/icons/icon-16.png -------------------------------------------------------------------------------- /icons/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PFython/pyscript-local-runtime/f5b5efb0c33cadeff8716532f24a6daac636ff7c/icons/icon-32.png -------------------------------------------------------------------------------- /icons/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PFython/pyscript-local-runtime/f5b5efb0c33cadeff8716532f24a6daac636ff7c/icons/icon-48.png -------------------------------------------------------------------------------- /icons/icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PFython/pyscript-local-runtime/f5b5efb0c33cadeff8716532f24a6daac636ff7c/icons/icon-64.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PyScript Demo 1", 3 | "description": "Build Chrome Extensions in Python/PyScript", 4 | "version": "1.0", 5 | "manifest_version": 3, 6 | "icons": { 7 | "16": "icons/icon-16.png", 8 | "32": "icons/icon-32.png", 9 | "48": "icons/icon-48.png", 10 | "128": "icons/icon-128.png" 11 | }, 12 | "action": { 13 | "default_popup": "popup.html", 14 | "default_icon": "icons/icon-128.png" 15 | }, 16 | "content_security_policy": { 17 | "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /popup.css: -------------------------------------------------------------------------------- 1 | body { 2 | width: 20rem; 3 | height: 17rem; 4 | background: orange; 5 | } 6 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PyScript Demo 1 5 | 6 | 7 | 8 | [[runtimes]] 9 | src = "runtime/pyodide.js" 10 | name = "pyodide-0.21.3" 11 | lang = "python" 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |

Hello World!

20 |

21 | 22 | 23 | from js import document as popup # DOM for this popup page 24 | from datetime import datetime 25 | 26 | # Use print to update the popup 27 | print("This is the current date and time in ISO8601 format, courtesy of Python and PyScript.

") 28 | 29 | # Update an element in the popup directly 30 | insert_content = popup.getElementById('datetime') 31 | insert_content.innerHTML = f"{datetime.now().isoformat()}" 32 |
33 | 34 | text = "This is a REPL session.
" 35 | text += "You can add several lines of Python
" 36 | text += "Then just press Shift-ENTER to run!" 37 | print(text.upper()) 38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PFython/pyscript-local-runtime/f5b5efb0c33cadeff8716532f24a6daac636ff7c/popup.png -------------------------------------------------------------------------------- /runtime/micropip-0.1-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PFython/pyscript-local-runtime/f5b5efb0c33cadeff8716532f24a6daac636ff7c/runtime/micropip-0.1-py3-none-any.whl -------------------------------------------------------------------------------- /runtime/packaging-21.3-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PFython/pyscript-local-runtime/f5b5efb0c33cadeff8716532f24a6daac636ff7c/runtime/packaging-21.3-py3-none-any.whl -------------------------------------------------------------------------------- /runtime/pyodide.asm.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PFython/pyscript-local-runtime/f5b5efb0c33cadeff8716532f24a6daac636ff7c/runtime/pyodide.asm.data -------------------------------------------------------------------------------- /runtime/pyodide.asm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PFython/pyscript-local-runtime/f5b5efb0c33cadeff8716532f24a6daac636ff7c/runtime/pyodide.asm.wasm -------------------------------------------------------------------------------- /runtime/pyodide.js: -------------------------------------------------------------------------------- 1 | !function(global,factory){"object"==typeof exports&&"undefined"!=typeof module?factory(exports):"function"==typeof define&&define.amd?define(["exports"],factory):factory((global="undefined"!=typeof globalThis?globalThis:global||self).loadPyodide={})}(this,(function(exports){"use strict";"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;var errorStackParser={exports:{}},stackframe={exports:{}};!function(module,exports){module.exports=function(){function _isNumber(n){return!isNaN(parseFloat(n))&&isFinite(n)}function _capitalize(str){return str.charAt(0).toUpperCase()+str.substring(1)}function _getter(p){return function(){return this[p]}}var booleanProps=["isConstructor","isEval","isNative","isToplevel"],numericProps=["columnNumber","lineNumber"],stringProps=["fileName","functionName","source"],arrayProps=["args"],objectProps=["evalOrigin"],props=booleanProps.concat(numericProps,stringProps,arrayProps,objectProps);function StackFrame(obj){if(obj)for(var i=0;i-1&&(line=line.replace(/eval code/g,"eval").replace(/(\(eval at [^()]*)|(,.*$)/g,""));var sanitizedLine=line.replace(/^\s+/,"").replace(/\(eval code/g,"(").replace(/^.*?\s+/,""),location=sanitizedLine.match(/ (\(.+\)$)/);sanitizedLine=location?sanitizedLine.replace(location[0],""):sanitizedLine;var locationParts=this.extractLocation(location?location[1]:sanitizedLine),functionName=location&&sanitizedLine||void 0,fileName=["eval",""].indexOf(locationParts[0])>-1?void 0:locationParts[0];return new StackFrame({functionName:functionName,fileName:fileName,lineNumber:locationParts[1],columnNumber:locationParts[2],source:line})}),this)},parseFFOrSafari:function(error){return error.stack.split("\n").filter((function(line){return!line.match(SAFARI_NATIVE_CODE_REGEXP)}),this).map((function(line){if(line.indexOf(" > eval")>-1&&(line=line.replace(/ line (\d+)(?: > eval line \d+)* > eval:\d+:\d+/g,":$1")),-1===line.indexOf("@")&&-1===line.indexOf(":"))return new StackFrame({functionName:line});var functionNameRegex=/((.*".+"[^@]*)?[^@]*)(?:@)/,matches=line.match(functionNameRegex),functionName=matches&&matches[1]?matches[1]:void 0,locationParts=this.extractLocation(line.replace(functionNameRegex,""));return new StackFrame({functionName:functionName,fileName:locationParts[0],lineNumber:locationParts[1],columnNumber:locationParts[2],source:line})}),this)},parseOpera:function(e){return!e.stacktrace||e.message.indexOf("\n")>-1&&e.message.split("\n").length>e.stacktrace.split("\n").length?this.parseOpera9(e):e.stack?this.parseOpera11(e):this.parseOpera10(e)},parseOpera9:function(e){for(var lineRE=/Line (\d+).*script (?:in )?(\S+)/i,lines=e.message.split("\n"),result=[],i=2,len=lines.length;i/,"$2").replace(/\([^)]*\)/g,"")||void 0;functionCall.match(/\(([^)]*)\)/)&&(argsRaw=functionCall.replace(/^[^(]+\(([^)]*)\)$/,"$1"));var args=void 0===argsRaw||"[arguments not available]"===argsRaw?void 0:argsRaw.split(",");return new StackFrame({functionName:functionName,args:args,fileName:locationParts[0],lineNumber:locationParts[1],columnNumber:locationParts[2],source:line})}),this)}})}(errorStackParser);var ErrorStackParser=errorStackParser.exports;const IN_NODE="undefined"!=typeof process&&process.release&&"node"===process.release.name&&void 0===process.browser;let nodeUrlMod,nodeFetch,nodePath,nodeVmMod,nodeFsPromisesMod,resolvePath,pathSep,loadBinaryFile,loadScript;if(resolvePath=IN_NODE?function(path,base){return nodePath.resolve(base||".",path)}:function(path,base){return void 0===base&&(base=location),new URL(path,base).toString()},IN_NODE||(pathSep="/"),loadBinaryFile=IN_NODE?async function(path,_file_sub_resource_hash){if(path.startsWith("file://")&&(path=path.slice("file://".length)),path.includes("://")){let response=await nodeFetch(path);if(!response.ok)throw new Error(`Failed to load '${path}': request failed.`);return new Uint8Array(await response.arrayBuffer())}{const data=await nodeFsPromisesMod.readFile(path);return new Uint8Array(data.buffer,data.byteOffset,data.byteLength)}}:async function(path,subResourceHash){const url=new URL(path,location);let options=subResourceHash?{integrity:subResourceHash}:{},response=await fetch(url,options);if(!response.ok)throw new Error(`Failed to load '${url}': request failed.`);return new Uint8Array(await response.arrayBuffer())},globalThis.document)loadScript=async url=>await import(/* webpackIgnore: true */ url);else if(globalThis.importScripts)loadScript=async url=>{try{globalThis.importScripts(url)}catch(e){if(!(e instanceof TypeError))throw e;await import(/* webpackIgnore: true */ url)}};else{if(!IN_NODE)throw new Error("Cannot determine runtime environment");loadScript=async function(url){url.startsWith("file://")&&(url=url.slice("file://".length));url.includes("://")?nodeVmMod.runInThisContext(await(await nodeFetch(url)).text()):await import(/* webpackIgnore: true */ nodeUrlMod.pathToFileURL(url).href)}}function setStandardStreams(Module,stdin,stdout,stderr){stdout&&(Module.print=stdout),stderr&&(Module.printErr=stderr),stdin&&Module.preRun.push((function(){Module.FS.init(function(stdin){const encoder=new TextEncoder;let input=new Uint8Array(0),inputIndex=-1;function stdinWrapper(){try{if(-1===inputIndex){let text=stdin();if(null==text)return null;if("string"!=typeof text)throw new TypeError(`Expected stdin to return string, null, or undefined, got type ${typeof text}.`);text.endsWith("\n")||(text+="\n"),input=encoder.encode(text),inputIndex=0}if(inputIndex"get"===symbol?key=>{let result=target.get(key);return void 0===result&&(result=builtins_dict.get(key)),result}:"has"===symbol?key=>target.has(key)||builtins_dict.has(key):Reflect.get(target,symbol)}));let importhook=API._pyodide._importhook;importhook.register_js_finder(),importhook.register_js_module("js",config.jsglobals),importhook.register_unvendored_stdlib_finder();let pyodide=API.makePublicAPI();return importhook.register_js_module("pyodide_js",pyodide),API.pyodide_py=import_module("pyodide"),API.pyodide_code=import_module("pyodide.code"),API.pyodide_ffi=import_module("pyodide.ffi"),API.package_loader=import_module("pyodide._package_loader"),pyodide.pyodide_py=API.pyodide_py,pyodide.globals=API.globals,pyodide}async function loadPyodide(options={}){await async function(){if(!IN_NODE)return;if(nodeUrlMod=(await import(/* webpackIgnore: true */ "url")).default,nodeFsPromisesMod=await import(/* webpackIgnore: true */ "fs/promises"),nodeFetch=globalThis.fetch?fetch:(await import(/* webpackIgnore: true */ "node-fetch")).default,nodeVmMod=(await import(/* webpackIgnore: true */ "vm")).default,nodePath=await import(/* webpackIgnore: true */ "path"),pathSep=nodePath.sep,"undefined"!=typeof require)return;const node_modules={fs:await import(/* webpackIgnore: true */ "fs"),crypto:await import(/* webpackIgnore: true */ "crypto"),ws:await import(/* webpackIgnore: true */ "ws"),child_process:await import(/* webpackIgnore: true */ "child_process")};globalThis.require=function(mod){return node_modules[mod]}}();let indexURL=options.indexURL||function(){if("string"==typeof __dirname)return __dirname;let err;try{throw new Error}catch(e){err=e}let fileName=ErrorStackParser.parse(err)[0].fileName;const indexOfLastSlash=fileName.lastIndexOf(pathSep);if(-1===indexOfLastSlash)throw new Error("Could not extract indexURL path from pyodide module location");return fileName.slice(0,indexOfLastSlash)}();indexURL=resolvePath(indexURL),indexURL.endsWith("/")||(indexURL+="/"),options.indexURL=indexURL;const default_config={fullStdLib:!0,jsglobals:globalThis,stdin:globalThis.prompt?globalThis.prompt:void 0,homedir:"/home/pyodide",lockFileURL:indexURL+"repodata.json"},config=Object.assign(default_config,options),pyodide_py_tar_promise=loadBinaryFile(config.indexURL+"pyodide_py.tar"),Module={noImageDecoding:!0,noAudioDecoding:!0,noWasmDecoding:!1,preloadedWasm:{},preRun:[]},API={config:config};Module.API=API,setStandardStreams(Module,config.stdin,config.stdout,config.stderr),function(Module,path){Module.preRun.push((function(){try{Module.FS.mkdirTree(path)}catch(e){console.error(`Error occurred while making a home directory '${path}':`),console.error(e),console.error("Using '/' for a home directory instead"),path="/"}Module.ENV.HOME=path,Module.FS.chdir(path)}))}(Module,config.homedir);const moduleLoaded=new Promise((r=>Module.postRun=r));Module.locateFile=path=>config.indexURL+path;const scriptSrc=`${config.indexURL}pyodide.asm.js`;if(await loadScript(scriptSrc),await _createPyodideModule(Module),await moduleLoaded,"0.21.3"!==API.version)throw new Error(`Pyodide version does not match: '0.21.3' <==> '${API.version}'. If you updated the Pyodide version, make sure you also updated the 'indexURL' parameter passed to loadPyodide.`);Module.locateFile=path=>{throw new Error("Didn't expect to load any more file_packager files!")};const pyodide_py_tar=await pyodide_py_tar_promise;!function(Module,pyodide_py_tar){let stream=Module.FS.open("/pyodide_py.tar","w");Module.FS.write(stream,pyodide_py_tar,0,pyodide_py_tar.byteLength,void 0,!0),Module.FS.close(stream);const code_ptr=Module.stringToNewUTF8('\nfrom sys import version_info\npyversion = f"python{version_info.major}.{version_info.minor}"\nimport shutil\nshutil.unpack_archive("/pyodide_py.tar", f"/lib/{pyversion}/site-packages/")\ndel shutil\nimport importlib\nimportlib.invalidate_caches()\ndel importlib\n ');if(Module._PyRun_SimpleString(code_ptr))throw new Error("OOPS!");Module._free(code_ptr),Module.FS.unlink("/pyodide_py.tar")}(Module,pyodide_py_tar),Module._pyodide_init();const pyodide=finalizeBootstrap(API,config);if(pyodide.version.includes("dev")||API.setCdnUrl(`https://cdn.jsdelivr.net/pyodide/v${pyodide.version}/full/`),await API.packageIndexReady,"0.21.3"!==API.repodata_info.version)throw new Error("Lock file version doesn't match Pyodide version");return config.fullStdLib&&await pyodide.loadPackage(["distutils"]),pyodide.runPython("print('Python initialization complete')"),pyodide}globalThis.loadPyodide=loadPyodide,exports.loadPyodide=loadPyodide,exports.version="0.21.3",Object.defineProperty(exports,"__esModule",{value:!0})})); 2 | //# sourceMappingURL=pyodide.js.map 3 | -------------------------------------------------------------------------------- /runtime/pyodide_py.tar: -------------------------------------------------------------------------------- 1 | pyodide/0000755000000000000000000000000014310733215011210 5ustar rootrootpyodide/_core.py0000644000000000000000000000116314310731640012652 0ustar rootrootimport sys 2 | 3 | IN_BROWSER = "_pyodide_core" in sys.modules 4 | 5 | if IN_BROWSER: 6 | from _pyodide_core import ( 7 | ConversionError, 8 | JsException, 9 | JsProxy, 10 | create_once_callable, 11 | create_proxy, 12 | destroy_proxies, 13 | to_js, 14 | ) 15 | else: 16 | from _pyodide._core_docs import ( 17 | ConversionError, 18 | JsException, 19 | JsProxy, 20 | create_once_callable, 21 | create_proxy, 22 | destroy_proxies, 23 | to_js, 24 | ) 25 | 26 | 27 | __all__ = [ 28 | "JsProxy", 29 | "JsException", 30 | "create_proxy", 31 | "create_once_callable", 32 | "to_js", 33 | "ConversionError", 34 | "destroy_proxies", 35 | ] 36 | pyodide/_run_js.py0000644000000000000000000000073014310731640013221 0ustar rootrootfrom typing import Any 37 | 38 | 39 | def run_js(code: str, /) -> Any: 40 | """ 41 | A wrapper for the JavaScript 'eval' function. 42 | 43 | Runs 'code' as a Javascript code string and returns the result. Unlike 44 | JavaScript's 'eval', if 'code' is not a string we raise a TypeError. 45 | """ 46 | from js import eval 47 | 48 | if not isinstance(code, str): 49 | raise TypeError( 50 | f"argument should have type 'string' not type '{type(code).__name__}'" 51 | ) 52 | return eval(code) 53 | pyodide/_state.py0000644000000000000000000000262414310731640013045 0ustar rootrootimport gc 54 | import sys 55 | from typing import Any 56 | 57 | import __main__ 58 | 59 | from _pyodide._importhook import jsfinder 60 | 61 | from ._core import JsProxy 62 | 63 | 64 | def save_state() -> dict[str, Any]: 65 | """Record the current global state. 66 | 67 | This includes which JavaScript packages are loaded and the global scope in 68 | ``__main__.__dict__``. Many loaded modules might have global state, but 69 | there is no general way to track it and we don't try to. 70 | """ 71 | loaded_js_modules = {} 72 | for [key, value] in sys.modules.items(): 73 | if isinstance(value, JsProxy): 74 | loaded_js_modules[key] = value 75 | 76 | return dict( 77 | globals=dict(__main__.__dict__), 78 | js_modules=dict(jsfinder.jsproxies), 79 | loaded_js_modules=loaded_js_modules, 80 | ) 81 | 82 | 83 | def restore_state(state: dict[str, Any]) -> int: 84 | """Restore the global state to a snapshot. The argument ``state`` should 85 | come from ``save_state``""" 86 | __main__.__dict__.clear() 87 | __main__.__dict__.update(state["globals"]) 88 | 89 | jsfinder.jsproxies = state["js_modules"] 90 | loaded_js_modules = state["loaded_js_modules"] 91 | for [key, value] in list(sys.modules.items()): 92 | if isinstance(value, JsProxy) and key not in loaded_js_modules: 93 | del sys.modules[key] 94 | sys.modules.update(loaded_js_modules) 95 | 96 | sys.last_type = None 97 | sys.last_value = None 98 | sys.last_traceback = None 99 | 100 | return gc.collect(2) 101 | 102 | 103 | __all__ = ["save_state", "restore_state"] 104 | pyodide/code.py0000644000000000000000000000133214310731640012473 0ustar rootrootfrom typing import Any 105 | 106 | from _pyodide._base import ( 107 | CodeRunner, 108 | eval_code, 109 | eval_code_async, 110 | find_imports, 111 | should_quiet, 112 | ) 113 | 114 | 115 | def run_js(code: str, /) -> Any: 116 | """ 117 | A wrapper for the JavaScript 'eval' function. 118 | 119 | Runs 'code' as a Javascript code string and returns the result. Unlike 120 | JavaScript's 'eval', if 'code' is not a string we raise a TypeError. 121 | """ 122 | from js import eval as eval_ 123 | 124 | if not isinstance(code, str): 125 | raise TypeError( 126 | f"argument should have type 'string' not type '{type(code).__name__}'" 127 | ) 128 | return eval_(code) 129 | 130 | 131 | __all__ = [ 132 | "CodeRunner", 133 | "eval_code", 134 | "eval_code_async", 135 | "find_imports", 136 | "should_quiet", 137 | "run_js", 138 | ] 139 | pyodide/console.py0000644000000000000000000004312114310731640013225 0ustar rootrootimport ast 140 | import asyncio 141 | import rlcompleter 142 | import sys 143 | import traceback 144 | from asyncio import Future, ensure_future 145 | from codeop import CommandCompiler, Compile, _features # type: ignore[attr-defined] 146 | from collections.abc import Callable, Generator 147 | from contextlib import ( 148 | ExitStack, 149 | _RedirectStream, 150 | contextmanager, 151 | redirect_stderr, 152 | redirect_stdout, 153 | ) 154 | from platform import python_build, python_version 155 | from tokenize import TokenError 156 | from types import TracebackType 157 | from typing import Any, Literal 158 | 159 | from _pyodide._base import CodeRunner, ReturnMode, should_quiet 160 | 161 | __all__ = ["Console", "PyodideConsole", "BANNER", "repr_shorten", "ConsoleFuture"] 162 | 163 | 164 | BANNER = f""" 165 | Python {python_version()} ({', '.join(python_build())}) on WebAssembly/Emscripten 166 | Type "help", "copyright", "credits" or "license" for more information. 167 | """.strip() 168 | 169 | 170 | class redirect_stdin(_RedirectStream[Any]): 171 | _stream = "stdin" 172 | 173 | 174 | class _WriteStream: 175 | """A utility class so we can specify our own handlers for writes to sdout, stderr""" 176 | 177 | def __init__( 178 | self, write_handler: Callable[[str], Any], name: str | None = None 179 | ) -> None: 180 | self.write_handler = write_handler 181 | self.name = name 182 | 183 | def write(self, text: str) -> None: 184 | self.write_handler(text) 185 | 186 | def flush(self) -> None: 187 | pass 188 | 189 | def isatty(self) -> bool: 190 | return True 191 | 192 | 193 | class _ReadStream: 194 | """A utility class so we can specify our own handler for reading from stdin""" 195 | 196 | def __init__( 197 | self, read_handler: Callable[[int], str], name: str | None = None 198 | ) -> None: 199 | self.read_handler = read_handler 200 | self.name = name 201 | 202 | def readline(self, n: int = -1) -> str: 203 | return self.read_handler(n) 204 | 205 | def flush(self) -> None: 206 | pass 207 | 208 | def isatty(self) -> bool: 209 | return True 210 | 211 | 212 | class _Compile(Compile): 213 | """Compile code with CodeRunner, and remember future imports 214 | 215 | Instances of this class behave much like the built-in compile function, 216 | but if one is used to compile text containing a future statement, it 217 | "remembers" and compiles all subsequent program texts with the statement in 218 | force. It uses CodeRunner instead of the built-in compile. 219 | """ 220 | 221 | def __init__( 222 | self, 223 | *, 224 | return_mode: ReturnMode = "last_expr", 225 | quiet_trailing_semicolon: bool = True, 226 | flags: int = 0x0, 227 | ) -> None: 228 | super().__init__() 229 | self.flags |= flags 230 | self.return_mode = return_mode 231 | self.quiet_trailing_semicolon = quiet_trailing_semicolon 232 | 233 | def __call__(self, source: str, filename: str, symbol: str) -> CodeRunner: # type: ignore[override] 234 | return_mode = self.return_mode 235 | try: 236 | if self.quiet_trailing_semicolon and should_quiet(source): 237 | return_mode = "none" 238 | except (TokenError, SyntaxError): 239 | # Invalid code, let the Python parser throw the error later. 240 | pass 241 | 242 | code_runner = CodeRunner( 243 | source, 244 | mode=symbol, 245 | filename=filename, 246 | return_mode=return_mode, 247 | flags=self.flags, 248 | ).compile() 249 | for feature in _features: 250 | if code_runner.code.co_flags & feature.compiler_flag: 251 | self.flags |= feature.compiler_flag 252 | return code_runner 253 | 254 | 255 | class _CommandCompiler(CommandCompiler): 256 | """Compile code with CodeRunner, and remember future imports, return None if 257 | code is incomplete. 258 | 259 | Instances of this class have __call__ methods identical in signature to 260 | compile; the difference is that if the instance compiles program text 261 | containing a __future__ statement, the instance 'remembers' and compiles all 262 | subsequent program texts with the statement in force. 263 | 264 | If the source is determined to be incomplete, will suppress the SyntaxError 265 | and return ``None``. 266 | """ 267 | 268 | def __init__( 269 | self, 270 | *, 271 | return_mode: ReturnMode = "last_expr", 272 | quiet_trailing_semicolon: bool = True, 273 | flags: int = 0x0, 274 | ) -> None: 275 | self.compiler = _Compile( 276 | return_mode=return_mode, 277 | quiet_trailing_semicolon=quiet_trailing_semicolon, 278 | flags=flags, 279 | ) 280 | 281 | def __call__( # type: ignore[override] 282 | self, source: str, filename: str = "", symbol: str = "single" 283 | ) -> CodeRunner | None: 284 | return super().__call__(source, filename, symbol) # type: ignore[return-value] 285 | 286 | 287 | ConsoleFutureStatus = Literal["incomplete", "syntax-error", "complete"] 288 | INCOMPLETE: ConsoleFutureStatus = "incomplete" 289 | SYNTAX_ERROR: ConsoleFutureStatus = "syntax-error" 290 | COMPLETE: ConsoleFutureStatus = "complete" 291 | 292 | 293 | class ConsoleFuture(Future[Any]): 294 | """A future with extra fields used as the return value for :any:`Console` apis. 295 | 296 | Attributes 297 | ---------- 298 | syntax_check : str 299 | One of ``"incomplete"``, ``"syntax-error"``, or ``"complete"``. If the value is 300 | ``"incomplete"`` then the future has already been resolved with result equal to 301 | ``None``. If the value is ``"syntax-error"``, the ``Future`` has already been 302 | rejected with a ``SyntaxError``. If the value is ``"complete"``, then the input 303 | complete and syntactically correct. 304 | 305 | formatted_error : str 306 | If the ``Future`` is rejected, this will be filled with a formatted version of 307 | the code. This is a convenience that simplifies code and helps to avoid large 308 | memory leaks when using from JavaScript. 309 | 310 | """ 311 | 312 | def __init__( 313 | self, 314 | syntax_check: ConsoleFutureStatus, 315 | ): 316 | super().__init__() 317 | self.syntax_check: ConsoleFutureStatus = syntax_check 318 | self.formatted_error: str | None = None 319 | 320 | 321 | class Console: 322 | """Interactive Pyodide console 323 | 324 | An interactive console based on the Python standard library 325 | `code.InteractiveConsole` that manages stream redirections and asynchronous 326 | execution of the code. 327 | 328 | The stream callbacks can be modified directly as long as 329 | `persistent_stream_redirection` isn't in effect. 330 | 331 | Parameters 332 | ---------- 333 | globals : ``dict`` 334 | The global namespace in which to evaluate the code. Defaults to a new empty dictionary. 335 | 336 | stdin_callback : ``Callable[[int], str]`` 337 | Function to call at each read from ``sys.stdin``. Defaults to ``None``. 338 | 339 | stdout_callback : ``Callable[[str], None]`` 340 | Function to call at each write to ``sys.stdout``. Defaults to ``None``. 341 | 342 | stderr_callback : ``Callable[[str], None]`` 343 | Function to call at each write to ``sys.stderr``. Defaults to ``None``. 344 | 345 | persistent_stream_redirection : ``bool`` 346 | Should redirection of standard streams be kept between calls to :any:`runcode `? 347 | Defaults to ``False``. 348 | 349 | filename : ``str`` 350 | The file name to report in error messages. Defaults to ````. 351 | 352 | Attributes 353 | ---------- 354 | globals : ``Dict[str, Any]`` 355 | The namespace used as the global 356 | 357 | stdin_callback : ``Callback[[], str]`` 358 | Function to call at each read from ``sys.stdin``. 359 | 360 | stdout_callback : ``Callback[[str], None]`` 361 | Function to call at each write to ``sys.stdout``. 362 | 363 | stderr_callback : ``Callback[[str], None]`` 364 | Function to call at each write to ``sys.stderr``. 365 | 366 | buffer : ``List[str]`` 367 | The list of strings that have been :any:`pushed ` to the console. 368 | 369 | completer_word_break_characters : ``str`` 370 | The set of characters considered by :any:`complete ` to be word breaks. 371 | """ 372 | 373 | def __init__( 374 | self, 375 | globals: dict[str, Any] | None = None, 376 | *, 377 | stdin_callback: Callable[[int], str] | None = None, 378 | stdout_callback: Callable[[str], None] | None = None, 379 | stderr_callback: Callable[[str], None] | None = None, 380 | persistent_stream_redirection: bool = False, 381 | filename: str = "", 382 | ) -> None: 383 | if globals is None: 384 | globals = {"__name__": "__console__", "__doc__": None} 385 | self.globals = globals 386 | self._stdout = None 387 | self._stderr = None 388 | self.stdin_callback = stdin_callback 389 | self.stdout_callback = stdout_callback 390 | self.stderr_callback = stderr_callback 391 | self.filename = filename 392 | self.buffer: list[str] = [] 393 | self._lock = asyncio.Lock() 394 | self._streams_redirected = False 395 | self._stream_generator: Generator[ 396 | None, None, None 397 | ] | None = None # track persistent stream redirection 398 | if persistent_stream_redirection: 399 | self.persistent_redirect_streams() 400 | self._completer = rlcompleter.Completer(self.globals) 401 | # all nonalphanums except '.' 402 | # see https://github.com/python/cpython/blob/a4258e8cd776ba655cc54ba54eaeffeddb0a267c/Modules/readline.c#L1211 403 | self.completer_word_break_characters = ( 404 | """ \t\n`~!@#$%^&*()-=+[{]}\\|;:'\",<>/?""" 405 | ) 406 | self._compile = _CommandCompiler(flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT) 407 | 408 | def persistent_redirect_streams(self) -> None: 409 | """Redirect stdin/stdout/stderr persistently""" 410 | if self._stream_generator: 411 | return 412 | self._stream_generator = self._stdstreams_redirections_inner() 413 | assert self._stream_generator is not None 414 | next(self._stream_generator) # trigger stream redirection 415 | # streams will be reverted to normal when self._stream_generator is destroyed. 416 | 417 | def persistent_restore_streams(self) -> None: 418 | """Restore stdin/stdout/stderr if they have been persistently redirected""" 419 | # allowing _stream_generator to be garbage collected restores the streams 420 | self._stream_generator = None 421 | 422 | @contextmanager 423 | def redirect_streams(self) -> Generator[None, None, None]: 424 | """A context manager to redirect standard streams. 425 | 426 | This supports nesting.""" 427 | yield from self._stdstreams_redirections_inner() 428 | 429 | def _stdstreams_redirections_inner(self) -> Generator[None, None, None]: 430 | """This is the generator which implements redirect_streams and the stdstreams_redirections""" 431 | # already redirected? 432 | if self._streams_redirected: 433 | yield 434 | return 435 | redirects: list[Any] = [] 436 | if self.stdin_callback: 437 | stdin_name = getattr(sys.stdin, "name", "") 438 | stdin_stream = _ReadStream(self.stdin_callback, name=stdin_name) 439 | redirects.append(redirect_stdin(stdin_stream)) 440 | if self.stdout_callback: 441 | stdout_name = getattr(sys.stdout, "name", "") 442 | stdout_stream = _WriteStream(self.stdout_callback, name=stdout_name) 443 | redirects.append(redirect_stdout(stdout_stream)) 444 | if self.stderr_callback: 445 | stderr_name = getattr(sys.stderr, "name", "") 446 | stderr_stream = _WriteStream(self.stderr_callback, name=stderr_name) 447 | redirects.append(redirect_stderr(stderr_stream)) 448 | try: 449 | self._streams_redirected = True 450 | with ExitStack() as stack: 451 | for redirect in redirects: 452 | stack.enter_context(redirect) 453 | yield 454 | finally: 455 | self._streams_redirected = False 456 | 457 | def runsource(self, source: str, filename: str = "") -> ConsoleFuture: 458 | """Compile and run source code in the interpreter. 459 | 460 | Returns 461 | ------- 462 | :any:`ConsoleFuture` 463 | 464 | """ 465 | res: ConsoleFuture | None 466 | 467 | try: 468 | code = self._compile(source, filename, "single") 469 | except (OverflowError, SyntaxError, ValueError) as e: 470 | # Case 1 471 | if e.__traceback__: 472 | traceback.clear_frames(e.__traceback__) 473 | res = ConsoleFuture(SYNTAX_ERROR) 474 | res.set_exception(e) 475 | res.formatted_error = self.formatsyntaxerror(e) 476 | return res 477 | 478 | if code is None: 479 | res = ConsoleFuture(INCOMPLETE) 480 | res.set_result(None) 481 | return res 482 | 483 | res = ConsoleFuture(COMPLETE) 484 | 485 | def done_cb(fut: asyncio.Task[Any]) -> None: 486 | nonlocal res 487 | assert res is not None 488 | exc = fut.exception() 489 | if exc: 490 | res.formatted_error = self.formattraceback(exc) 491 | res.set_exception(exc) 492 | exc = None 493 | else: 494 | res.set_result(fut.result()) 495 | res = None 496 | 497 | ensure_future(self.runcode(source, code)).add_done_callback(done_cb) 498 | return res 499 | 500 | async def runcode(self, source: str, code: CodeRunner) -> Any: 501 | """Execute a code object and return the result.""" 502 | async with self._lock: 503 | with self.redirect_streams(): 504 | try: 505 | return await code.run_async(self.globals) 506 | finally: 507 | sys.stdout.flush() 508 | sys.stderr.flush() 509 | 510 | def formatsyntaxerror(self, e: Exception) -> str: 511 | """Format the syntax error that just occurred. 512 | 513 | This doesn't include a stack trace because there isn't one. The actual 514 | error object is stored into `sys.last_value`. 515 | """ 516 | sys.last_type = type(e) 517 | sys.last_value = e 518 | sys.last_traceback = None 519 | return "".join(traceback.format_exception_only(type(e), e)) 520 | 521 | def num_frames_to_keep(self, tb: TracebackType | None) -> int: 522 | keep_frames = False 523 | kept_frames = 0 524 | # Try to trim out stack frames inside our code 525 | for (frame, _) in traceback.walk_tb(tb): 526 | keep_frames = keep_frames or frame.f_code.co_filename == "" 527 | keep_frames = keep_frames or frame.f_code.co_filename == "" 528 | if keep_frames: 529 | kept_frames += 1 530 | return kept_frames 531 | 532 | def formattraceback(self, e: BaseException) -> str: 533 | """Format the exception that just occurred. 534 | 535 | The actual error object is stored into `sys.last_value`. 536 | """ 537 | sys.last_type = type(e) 538 | sys.last_value = e 539 | sys.last_traceback = e.__traceback__ 540 | nframes = self.num_frames_to_keep(e.__traceback__) 541 | return "".join( 542 | traceback.format_exception(type(e), e, e.__traceback__, -nframes) 543 | ) 544 | 545 | def push(self, line: str) -> ConsoleFuture: 546 | """Push a line to the interpreter. 547 | 548 | The line should not have a trailing newline; it may have internal 549 | newlines. The line is appended to a buffer and the interpreter's 550 | runsource() method is called with the concatenated contents of the 551 | buffer as source. If this indicates that the command was executed or 552 | invalid, the buffer is reset; otherwise, the command is incomplete, and 553 | the buffer is left as it was after the line was appended. 554 | 555 | The return value is the result of calling :any:`Console.runsource` on the current buffer 556 | contents. 557 | """ 558 | self.buffer.append(line) 559 | source = "\n".join(self.buffer) 560 | result = self.runsource(source, self.filename) 561 | if result.syntax_check != INCOMPLETE: 562 | self.buffer = [] 563 | return result 564 | 565 | def complete(self, source: str) -> tuple[list[str], int]: 566 | """Use Python's rlcompleter to complete the source string using the :any:`globals ` namespace. 567 | 568 | Finds last "word" in the source string and completes it with rlcompleter. Word 569 | breaks are determined by the set of characters in 570 | :any:`completer_word_break_characters `. 571 | 572 | Parameters 573 | ---------- 574 | source : str 575 | The source string to complete at the end. 576 | 577 | Returns 578 | ------- 579 | completions : List[str] 580 | A list of completion strings. 581 | start : int 582 | The index where completion starts. 583 | 584 | Examples 585 | -------- 586 | >>> shell = Console() 587 | >>> shell.complete("str.isa") 588 | (['str.isalnum(', 'str.isalpha(', 'str.isascii('], 0) 589 | >>> shell.complete("a = 5 ; str.isa") 590 | (['str.isalnum(', 'str.isalpha(', 'str.isascii('], 8) 591 | """ 592 | start = max(map(source.rfind, self.completer_word_break_characters)) + 1 593 | source = source[start:] 594 | if "." in source: 595 | completions = self._completer.attr_matches(source) 596 | else: 597 | completions = self._completer.global_matches(source) 598 | return completions, start 599 | 600 | 601 | class PyodideConsole(Console): 602 | """A subclass of :any:`Console` that uses :any:`pyodide.loadPackagesFromImports` before running the code.""" 603 | 604 | async def runcode(self, source: str, code: CodeRunner) -> ConsoleFuture: 605 | """Execute a code object. 606 | All exceptions are caught except SystemExit, which is reraised. 607 | Returns 608 | ------- 609 | The return value is a dependent sum type with the following possibilities: 610 | * `("success", result : Any)` -- the code executed successfully 611 | * `("exception", message : str)` -- An exception occurred. `message` is the 612 | result of calling :any:`Console.formattraceback`. 613 | """ 614 | from pyodide_js import loadPackagesFromImports 615 | 616 | await loadPackagesFromImports(source) 617 | return await super().runcode(source, code) 618 | 619 | 620 | def repr_shorten( 621 | value: Any, limit: int = 1000, split: int | None = None, separator: str = "..." 622 | ) -> str: 623 | """Compute the string representation of ``value`` and shorten it 624 | if necessary. 625 | 626 | If it is longer than ``limit`` then return the firsts ``split`` 627 | characters and the last ``split`` characters separated by '...'. 628 | Default value for ``split`` is `limit // 2`. 629 | """ 630 | if split is None: 631 | split = limit // 2 632 | text = repr(value) 633 | if len(text) > limit: 634 | text = f"{text[:split]}{separator}{text[-split:]}" 635 | return text 636 | pyodide/ffi/0000755000000000000000000000000014310733215011754 5ustar rootrootpyodide/ffi/__init__.py0000644000000000000000000000072614310731640014072 0ustar rootrootfrom _pyodide._importhook import register_js_module, unregister_js_module 637 | 638 | from .._core import ( 639 | IN_BROWSER, 640 | ConversionError, 641 | JsException, 642 | JsProxy, 643 | create_once_callable, 644 | create_proxy, 645 | destroy_proxies, 646 | to_js, 647 | ) 648 | 649 | __all__ = [ 650 | "IN_BROWSER", 651 | "ConversionError", 652 | "JsException", 653 | "JsProxy", 654 | "create_once_callable", 655 | "create_proxy", 656 | "destroy_proxies", 657 | "to_js", 658 | "register_js_module", 659 | "unregister_js_module", 660 | ] 661 | pyodide/ffi/wrappers.py0000644000000000000000000000646314310731640014202 0ustar rootrootfrom collections.abc import Callable 662 | from typing import Any 663 | 664 | from .._core import IN_BROWSER, JsProxy, create_once_callable, create_proxy 665 | 666 | if IN_BROWSER: 667 | from js import clearInterval, clearTimeout, setInterval, setTimeout 668 | 669 | 670 | class Destroyable: 671 | def destroy(self): 672 | pass 673 | 674 | 675 | EVENT_LISTENERS: dict[tuple[int, str, Callable[[Any], None]], JsProxy] = {} 676 | 677 | 678 | def add_event_listener( 679 | elt: JsProxy, event: str, listener: Callable[[Any], None] 680 | ) -> None: 681 | """Wrapper for JavaScript's addEventListener() which automatically manages the lifetime 682 | of a JsProxy corresponding to the listener param. 683 | """ 684 | proxy = create_proxy(listener) 685 | EVENT_LISTENERS[(elt.js_id, event, listener)] = proxy 686 | elt.addEventListener(event, proxy) 687 | 688 | 689 | def remove_event_listener( 690 | elt: JsProxy, event: str, listener: Callable[[Any], None] 691 | ) -> None: 692 | """Wrapper for JavaScript's removeEventListener() which automatically manages the lifetime 693 | of a JsProxy corresponding to the listener param. 694 | """ 695 | proxy = EVENT_LISTENERS.pop((elt.js_id, event, listener)) 696 | elt.removeEventListener(event, proxy) 697 | proxy.destroy() 698 | 699 | 700 | TIMEOUTS: dict[int, Destroyable] = {} 701 | 702 | 703 | def set_timeout(callback: Callable[[], None], timeout: int) -> int | JsProxy: 704 | """Wrapper for JavaScript's setTimeout() which automatically manages the lifetime 705 | of a JsProxy corresponding to the callback param. 706 | """ 707 | id = -1 708 | 709 | def wrapper(): 710 | nonlocal id 711 | callback() 712 | TIMEOUTS.pop(id, None) 713 | 714 | callable = create_once_callable(wrapper) 715 | timeout_retval: int | JsProxy = setTimeout(callable, timeout) 716 | id = timeout_retval if isinstance(timeout_retval, int) else timeout_retval.js_id 717 | TIMEOUTS[id] = callable 718 | return timeout_retval 719 | 720 | 721 | # An object with a no-op destroy method so we can do 722 | # 723 | # TIMEOUTS.pop(id, DUMMY_DESTROYABLE).destroy() 724 | # 725 | # and either it gets a real object and calls the real destroy method or it gets 726 | # the fake which does nothing. This is to handle the case where clear_timeout is 727 | # called after the timeout executes. 728 | DUMMY_DESTROYABLE = Destroyable() 729 | 730 | 731 | def clear_timeout(timeout_retval: int | JsProxy) -> None: 732 | """Wrapper for JavaScript's clearTimeout() which automatically manages the lifetime 733 | of a JsProxy corresponding to the callback param. 734 | """ 735 | clearTimeout(timeout_retval) 736 | id = timeout_retval if isinstance(timeout_retval, int) else timeout_retval.js_id 737 | TIMEOUTS.pop(id, DUMMY_DESTROYABLE).destroy() 738 | 739 | 740 | INTERVAL_CALLBACKS: dict[int, Destroyable] = {} 741 | 742 | 743 | def set_interval(callback: Callable[[], None], interval: int) -> int | JsProxy: 744 | """Wrapper for JavaScript's setInterval() which automatically manages the lifetime 745 | of a JsProxy corresponding to the callback param. 746 | """ 747 | proxy = create_proxy(callback) 748 | interval_retval = setInterval(proxy, interval) 749 | id = interval_retval if isinstance(interval_retval, int) else interval_retval.js_id 750 | INTERVAL_CALLBACKS[id] = proxy 751 | return interval_retval 752 | 753 | 754 | def clear_interval(interval_retval: int | JsProxy) -> None: 755 | """Wrapper for JavaScript's clearInterval() which automatically manages the lifetime 756 | of a JsProxy corresponding to the callback param. 757 | """ 758 | clearInterval(interval_retval) 759 | id = interval_retval if isinstance(interval_retval, int) else interval_retval.js_id 760 | INTERVAL_CALLBACKS.pop(id, DUMMY_DESTROYABLE).destroy() 761 | pyodide/http.py0000644000000000000000000001635014310731640012546 0ustar rootrootimport json 762 | from io import StringIO 763 | from typing import Any, BinaryIO, TextIO 764 | 765 | from ._core import JsProxy, to_js 766 | 767 | try: 768 | from js import XMLHttpRequest 769 | except ImportError: 770 | pass 771 | 772 | from ._core import IN_BROWSER, JsException 773 | from ._package_loader import unpack_buffer 774 | 775 | __all__ = [ 776 | "open_url", 777 | "pyfetch", 778 | "FetchResponse", 779 | ] 780 | 781 | 782 | def open_url(url: str) -> StringIO: 783 | """Fetches a given URL synchronously. 784 | 785 | The download of binary files is not supported. To download binary 786 | files use :func:`pyodide.http.pyfetch` which is asynchronous. 787 | 788 | Parameters 789 | ---------- 790 | url : str 791 | URL to fetch 792 | 793 | Returns 794 | ------- 795 | io.StringIO 796 | the contents of the URL. 797 | """ 798 | 799 | req = XMLHttpRequest.new() 800 | req.open("GET", url, False) 801 | req.send(None) 802 | return StringIO(req.response) 803 | 804 | 805 | class FetchResponse: 806 | """A wrapper for a Javascript fetch response. 807 | 808 | See also the Javascript fetch 809 | `Response `_ api 810 | docs. 811 | 812 | Parameters 813 | ---------- 814 | url 815 | URL to fetch 816 | js_response 817 | A JsProxy of the fetch response 818 | """ 819 | 820 | def __init__(self, url: str, js_response: JsProxy): 821 | self._url = url 822 | self.js_response = js_response 823 | 824 | @property 825 | def body_used(self) -> bool: 826 | """Has the response been used yet? 827 | 828 | (If so, attempting to retrieve the body again will raise an OSError.) 829 | """ 830 | return self.js_response.bodyUsed 831 | 832 | @property 833 | def ok(self) -> bool: 834 | """Was the request successful?""" 835 | return self.js_response.ok 836 | 837 | @property 838 | def redirected(self) -> bool: 839 | """Was the request redirected?""" 840 | return self.js_response.redirected 841 | 842 | @property 843 | def status(self) -> str: 844 | """Response status code""" 845 | return self.js_response.status 846 | 847 | @property 848 | def status_text(self) -> str: 849 | """Response status text""" 850 | return self.js_response.statusText 851 | 852 | @property 853 | def type(self) -> str: 854 | """The `type `_ of the response.""" 855 | return self.js_response.type 856 | 857 | @property 858 | def url(self) -> str: 859 | """The `url `_ of the response. 860 | 861 | It may be different than the url passed to fetch. 862 | """ 863 | return self.js_response.url 864 | 865 | def _raise_if_failed(self) -> None: 866 | if self.js_response.status >= 400: 867 | raise OSError( 868 | f"Request for {self._url} failed with status {self.status}: {self.status_text}" 869 | ) 870 | if self.js_response.bodyUsed: 871 | raise OSError("Response body is already used") 872 | 873 | def clone(self) -> "FetchResponse": 874 | """Return an identical copy of the FetchResponse. 875 | 876 | This method exists to allow multiple uses of response objects. See 877 | `Response.clone `_ 878 | """ 879 | if self.js_response.bodyUsed: 880 | raise OSError("Response body is already used") 881 | return FetchResponse(self._url, self.js_response.clone()) 882 | 883 | async def buffer(self) -> JsProxy: 884 | """Return the response body as a Javascript ArrayBuffer""" 885 | self._raise_if_failed() 886 | return await self.js_response.arrayBuffer() 887 | 888 | async def string(self) -> str: 889 | """Return the response body as a string""" 890 | self._raise_if_failed() 891 | return await self.js_response.text() 892 | 893 | async def json(self, **kwargs: Any) -> Any: 894 | """Return the response body as a Javascript JSON object. 895 | 896 | Any keyword arguments are passed to `json.loads 897 | `_. 898 | """ 899 | self._raise_if_failed() 900 | return json.loads(await self.string(), **kwargs) 901 | 902 | async def memoryview(self) -> memoryview: 903 | """Return the response body as a memoryview object""" 904 | self._raise_if_failed() 905 | return (await self.buffer()).to_memoryview() 906 | 907 | async def bytes(self) -> bytes: 908 | """Return the response body as a bytes object""" 909 | self._raise_if_failed() 910 | return (await self.buffer()).to_bytes() 911 | 912 | async def _into_file(self, f: TextIO | BinaryIO) -> None: 913 | """Write the data into an empty file with no copy. 914 | 915 | Warning: should only be used when f is an empty file, otherwise it may 916 | overwrite the data of f. 917 | """ 918 | buf = await self.buffer() 919 | buf._into_file(f) 920 | 921 | async def _create_file(self, path: str) -> None: 922 | """Uses the data to back a new file without copying it. 923 | 924 | This method avoids copying the data when creating a new file. If you 925 | want to write the data into an existing file, use 926 | 927 | .. code-block:: python 928 | 929 | buf = await resp.buffer() 930 | buf.to_file(file) 931 | 932 | Parameters 933 | ---------- 934 | path : str 935 | 936 | The path to the file to create. The file should not exist but 937 | it should be in a directory that exists. Otherwise, will raise 938 | an ``OSError`` 939 | """ 940 | with open(path, "x") as f: 941 | await self._into_file(f) 942 | 943 | async def unpack_archive( 944 | self, *, extract_dir: str | None = None, format: str | None = None 945 | ) -> None: 946 | """Treat the data as an archive and unpack it into target directory. 947 | 948 | Assumes that the file is an archive in a format that shutil has an 949 | unpacker for. The arguments extract_dir and format are passed directly 950 | on to ``shutil.unpack_archive``. 951 | 952 | Parameters 953 | ---------- 954 | extract_dir : str 955 | Directory to extract the archive into. If not 956 | provided, the current working directory is used. 957 | 958 | format : str 959 | The archive format: one of “zip”, “tar”, “gztar”, “bztar”. 960 | Or any other format registered with ``shutil.register_unpack_format()``. If not 961 | provided, ``unpack_archive()`` will use the archive file name extension 962 | and see if an unpacker was registered for that extension. In case 963 | none is found, a ``ValueError`` is raised. 964 | """ 965 | buf = await self.buffer() 966 | filename = self._url.rsplit("/", -1)[-1] 967 | unpack_buffer(buf, filename=filename, format=format, extract_dir=extract_dir) 968 | 969 | 970 | async def pyfetch(url: str, **kwargs: Any) -> FetchResponse: 971 | r"""Fetch the url and return the response. 972 | 973 | This functions provides a similar API to the JavaScript `fetch function 974 | `_ however it is 975 | designed to be convenient to use from Python. The 976 | :class:`pyodide.http.FetchResponse` has methods with the output types 977 | already converted to Python objects. 978 | 979 | Parameters 980 | ---------- 981 | url : str 982 | URL to fetch. 983 | 984 | \*\*kwargs : Any 985 | keyword arguments are passed along as `optional parameters to the fetch API 986 | `_. 987 | """ 988 | if IN_BROWSER: 989 | from js import Object 990 | from js import fetch as _jsfetch 991 | 992 | try: 993 | return FetchResponse( 994 | url, await _jsfetch(url, to_js(kwargs, dict_converter=Object.fromEntries)) 995 | ) 996 | except JsException as e: 997 | raise OSError(e.js_error.message) from None 998 | pyodide/__init__.py0000644000000000000000000000513114310731640013321 0ustar rootroot# When the pyodide package is imported, both the js and the pyodide_js modules 999 | # will be available to import from. Not all functions in pyodide_js will work 1000 | # until after pyodide is first imported, imported functions from pyodide_js 1001 | # should not be used at import time. It is fine to use js functions at import 1002 | # time. 1003 | # 1004 | # All pure Python code that does not require js or pyodide_js should go in 1005 | # the _pyodide package. 1006 | # 1007 | # This package is imported by the test suite as well, and currently we don't use 1008 | # pytest mocks for js or pyodide_js, so make sure to test "if IN_BROWSER" before 1009 | # importing from these. 1010 | __version__ = "0.21.3" 1011 | 1012 | __all__ = ["__version__"] 1013 | 1014 | from typing import Any 1015 | 1016 | from . import _state # noqa: F401 1017 | from .code import CodeRunner # noqa: F401 1018 | from .code import eval_code # noqa: F401 1019 | from .code import eval_code_async # noqa: F401 1020 | from .code import find_imports # noqa: F401 1021 | from .code import should_quiet # noqa: F401 1022 | from .ffi import ConversionError # noqa: F401 1023 | from .ffi import JsException # noqa: F401 1024 | from .ffi import JsProxy # noqa: F401 1025 | from .ffi import create_once_callable # noqa: F401 1026 | from .ffi import create_proxy # noqa: F401 1027 | from .ffi import destroy_proxies # noqa: F401 1028 | from .ffi import register_js_module # noqa: F401 1029 | from .ffi import to_js # noqa: F401 1030 | from .ffi import unregister_js_module # noqa: F401 1031 | from .http import open_url # noqa: F401 1032 | 1033 | DEPRECATED_LIST = { 1034 | "CodeRunner": "code", 1035 | "eval_code": "code", 1036 | "eval_code_async": "code", 1037 | "find_imports": "code", 1038 | "should_quiet": "code", 1039 | "open_url": "http", 1040 | "ConversionError": "ffi", 1041 | "JsException": "ffi", 1042 | "JsProxy": "ffi", 1043 | "create_once_callable": "ffi", 1044 | "create_proxy": "ffi", 1045 | "destroy_proxies": "ffi", 1046 | "to_js": "ffi", 1047 | "register_js_module": "ffi", 1048 | "unregister_js_module": "ffi", 1049 | } 1050 | 1051 | 1052 | from .webloop import _initialize_event_loop 1053 | 1054 | _initialize_event_loop() 1055 | del _initialize_event_loop 1056 | 1057 | 1058 | def __dir__() -> list[str]: 1059 | return __all__ 1060 | 1061 | 1062 | for name in DEPRECATED_LIST: 1063 | globals()[f"_deprecated_{name}"] = globals()[name] 1064 | del globals()[name] 1065 | 1066 | 1067 | def __getattr__(name: str) -> Any: 1068 | if name in DEPRECATED_LIST: 1069 | from warnings import warn 1070 | 1071 | warn( 1072 | f"pyodide.{name} has been moved to pyodide.{DEPRECATED_LIST[name]}.{name} " 1073 | "Accessing it through the pyodide module is deprecated.", 1074 | FutureWarning, 1075 | ) 1076 | # Put the name back so we won't warn next time this name is accessed 1077 | globals()[name] = globals()[f"_deprecated_{name}"] 1078 | return globals()[name] 1079 | raise AttributeError(f"module {__name__!r} has no attribute {name!r}") 1080 | pyodide/_package_loader.py0000644000000000000000000002433614310731640014652 0ustar rootrootimport base64 1081 | import binascii 1082 | import re 1083 | import shutil 1084 | import sysconfig 1085 | import tarfile 1086 | from collections.abc import Iterable 1087 | from importlib.machinery import EXTENSION_SUFFIXES 1088 | from pathlib import Path 1089 | from site import getsitepackages 1090 | from tempfile import NamedTemporaryFile 1091 | from typing import IO, Any, Literal 1092 | from zipfile import ZipFile 1093 | 1094 | from ._core import IN_BROWSER, JsProxy, to_js 1095 | 1096 | SITE_PACKAGES = Path(getsitepackages()[0]) 1097 | STD_LIB = Path(sysconfig.get_path("stdlib")) 1098 | TARGETS = {"site": SITE_PACKAGES, "lib": STD_LIB, "dynlib": Path("/usr/lib")} 1099 | ZIP_TYPES = {".whl", ".zip"} 1100 | TAR_TYPES = {".tar", ".gz", ".bz", ".gz", ".tgz", ".bz2", ".tbz2"} 1101 | EXTENSION_TAGS = [suffix.removesuffix(".so") for suffix in EXTENSION_SUFFIXES] 1102 | # See PEP 3149. I think the situation has since been updated since PEP 3149 does 1103 | # not talk about platform triples. But I could not find any newer pep discussing 1104 | # shared library names. 1105 | # 1106 | # There are other interpreters but it's better to have false negatives than 1107 | # false positives. 1108 | PLATFORM_TAG_REGEX = re.compile( 1109 | r"\.(cpython|pypy|jython)-[0-9]{2,}[a-z]*(-[a-z0-9_-]*)?" 1110 | ) 1111 | 1112 | 1113 | def parse_wheel_name(filename: str) -> tuple[str, str, str, str, str]: 1114 | tokens = filename.split("-") 1115 | # TODO: support optional build tags in the filename (cf PEP 427) 1116 | if len(tokens) < 5: 1117 | raise ValueError(f"{filename} is not a valid wheel file name.") 1118 | version, python_tag, abi_tag, platform = tokens[-4:] 1119 | name = "-".join(tokens[:-4]) 1120 | return name, version, python_tag, abi_tag, platform 1121 | 1122 | 1123 | # Vendored from packaging 1124 | _canonicalize_regex = re.compile(r"[-_.]+") 1125 | 1126 | 1127 | def canonicalize_name(name: str) -> str: 1128 | # This is taken from PEP 503. 1129 | return _canonicalize_regex.sub("-", name).lower() 1130 | 1131 | 1132 | # Vendored from pip 1133 | class UnsupportedWheel(Exception): 1134 | """Unsupported wheel.""" 1135 | 1136 | 1137 | def wheel_dist_info_dir(source: ZipFile, name: str) -> str: 1138 | """Returns the name of the contained .dist-info directory. 1139 | 1140 | Raises UnsupportedWheel if not found, >1 found, or it doesn't match the 1141 | provided name. 1142 | """ 1143 | # Zip file path separators must be / 1144 | subdirs = {p.split("/", 1)[0] for p in source.namelist()} 1145 | 1146 | info_dirs = [s for s in subdirs if s.endswith(".dist-info")] 1147 | 1148 | if not info_dirs: 1149 | raise UnsupportedWheel(f".dist-info directory not found in wheel {name!r}") 1150 | 1151 | if len(info_dirs) > 1: 1152 | raise UnsupportedWheel( 1153 | "multiple .dist-info directories found in wheel {!r}: {}".format( 1154 | name, ", ".join(info_dirs) 1155 | ) 1156 | ) 1157 | 1158 | info_dir = info_dirs[0] 1159 | 1160 | info_dir_name = canonicalize_name(info_dir) 1161 | canonical_name = canonicalize_name(name) 1162 | if not info_dir_name.startswith(canonical_name): 1163 | raise UnsupportedWheel( 1164 | ".dist-info directory {!r} does not start with {!r}".format( 1165 | info_dir, canonical_name 1166 | ) 1167 | ) 1168 | 1169 | return info_dir 1170 | 1171 | 1172 | def make_whlfile( 1173 | *args: Any, owner: int | None = None, group: int | None = None, **kwargs: Any 1174 | ) -> str: 1175 | return shutil._make_zipfile(*args, **kwargs) # type: ignore[attr-defined] 1176 | 1177 | 1178 | if IN_BROWSER: 1179 | shutil.register_archive_format("whl", make_whlfile, description="Wheel file") 1180 | shutil.register_unpack_format( 1181 | "whl", [".whl", ".wheel"], shutil._unpack_zipfile, description="Wheel file" # type: ignore[attr-defined] 1182 | ) 1183 | 1184 | 1185 | def get_format(format: str) -> str: 1186 | for (fmt, extensions, _) in shutil.get_unpack_formats(): 1187 | if format == fmt: 1188 | return fmt 1189 | if format in extensions: 1190 | return fmt 1191 | if "." + format in extensions: 1192 | return fmt 1193 | raise ValueError(f"Unrecognized format {format}") 1194 | 1195 | 1196 | def unpack_buffer( 1197 | buffer: JsProxy, 1198 | *, 1199 | filename: str = "", 1200 | format: str | None = None, 1201 | target: Literal["site", "lib", "dynlib"] | None = None, 1202 | extract_dir: str | None = None, 1203 | calculate_dynlibs: bool = False, 1204 | installer: str | None = None, 1205 | source: str | None = None, 1206 | ) -> JsProxy | None: 1207 | """Used to install a package either into sitepackages or into the standard 1208 | library. 1209 | 1210 | This is a helper method called from ``loadPackage``. 1211 | 1212 | Parameters 1213 | ---------- 1214 | buffer 1215 | A Javascript ``Uint8Array`` with the binary data for the archive. 1216 | 1217 | filename 1218 | The name of the file we are extracting. We only care about it to figure 1219 | out whether the buffer represents a tar file or a zip file. Ignored if 1220 | format argument is present. 1221 | 1222 | format 1223 | Controls the format that we assume the archive has. Overrides the file 1224 | extension of filename. In particular we decide the file format as 1225 | follows: 1226 | 1227 | 1. If format is present, we use that. 1228 | 2. If file name is present, it should have an extension, like a.zip, 1229 | a.tar, etc. Then we use that. 1230 | 3. If neither is present or the file name has no extension, we throw an 1231 | error. 1232 | 1233 | 1234 | extract_dir 1235 | Controls which directory the file is unpacked into. Default is the 1236 | working directory. Mutually exclusive with target. 1237 | 1238 | target 1239 | Controls which directory the file is unpacked into. Either "site" which 1240 | unpacked the file into the sitepackages directory or "lib" which 1241 | unpacked the file into the standard library. Mutually exclusive with 1242 | extract_dir. 1243 | 1244 | calculate_dynlibs 1245 | If true, will return a Javascript Array of paths to dynamic libraries 1246 | ('.so' files) that were in the archive. We need to precompile these Wasm 1247 | binaries in `load-pyodide.js`. These paths point to the unpacked 1248 | locations of the .so files. 1249 | 1250 | Returns 1251 | ------- 1252 | If calculate_dynlibs is True, a Javascript Array of dynamic libraries. 1253 | Otherwise, return None. 1254 | 1255 | """ 1256 | if format: 1257 | format = get_format(format) 1258 | if target and extract_dir: 1259 | raise ValueError("Cannot provide both 'target' and 'extract_dir'") 1260 | if not filename and format is None: 1261 | raise ValueError("At least one of filename and format must be provided") 1262 | if target: 1263 | extract_path = TARGETS[target] 1264 | elif extract_dir: 1265 | extract_path = Path(extract_dir) 1266 | else: 1267 | extract_path = Path(".") 1268 | filename = filename.rpartition("/")[-1] 1269 | with NamedTemporaryFile(suffix=filename) as f: 1270 | buffer._into_file(f) 1271 | shutil.unpack_archive(f.name, extract_path, format) 1272 | suffix = Path(filename).suffix 1273 | if suffix == ".whl": 1274 | set_wheel_installer(filename, f, extract_path, installer, source) 1275 | if calculate_dynlibs: 1276 | suffix = Path(f.name).suffix 1277 | return to_js(get_dynlibs(f, suffix, extract_path)) 1278 | else: 1279 | return None 1280 | 1281 | 1282 | def should_load_dynlib(path: str) -> bool: 1283 | suffixes = Path(path).suffixes 1284 | if not suffixes: 1285 | return False 1286 | if suffixes[-1] != ".so": 1287 | return False 1288 | if len(suffixes) == 1: 1289 | return True 1290 | tag = suffixes[-2] 1291 | if tag in EXTENSION_TAGS: 1292 | return True 1293 | # Okay probably it's not compatible now. But it might be an unrelated .so 1294 | # file with a name with an extra dot: `some.name.so` vs 1295 | # `some.cpython-39-x86_64-linux-gnu.so` Let's make a best effort here to 1296 | # check. 1297 | return not PLATFORM_TAG_REGEX.match(tag) 1298 | 1299 | 1300 | def set_wheel_installer( 1301 | filename: str, 1302 | archive: IO[bytes], 1303 | target_dir: Path, 1304 | installer: str | None, 1305 | source: str | None, 1306 | ) -> None: 1307 | """Record the installer and source of a wheel into the `dist-info` 1308 | directory. 1309 | 1310 | We put the installer into an INSTALLER file according to the packaging spec: 1311 | packaging.python.org/en/latest/specifications/recording-installed-packages/#the-dist-info-directory 1312 | 1313 | We put the source into PYODIDE_SORUCE. 1314 | 1315 | The packaging spec allows us to make custom files. It also allows wheels to 1316 | include custom files in their .dist-info directory. The spec has no attempt 1317 | to coordinate these so that installers don't trample files that wheels 1318 | include. We make a best effort with our PYODIDE prefix. 1319 | 1320 | Parameters 1321 | ---------- 1322 | filename 1323 | The file name of the wheel. 1324 | 1325 | archive 1326 | A binary representation of a wheel archive 1327 | 1328 | target_dir 1329 | The directory the wheel is being installed into. Probably site-packages. 1330 | 1331 | installer 1332 | The name of the installer. Currently either `pyodide.unpackArchive`, 1333 | `pyodide.loadPackage` or `micropip`. 1334 | 1335 | source 1336 | Where did the package come from? Either a url, `pyodide`, or `PyPI`. 1337 | """ 1338 | z = ZipFile(archive) 1339 | wheel_name = parse_wheel_name(filename)[0] 1340 | dist_info_name = wheel_dist_info_dir(z, wheel_name) 1341 | dist_info = target_dir / dist_info_name 1342 | if installer: 1343 | (dist_info / "INSTALLER").write_text(installer) 1344 | if source: 1345 | (dist_info / "PYODIDE_SOURCE").write_text(source) 1346 | 1347 | 1348 | def get_dynlibs(archive: IO[bytes], suffix: str, target_dir: Path) -> list[str]: 1349 | """List out the paths to .so files in a zip or tar archive. 1350 | 1351 | Parameters 1352 | ---------- 1353 | archive 1354 | A binary representation of either a zip or a tar archive. We use the `.name` 1355 | field to determine which file type. 1356 | 1357 | target_dir 1358 | The directory the archive is unpacked into. Paths will be adjusted to point 1359 | inside this directory. 1360 | 1361 | Returns 1362 | ------- 1363 | The list of paths to dynamic libraries ('.so' files) that were in the archive, 1364 | but adjusted to point to their unpacked locations. 1365 | """ 1366 | dynlib_paths_iter: Iterable[str] 1367 | if suffix in ZIP_TYPES: 1368 | dynlib_paths_iter = ZipFile(archive).namelist() 1369 | elif suffix in TAR_TYPES: 1370 | dynlib_paths_iter = (tinfo.name for tinfo in tarfile.open(archive.name)) 1371 | else: 1372 | raise ValueError(f"Unexpected suffix {suffix}") 1373 | 1374 | return [ 1375 | str((target_dir / path).resolve()) 1376 | for path in dynlib_paths_iter 1377 | if should_load_dynlib(path) 1378 | ] 1379 | 1380 | 1381 | def sub_resource_hash(sha_256: str) -> str: 1382 | """Calculates the sub resource integrity hash given a SHA-256 1383 | 1384 | Parameters 1385 | ---------- 1386 | sha_256 1387 | A hexdigest of the SHA-256 sum. 1388 | 1389 | Returns 1390 | ------- 1391 | The sub resource integrity hash corresponding to the sum. 1392 | 1393 | >>> sha_256 = 'c0dc86efda0060d4084098a90ec92b3d4aa89d7f7e0fba5424561d21451e1758' 1394 | >>> sub_resource_hash(sha_256) 1395 | 'sha256-wNyG79oAYNQIQJipDskrPUqonX9+D7pUJFYdIUUeF1g=' 1396 | """ 1397 | binary_digest = binascii.unhexlify(sha_256) 1398 | return "sha256-" + base64.b64encode(binary_digest).decode() 1399 | pyodide/webloop.py0000644000000000000000000003542514310731640013242 0ustar rootrootimport asyncio 1400 | import contextvars 1401 | import sys 1402 | import time 1403 | import traceback 1404 | from collections.abc import Callable 1405 | from typing import Any 1406 | 1407 | from ._core import IN_BROWSER, create_once_callable 1408 | 1409 | if IN_BROWSER: 1410 | from js import setTimeout 1411 | 1412 | 1413 | class WebLoop(asyncio.AbstractEventLoop): 1414 | """A custom event loop for use in Pyodide. 1415 | 1416 | Schedules tasks on the browser event loop. Does no lifecycle management and runs 1417 | forever. 1418 | 1419 | ``run_forever`` and ``run_until_complete`` cannot block like a normal event loop would 1420 | because we only have one thread so blocking would stall the browser event loop 1421 | and prevent anything from ever happening. 1422 | 1423 | We defer all work to the browser event loop using the setTimeout function. 1424 | To ensure that this event loop doesn't stall out UI and other browser handling, 1425 | we want to make sure that each task is scheduled on the browser event loop as a 1426 | task not as a microtask. ``setTimeout(callback, 0)`` enqueues the callback as a 1427 | task so it works well for our purposes. 1428 | 1429 | See `Event Loop Methods `_. 1430 | """ 1431 | 1432 | def __init__(self): 1433 | self._task_factory = None 1434 | asyncio._set_running_loop(self) 1435 | self._exception_handler = None 1436 | self._current_handle = None 1437 | 1438 | def get_debug(self): 1439 | return False 1440 | 1441 | # 1442 | # Lifecycle methods: We ignore all lifecycle management 1443 | # 1444 | 1445 | def is_running(self) -> bool: 1446 | """Returns ``True`` if the event loop is running. 1447 | 1448 | Always returns ``True`` because WebLoop has no lifecycle management. 1449 | """ 1450 | return True 1451 | 1452 | def is_closed(self) -> bool: 1453 | """Returns ``True`` if the event loop was closed. 1454 | 1455 | Always returns ``False`` because WebLoop has no lifecycle management. 1456 | """ 1457 | return False 1458 | 1459 | def _check_closed(self): 1460 | """Used in create_task. 1461 | 1462 | Would raise an error if ``self.is_closed()``, but we are skipping all lifecycle stuff. 1463 | """ 1464 | pass 1465 | 1466 | def run_forever(self): 1467 | """Run the event loop forever. Does nothing in this implementation. 1468 | 1469 | We cannot block like a normal event loop would 1470 | because we only have one thread so blocking would stall the browser event loop 1471 | and prevent anything from ever happening. 1472 | """ 1473 | pass 1474 | 1475 | def run_until_complete(self, future): 1476 | """Run until future is done. 1477 | 1478 | If the argument is a coroutine, it is wrapped in a Task. 1479 | 1480 | The native event loop `run_until_complete` blocks until evaluation of the 1481 | future is complete and then returns the result of the future. 1482 | Since we cannot block, we just ensure that the future is scheduled and 1483 | return the future. This makes this method a bit useless. Instead, use 1484 | `future.add_done_callback(do_something_with_result)` or: 1485 | ```python 1486 | async def wrapper(): 1487 | result = await future 1488 | do_something_with_result(result) 1489 | ``` 1490 | """ 1491 | return asyncio.ensure_future(future) 1492 | 1493 | # 1494 | # Scheduling methods: use browser.setTimeout to schedule tasks on the browser event loop. 1495 | # 1496 | 1497 | def call_soon( 1498 | self, 1499 | callback: Callable[..., Any], 1500 | *args: Any, 1501 | context: contextvars.Context | None = None, 1502 | ) -> asyncio.Handle: 1503 | """Arrange for a callback to be called as soon as possible. 1504 | 1505 | Any positional arguments after the callback will be passed to 1506 | the callback when it is called. 1507 | 1508 | This schedules the callback on the browser event loop using ``setTimeout(callback, 0)``. 1509 | """ 1510 | delay = 0 1511 | return self.call_later(delay, callback, *args, context=context) 1512 | 1513 | def call_soon_threadsafe( 1514 | self, 1515 | callback: Callable[..., Any], 1516 | *args: Any, 1517 | context: contextvars.Context | None = None, 1518 | ) -> asyncio.Handle: 1519 | """Like ``call_soon()``, but thread-safe. 1520 | 1521 | We have no threads so everything is "thread safe", and we just use ``call_soon``. 1522 | """ 1523 | return self.call_soon(callback, *args, context=context) 1524 | 1525 | def call_later( # type: ignore[override] 1526 | self, 1527 | delay: float, 1528 | callback: Callable[..., Any], 1529 | *args: Any, 1530 | context: contextvars.Context | None = None, 1531 | ) -> asyncio.Handle: 1532 | """Arrange for a callback to be called at a given time. 1533 | 1534 | Return a Handle: an opaque object with a cancel() method that 1535 | can be used to cancel the call. 1536 | 1537 | The delay can be an int or float, expressed in seconds. It is 1538 | always relative to the current time. 1539 | 1540 | Each callback will be called exactly once. If two callbacks 1541 | are scheduled for exactly the same time, it undefined which 1542 | will be called first. 1543 | 1544 | Any positional arguments after the callback will be passed to 1545 | the callback when it is called. 1546 | 1547 | This uses `setTimeout(callback, delay)` 1548 | """ 1549 | if delay < 0: 1550 | raise ValueError("Can't schedule in the past") 1551 | h = asyncio.Handle(callback, args, self, context=context) 1552 | 1553 | def run_handle(): 1554 | if h.cancelled(): 1555 | return 1556 | h._run() 1557 | 1558 | setTimeout(create_once_callable(run_handle), delay * 1000) 1559 | return h 1560 | 1561 | def call_at( # type: ignore[override] 1562 | self, 1563 | when: float, 1564 | callback: Callable[..., Any], 1565 | *args: Any, 1566 | context: contextvars.Context | None = None, 1567 | ) -> asyncio.Handle: 1568 | """Like ``call_later()``, but uses an absolute time. 1569 | 1570 | Absolute time corresponds to the event loop's ``time()`` method. 1571 | 1572 | This uses ``setTimeout(callback, when - cur_time)`` 1573 | """ 1574 | cur_time = self.time() 1575 | delay = when - cur_time 1576 | return self.call_later(delay, callback, *args, context=context) 1577 | 1578 | def run_in_executor(self, executor, func, *args): 1579 | """Arrange for func to be called in the specified executor. 1580 | 1581 | This is normally supposed to run func(*args) in a separate process or 1582 | thread and signal back to our event loop when it is done. It's possible 1583 | to make the executor, but if we actually try to submit any functions to 1584 | it, it will try to create a thread and throw an error. Best we can do is 1585 | to run func(args) in this thread and stick the result into a future. 1586 | """ 1587 | fut = self.create_future() 1588 | try: 1589 | fut.set_result(func(*args)) 1590 | except BaseException as e: 1591 | fut.set_exception(e) 1592 | return fut 1593 | 1594 | # 1595 | # The remaining methods are copied directly from BaseEventLoop 1596 | # 1597 | 1598 | def time(self) -> float: 1599 | """Return the time according to the event loop's clock. 1600 | 1601 | This is a float expressed in seconds since an epoch, but the 1602 | epoch, precision, accuracy and drift are unspecified and may 1603 | differ per event loop. 1604 | 1605 | Copied from ``BaseEventLoop.time`` 1606 | """ 1607 | return time.monotonic() 1608 | 1609 | def create_future(self) -> asyncio.Future[Any]: 1610 | """Create a Future object attached to the loop. 1611 | 1612 | Copied from ``BaseEventLoop.create_future`` 1613 | """ 1614 | return asyncio.futures.Future(loop=self) 1615 | 1616 | def create_task(self, coro, *, name=None): 1617 | """Schedule a coroutine object. 1618 | 1619 | Return a task object. 1620 | 1621 | Copied from ``BaseEventLoop.create_task`` 1622 | """ 1623 | self._check_closed() 1624 | if self._task_factory is None: 1625 | task = asyncio.tasks.Task(coro, loop=self, name=name) 1626 | if task._source_traceback: # type: ignore[attr-defined] 1627 | # Added comment: 1628 | # this only happens if get_debug() returns True. 1629 | # In that case, remove create_task from _source_traceback. 1630 | del task._source_traceback[-1] # type: ignore[attr-defined] 1631 | else: 1632 | task = self._task_factory(self, coro) 1633 | asyncio.tasks._set_task_name(task, name) # type: ignore[attr-defined] 1634 | 1635 | return task 1636 | 1637 | def set_task_factory(self, factory): 1638 | """Set a task factory that will be used by loop.create_task(). 1639 | 1640 | If factory is None the default task factory will be set. 1641 | 1642 | If factory is a callable, it should have a signature matching 1643 | '(loop, coro)', where 'loop' will be a reference to the active 1644 | event loop, 'coro' will be a coroutine object. The callable 1645 | must return a Future. 1646 | 1647 | Copied from ``BaseEventLoop.set_task_factory`` 1648 | """ 1649 | if factory is not None and not callable(factory): 1650 | raise TypeError("task factory must be a callable or None") 1651 | self._task_factory = factory 1652 | 1653 | def get_task_factory(self): 1654 | """Return a task factory, or None if the default one is in use. 1655 | 1656 | Copied from ``BaseEventLoop.get_task_factory`` 1657 | """ 1658 | return self._task_factory 1659 | 1660 | def get_exception_handler(self): 1661 | """Return an exception handler, or None if the default one is in use.""" 1662 | return self._exception_handler 1663 | 1664 | def set_exception_handler(self, handler): 1665 | """Set handler as the new event loop exception handler. 1666 | 1667 | If handler is None, the default exception handler will be set. 1668 | 1669 | If handler is a callable object, it should have a signature matching 1670 | '(loop, context)', where 'loop' will be a reference to the active event 1671 | loop, 'context' will be a dict object (see `call_exception_handler()` 1672 | documentation for details about context). 1673 | """ 1674 | if handler is not None and not callable(handler): 1675 | raise TypeError( 1676 | f"A callable object or None is expected, " f"got {handler!r}" 1677 | ) 1678 | self._exception_handler = handler 1679 | 1680 | def default_exception_handler(self, context): 1681 | """Default exception handler. 1682 | 1683 | This is called when an exception occurs and no exception handler is set, 1684 | and can be called by a custom exception handler that wants to defer to 1685 | the default behavior. This default handler logs the error message and 1686 | other context-dependent information. 1687 | 1688 | 1689 | In debug mode, a truncated stack trace is also appended showing where 1690 | the given object (e.g. a handle or future or task) was created, if any. 1691 | The context parameter has the same meaning as in 1692 | `call_exception_handler()`. 1693 | """ 1694 | message = context.get("message") 1695 | if not message: 1696 | message = "Unhandled exception in event loop" 1697 | 1698 | if ( 1699 | "source_traceback" not in context 1700 | and self._current_handle is not None 1701 | and self._current_handle._source_traceback 1702 | ): 1703 | context["handle_traceback"] = self._current_handle._source_traceback 1704 | 1705 | log_lines = [message] 1706 | for key in sorted(context): 1707 | if key in {"message", "exception"}: 1708 | continue 1709 | value = context[key] 1710 | if key == "source_traceback": 1711 | tb = "".join(traceback.format_list(value)) 1712 | value = "Object created at (most recent call last):\n" 1713 | value += tb.rstrip() 1714 | elif key == "handle_traceback": 1715 | tb = "".join(traceback.format_list(value)) 1716 | value = "Handle created at (most recent call last):\n" 1717 | value += tb.rstrip() 1718 | else: 1719 | value = repr(value) 1720 | log_lines.append(f"{key}: {value}") 1721 | 1722 | print("\n".join(log_lines), file=sys.stderr) 1723 | 1724 | def call_exception_handler(self, context): 1725 | """Call the current event loop's exception handler. 1726 | The context argument is a dict containing the following keys: 1727 | - 'message': Error message; 1728 | - 'exception' (optional): Exception object; 1729 | - 'future' (optional): Future instance; 1730 | - 'task' (optional): Task instance; 1731 | - 'handle' (optional): Handle instance; 1732 | - 'protocol' (optional): Protocol instance; 1733 | - 'transport' (optional): Transport instance; 1734 | - 'socket' (optional): Socket instance; 1735 | - 'asyncgen' (optional): Asynchronous generator that caused 1736 | the exception. 1737 | New keys maybe introduced in the future. 1738 | Note: do not overload this method in an event loop subclass. 1739 | For custom exception handling, use the 1740 | `set_exception_handler()` method. 1741 | """ 1742 | if self._exception_handler is None: 1743 | try: 1744 | self.default_exception_handler(context) 1745 | except (SystemExit, KeyboardInterrupt): 1746 | raise 1747 | except BaseException: 1748 | # Second protection layer for unexpected errors 1749 | # in the default implementation, as well as for subclassed 1750 | # event loops with overloaded "default_exception_handler". 1751 | print("Exception in default exception handler", file=sys.stderr) 1752 | traceback.print_exc() 1753 | else: 1754 | try: 1755 | self._exception_handler(self, context) 1756 | except (SystemExit, KeyboardInterrupt): 1757 | raise 1758 | except BaseException as exc: 1759 | # Exception in the user set custom exception handler. 1760 | try: 1761 | # Let's try default handler. 1762 | self.default_exception_handler( 1763 | { 1764 | "message": "Unhandled error in exception handler", 1765 | "exception": exc, 1766 | "context": context, 1767 | } 1768 | ) 1769 | except (SystemExit, KeyboardInterrupt): 1770 | raise 1771 | except BaseException: 1772 | # Guard 'default_exception_handler' in case it is 1773 | # overloaded. 1774 | print( 1775 | "Exception in default exception handler " 1776 | "while handling an unexpected error " 1777 | "in custom exception handler", 1778 | file=sys.stderr, 1779 | ) 1780 | traceback.print_exc() 1781 | 1782 | 1783 | class WebLoopPolicy(asyncio.DefaultEventLoopPolicy): 1784 | """ 1785 | A simple event loop policy for managing WebLoop based event loops. 1786 | """ 1787 | 1788 | def __init__(self): 1789 | self._default_loop = None 1790 | 1791 | def get_event_loop(self): 1792 | """Get the current event loop""" 1793 | if self._default_loop: 1794 | return self._default_loop 1795 | return self.new_event_loop() 1796 | 1797 | def new_event_loop(self) -> WebLoop: 1798 | """Create a new event loop""" 1799 | self._default_loop = WebLoop() # type: ignore[abstract] 1800 | return self._default_loop 1801 | 1802 | def set_event_loop(self, loop: Any) -> None: 1803 | """Set the current event loop""" 1804 | self._default_loop = loop 1805 | 1806 | 1807 | def _initialize_event_loop(): 1808 | from ._core import IN_BROWSER 1809 | 1810 | if not IN_BROWSER: 1811 | return 1812 | 1813 | import asyncio 1814 | 1815 | from .webloop import WebLoopPolicy 1816 | 1817 | policy = WebLoopPolicy() 1818 | asyncio.set_event_loop_policy(policy) 1819 | policy.get_event_loop() 1820 | 1821 | 1822 | __all__ = ["WebLoop", "WebLoopPolicy"] 1823 | _pyodide/0000755000000000000000000000000014310733215011347 5ustar rootroot_pyodide/__init__.py0000644000000000000000000000111014310731640013451 0ustar rootroot# _pyodide is imported at the very beginning of the initialization process so it 1824 | # cannot import from js, pyodide_js, or _pyodide_core. The one class here that 1825 | # does use such functions is JsFinder which requires access to 1826 | # _pyodide_core.JsProxy. 1827 | # 1828 | # register_js_finder is called from pyodide.js after _pyodide_core is completely 1829 | # initialized. 1830 | # 1831 | # All pure Python code that doesn't require imports from js, pyodide_js, or 1832 | # _pyodide_core belongs in _pyodide. Code that requires such imports belongs in 1833 | # pyodide. 1834 | from . import _base, _importhook 1835 | 1836 | __all__ = ["_base", "_importhook"] 1837 | _pyodide/docstring.py0000644000000000000000000000325214310731640013717 0ustar rootrootfrom textwrap import dedent 1838 | 1839 | 1840 | def dedent_docstring(docstring): 1841 | """This removes initial spaces from the lines of the docstring. 1842 | 1843 | After the first line of the docstring, all other lines will include some 1844 | spaces. This removes them. 1845 | 1846 | Examples 1847 | -------- 1848 | >>> from _pyodide.docstring import dedent_docstring 1849 | >>> dedent_docstring(dedent_docstring.__doc__).split("\\n")[2] 1850 | 'After the first line of the docstring, all other lines will include some' 1851 | """ 1852 | first_newline = docstring.find("\n") 1853 | if first_newline == -1: 1854 | return docstring 1855 | docstring = docstring[:first_newline] + dedent(docstring[first_newline:]) 1856 | return docstring 1857 | 1858 | 1859 | def get_cmeth_docstring(func): 1860 | """Get the value to use for the PyMethodDef.ml_doc attribute for a builtin 1861 | function. This is used in docstring.c. 1862 | 1863 | The ml_doc should start with a signature which cannot have any type 1864 | annotations. The signature must end with the exact characters ")\n--\n\n". 1865 | For example: "funcname(arg1, arg2)\n--\n\n" 1866 | 1867 | See: 1868 | https://github.com/python/cpython/blob/v3.8.2/Objects/typeobject.c#L84 1869 | 1870 | Examples 1871 | -------- 1872 | >>> from _pyodide.docstring import get_cmeth_docstring 1873 | >>> get_cmeth_docstring(sum)[:80] 1874 | "sum(iterable, /, start=0)\\n--\\n\\nReturn the sum of a 'start' value (default: 0) plu" 1875 | """ 1876 | from inspect import _empty, signature 1877 | 1878 | sig = signature(func) 1879 | # remove param and return annotations and 1880 | for param in sig.parameters.values(): 1881 | param._annotation = _empty # type: ignore[attr-defined] 1882 | sig._return_annotation = _empty # type: ignore[attr-defined] 1883 | 1884 | return func.__name__ + str(sig) + "\n--\n\n" + dedent_docstring(func.__doc__) 1885 | _pyodide/_base.py0000644000000000000000000004474314310731640013006 0ustar rootroot""" 1886 | A library of helper utilities for connecting Python to the browser environment. 1887 | """ 1888 | # Added by C: 1889 | # JsException (from jsproxy.c) 1890 | 1891 | import ast 1892 | import builtins 1893 | import tokenize 1894 | from collections.abc import Generator 1895 | from copy import deepcopy 1896 | from io import StringIO 1897 | from textwrap import dedent 1898 | from types import CodeType 1899 | from typing import Any, Literal 1900 | 1901 | 1902 | def should_quiet(source: str) -> bool: 1903 | """ 1904 | Should we suppress output? 1905 | 1906 | Returns ``True`` if the last nonwhitespace character of ``code`` is a semicolon. 1907 | 1908 | Examples 1909 | -------- 1910 | >>> should_quiet('1 + 1') 1911 | False 1912 | >>> should_quiet('1 + 1 ;') 1913 | True 1914 | >>> should_quiet('1 + 1 # comment ;') 1915 | False 1916 | """ 1917 | # largely inspired from IPython: 1918 | # https://github.com/ipython/ipython/blob/86d24741188b0cedd78ab080d498e775ed0e5272/IPython/core/displayhook.py#L84 1919 | 1920 | # We need to wrap tokens in a buffer because: 1921 | # "Tokenize requires one argument, readline, which must be 1922 | # a callable object which provides the same interface as the 1923 | # io.IOBase.readline() method of file objects" 1924 | source_io = StringIO(source) 1925 | tokens = list(tokenize.generate_tokens(source_io.readline)) 1926 | 1927 | for token in reversed(tokens): 1928 | if token.type in ( 1929 | tokenize.ENDMARKER, 1930 | tokenize.NL, # ignoring empty lines (\n\n) 1931 | tokenize.NEWLINE, 1932 | tokenize.COMMENT, 1933 | ): 1934 | continue 1935 | return (token.type == tokenize.OP) and (token.string == ";") 1936 | return False 1937 | 1938 | 1939 | def _last_assign_to_expr(mod: ast.Module) -> None: 1940 | """ 1941 | Implementation of 'last_expr_or_assign' return_mode. 1942 | It modifies the supplyied AST module so that the last 1943 | statement's value can be returned in 'last_expr' return_mode. 1944 | """ 1945 | # Largely inspired from IPython: 1946 | # https://github.com/ipython/ipython/blob/3587f5bb6c8570e7bbb06cf5f7e3bc9b9467355a/IPython/core/interactiveshell.py#L3229 1947 | 1948 | if not mod.body: 1949 | return 1950 | last_node = mod.body[-1] 1951 | 1952 | if isinstance(last_node, ast.Assign): 1953 | # In this case there can be multiple targets as in `a = b = 1`. 1954 | # We just take the first one. 1955 | target = last_node.targets[0] 1956 | elif isinstance(last_node, (ast.AugAssign, ast.AnnAssign)): 1957 | target = last_node.target 1958 | else: 1959 | return 1960 | if isinstance(target, ast.Name): 1961 | last_node = ast.Expr(ast.Name(target.id, ast.Load())) 1962 | mod.body.append(last_node) 1963 | # Update the line numbers shown in error messages. 1964 | ast.fix_missing_locations(mod) 1965 | 1966 | 1967 | class EvalCodeResultException(Exception): 1968 | """We will throw this to return a result from our code. 1969 | 1970 | This allows us to distinguish between "code used top level await" and "code 1971 | returned a generator or coroutine". 1972 | """ 1973 | 1974 | def __init__(self, v: Any) -> None: 1975 | super().__init__(v) 1976 | self.value = v 1977 | 1978 | 1979 | # We need EvalCodeResultException available inside the running code. I suppose 1980 | # we could import it, wrap all of the code in a try/finally block, and delete it 1981 | # again in the finally block but I think this is the best way. 1982 | # 1983 | # Put it into a list to avoid breaking CPython test test_inheritance 1984 | # (test.test_baseexception.ExceptionClassTests) which examines all Exceptions in 1985 | # builtins. 1986 | builtins.___EvalCodeResultException = [EvalCodeResultException] # type: ignore[attr-defined] 1987 | 1988 | # We will substitute in the value of x we are trying to return. 1989 | _raise_template_ast = ast.parse("raise ___EvalCodeResultException[0](x)").body[0] 1990 | 1991 | 1992 | def _last_expr_to_raise(mod: ast.Module) -> None: 1993 | """If the final ast node is a statement, raise an EvalCodeResultException 1994 | with the value of the statement. 1995 | """ 1996 | if not mod.body: 1997 | return 1998 | last_node = mod.body[-1] 1999 | if not isinstance(mod.body[-1], (ast.Expr, ast.Await)): 2000 | return 2001 | raise_expr = deepcopy(_raise_template_ast) 2002 | # Replace x with our value in _raise_template_ast. 2003 | raise_expr.exc.args[0] = last_node.value # type: ignore[attr-defined] 2004 | mod.body[-1] = raise_expr 2005 | 2006 | 2007 | def _parse_and_compile_gen( 2008 | source: str, 2009 | *, 2010 | return_mode: str = "last_expr", 2011 | quiet_trailing_semicolon: bool = True, 2012 | mode: str = "exec", 2013 | filename: str = "", 2014 | flags: int = 0x0, 2015 | ) -> Generator[ast.Module, ast.Module, CodeType]: 2016 | """Parse ``source``, then yield the AST, then compile the AST and return the 2017 | code object. 2018 | 2019 | By yielding the ast, we give callers the opportunity to do further ast 2020 | manipulations. Because generators are annoying to call, this is wrapped in 2021 | the Executor class. 2022 | """ 2023 | # handle mis-indented input from multi-line strings 2024 | source = dedent(source) 2025 | 2026 | mod = compile(source, filename, mode, flags | ast.PyCF_ONLY_AST) 2027 | 2028 | # Pause here, allow caller to transform ast if they like. 2029 | mod = yield mod 2030 | 2031 | if quiet_trailing_semicolon and should_quiet(source): 2032 | return_mode = "none" 2033 | 2034 | if return_mode == "last_expr_or_assign": 2035 | # add extra expression with just the L-value so that we can handle it 2036 | # with the last_expr code. 2037 | _last_assign_to_expr(mod) 2038 | 2039 | if return_mode.startswith("last_expr"): # last_expr or last_expr_or_assign 2040 | _last_expr_to_raise(mod) 2041 | 2042 | ast.fix_missing_locations(mod) 2043 | return compile(mod, filename, mode, flags=flags) 2044 | 2045 | 2046 | ReturnMode = Literal["last_expr", "last_expr_or_assign", "none"] 2047 | 2048 | 2049 | class CodeRunner: 2050 | """This class allows fine control over the execution of a code block. 2051 | 2052 | It is primarily intended for REPLs and other sophisticated consumers that 2053 | may wish to add their own AST transformations, separately signal to the user 2054 | when parsing is complete, etc. The simpler :any:`eval_code` and 2055 | :any:`eval_code_async` apis should be preferred when their flexibility 2056 | suffices. 2057 | 2058 | Parameters 2059 | ---------- 2060 | source : ``str`` 2061 | 2062 | The Python source code to run. 2063 | 2064 | return_mode : ``str`` 2065 | 2066 | Specifies what should be returned, must be one of ``'last_expr'``, 2067 | ``'last_expr_or_assign'`` or ``'none'``. On other values an exception is 2068 | raised. ``'last_expr'`` by default. 2069 | 2070 | * ``'last_expr'`` -- return the last expression 2071 | * ``'last_expr_or_assign'`` -- return the last expression or the last assignment. 2072 | * ``'none'`` -- always return ``None``. 2073 | 2074 | quiet_trailing_semicolon : ``bool`` 2075 | 2076 | Specifies whether a trailing semicolon should suppress the result or not. 2077 | When this is ``True`` executing ``"1+1 ;"`` returns ``None``, when 2078 | it is ``False``, executing ``"1+1 ;"`` return ``2``. ``True`` by default. 2079 | 2080 | filename : ``str`` 2081 | 2082 | The file name to use in error messages and stack traces. ``''`` by default. 2083 | 2084 | mode : ``str`` 2085 | 2086 | The "mode" to compile in. One of ``"exec"``, ``"single"``, or ``"eval"``. Defaults 2087 | to ``"exec"``. For most purposes it's unnecessary to use this argument. 2088 | See the documentation for the built-in 2089 | `compile ` function. 2090 | 2091 | flags : ``int`` 2092 | 2093 | The flags to compile with. See the documentation for the built-in 2094 | `compile ` function. 2095 | 2096 | Attributes: 2097 | 2098 | ast : The ast from parsing ``source``. If you wish to do an ast transform, 2099 | modify this variable before calling :any:`CodeRunner.compile`. 2100 | 2101 | code : Once you call :any:`CodeRunner.compile` the compiled code will 2102 | be available in the code field. You can modify this variable 2103 | before calling :any:`CodeRunner.run` to do a code transform. 2104 | """ 2105 | 2106 | def __init__( 2107 | self, 2108 | source: str, 2109 | *, 2110 | return_mode: ReturnMode = "last_expr", 2111 | mode: str = "exec", 2112 | quiet_trailing_semicolon: bool = True, 2113 | filename: str = "", 2114 | flags: int = 0x0, 2115 | ): 2116 | self._compiled = False 2117 | self._gen = _parse_and_compile_gen( 2118 | source, 2119 | return_mode=return_mode, 2120 | mode=mode, 2121 | quiet_trailing_semicolon=quiet_trailing_semicolon, 2122 | filename=filename, 2123 | flags=flags, 2124 | ) 2125 | self.ast = next(self._gen) 2126 | 2127 | def compile(self) -> "CodeRunner": 2128 | """Compile the current value of ``self.ast`` and store the result in ``self.code``. 2129 | 2130 | Can only be used once. Returns ``self`` (chainable). 2131 | """ 2132 | if self._compiled: 2133 | raise RuntimeError("Already compiled") 2134 | self._compiled = True 2135 | try: 2136 | # Triggers compilation 2137 | self._gen.send(self.ast) 2138 | except StopIteration as e: 2139 | # generator must return, which raises StopIteration 2140 | self.code = e.value 2141 | else: 2142 | raise AssertionError() 2143 | return self 2144 | 2145 | def run( 2146 | self, 2147 | globals: dict[str, Any] | None = None, 2148 | locals: dict[str, Any] | None = None, 2149 | ) -> Any | None: 2150 | """Executes ``self.code``. 2151 | 2152 | Can only be used after calling compile. The code may not use top level 2153 | await, use :any:`CodeRunner.run_async` for code that uses top level 2154 | await. 2155 | 2156 | Parameters 2157 | ---------- 2158 | globals : ``dict`` 2159 | 2160 | The global scope in which to execute code. This is used as the ``globals`` 2161 | parameter for ``exec``. If ``globals`` is absent, a new empty dictionary is used. 2162 | See `the exec documentation 2163 | `_ for more info. 2164 | 2165 | locals : ``dict`` 2166 | 2167 | The local scope in which to execute code. This is used as the ``locals`` 2168 | parameter for ``exec``. If ``locals`` is absent, the value of ``globals`` is 2169 | used. 2170 | See `the exec documentation 2171 | `_ for more info. 2172 | 2173 | Returns 2174 | ------- 2175 | Any 2176 | 2177 | If the last nonwhitespace character of ``source`` is a semicolon, 2178 | return ``None``. If the last statement is an expression, return the 2179 | result of the expression. Use the ``return_mode`` and 2180 | ``quiet_trailing_semicolon`` parameters to modify this default 2181 | behavior. 2182 | """ 2183 | if not self._compiled: 2184 | raise RuntimeError("Not yet compiled") 2185 | if self.code is None: 2186 | return None 2187 | try: 2188 | coroutine = eval(self.code, globals, locals) 2189 | if coroutine: 2190 | raise RuntimeError( 2191 | "Used eval_code with TOP_LEVEL_AWAIT. Use run_async for this instead." 2192 | ) 2193 | except EvalCodeResultException as e: 2194 | # Final expression from code returns here 2195 | return e.value 2196 | 2197 | return None 2198 | 2199 | async def run_async( 2200 | self, 2201 | globals: dict[str, Any] | None = None, 2202 | locals: dict[str, Any] | None = None, 2203 | ) -> None: 2204 | """Runs ``self.code`` which may use top level await. 2205 | 2206 | Can only be used after calling :any:`CodeRunner.compile`. If 2207 | ``self.code`` uses top level await, automatically awaits the resulting 2208 | coroutine. 2209 | 2210 | Parameters 2211 | ---------- 2212 | globals : ``dict`` 2213 | 2214 | The global scope in which to execute code. This is used as the ``globals`` 2215 | parameter for ``exec``. If ``globals`` is absent, a new empty dictionary is used. 2216 | See `the exec documentation 2217 | `_ for more info. 2218 | 2219 | locals : ``dict`` 2220 | 2221 | The local scope in which to execute code. This is used as the ``locals`` 2222 | parameter for ``exec``. If ``locals`` is absent, the value of ``globals`` is 2223 | used. 2224 | See `the exec documentation 2225 | `_ for more info. 2226 | 2227 | Returns 2228 | ------- 2229 | Any 2230 | If the last nonwhitespace character of ``source`` is a semicolon, 2231 | return ``None``. If the last statement is an expression, return the 2232 | result of the expression. Use the ``return_mode`` and 2233 | ``quiet_trailing_semicolon`` parameters to modify this default 2234 | behavior. 2235 | """ 2236 | if not self._compiled: 2237 | raise RuntimeError("Not yet compiled") 2238 | if self.code is None: 2239 | return 2240 | try: 2241 | coroutine = eval(self.code, globals, locals) 2242 | if coroutine: 2243 | await coroutine 2244 | except EvalCodeResultException as e: 2245 | return e.value 2246 | 2247 | 2248 | def eval_code( 2249 | source: str, 2250 | globals: dict[str, Any] | None = None, 2251 | locals: dict[str, Any] | None = None, 2252 | *, 2253 | return_mode: ReturnMode = "last_expr", 2254 | quiet_trailing_semicolon: bool = True, 2255 | filename: str = "", 2256 | flags: int = 0x0, 2257 | ) -> Any: 2258 | """Runs a string as Python source code. 2259 | 2260 | Parameters 2261 | ---------- 2262 | source : ``str`` 2263 | 2264 | The Python source code to run. 2265 | 2266 | globals : ``dict`` 2267 | 2268 | The global scope in which to execute code. This is used as the ``globals`` 2269 | parameter for ``exec``. If ``globals`` is absent, a new empty dictionary is used. 2270 | See `the exec documentation 2271 | `_ for more info. 2272 | 2273 | locals : ``dict`` 2274 | 2275 | The local scope in which to execute code. This is used as the ``locals`` 2276 | parameter for ``exec``. If ``locals`` is absent, the value of ``globals`` is 2277 | used. 2278 | See `the exec documentation 2279 | `_ for more info. 2280 | 2281 | return_mode : ``str`` 2282 | 2283 | Specifies what should be returned, must be one of ``'last_expr'``, 2284 | ``'last_expr_or_assign'`` or ``'none'``. On other values an exception is 2285 | raised. ``'last_expr'`` by default. 2286 | 2287 | * ``'last_expr'`` -- return the last expression 2288 | * ``'last_expr_or_assign'`` -- return the last expression or the last assignment. 2289 | * ``'none'`` -- always return ``None``. 2290 | 2291 | quiet_trailing_semicolon : ``bool`` 2292 | 2293 | Specifies whether a trailing semicolon should suppress the result or not. 2294 | When this is ``True`` executing ``"1+1 ;"`` returns ``None``, when 2295 | it is ``False``, executing ``"1+1 ;"`` return ``2``. ``True`` by default. 2296 | 2297 | filename : ``str`` 2298 | 2299 | The file name to use in error messages and stack traces. ``''`` by default. 2300 | 2301 | Returns 2302 | ------- 2303 | Any 2304 | 2305 | If the last nonwhitespace character of ``source`` is a semicolon, return 2306 | ``None``. If the last statement is an expression, return the result of the 2307 | expression. Use the ``return_mode`` and ``quiet_trailing_semicolon`` 2308 | parameters to modify this default behavior. 2309 | """ 2310 | return ( 2311 | CodeRunner( 2312 | source, 2313 | return_mode=return_mode, 2314 | quiet_trailing_semicolon=quiet_trailing_semicolon, 2315 | filename=filename, 2316 | flags=flags, 2317 | ) 2318 | .compile() 2319 | .run(globals, locals) 2320 | ) 2321 | 2322 | 2323 | async def eval_code_async( 2324 | source: str, 2325 | globals: dict[str, Any] | None = None, 2326 | locals: dict[str, Any] | None = None, 2327 | *, 2328 | return_mode: ReturnMode = "last_expr", 2329 | quiet_trailing_semicolon: bool = True, 2330 | filename: str = "", 2331 | flags: int = 0x0, 2332 | ) -> Any: 2333 | """Runs a code string asynchronously. 2334 | 2335 | Uses `PyCF_ALLOW_TOP_LEVEL_AWAIT 2336 | `_ 2337 | to compile the code. 2338 | 2339 | Parameters 2340 | ---------- 2341 | source : ``str`` 2342 | 2343 | The Python source code to run. 2344 | 2345 | globals : ``dict`` 2346 | 2347 | The global scope in which to execute code. This is used as the ``globals`` 2348 | parameter for ``exec``. If ``globals`` is absent, a new empty dictionary is used. 2349 | See `the exec documentation 2350 | `_ for more info. 2351 | 2352 | locals : ``dict`` 2353 | 2354 | The local scope in which to execute code. This is used as the ``locals`` 2355 | parameter for ``exec``. If ``locals`` is absent, the value of ``globals`` is 2356 | used. 2357 | See `the exec documentation 2358 | `_ for more info. 2359 | 2360 | return_mode : ``str`` 2361 | 2362 | Specifies what should be returned, must be one of ``'last_expr'``, 2363 | ``'last_expr_or_assign'`` or ``'none'``. On other values an exception is 2364 | raised. ``'last_expr'`` by default. 2365 | 2366 | * ``'last_expr'`` -- return the last expression 2367 | * ``'last_expr_or_assign'`` -- return the last expression or the last assignment. 2368 | * ``'none'`` -- always return ``None``. 2369 | 2370 | quiet_trailing_semicolon : ``bool`` 2371 | 2372 | Specifies whether a trailing semicolon should suppress the result or not. 2373 | When this is ``True`` executing ``"1+1 ;"`` returns ``None``, when 2374 | it is ``False``, executing ``"1+1 ;"`` return ``2``. ``True`` by default. 2375 | 2376 | filename : ``str`` 2377 | 2378 | The file name to use in error messages and stack traces. ``''`` by default. 2379 | 2380 | Returns 2381 | ------- 2382 | Any 2383 | If the last nonwhitespace character of ``source`` is a semicolon, return 2384 | ``None``. If the last statement is an expression, return the result of the 2385 | expression. Use the ``return_mode`` and ``quiet_trailing_semicolon`` 2386 | parameters to modify this default behavior. 2387 | """ 2388 | flags = flags or ast.PyCF_ALLOW_TOP_LEVEL_AWAIT 2389 | return ( 2390 | await CodeRunner( 2391 | source, 2392 | return_mode=return_mode, 2393 | quiet_trailing_semicolon=quiet_trailing_semicolon, 2394 | filename=filename, 2395 | flags=flags, 2396 | ) 2397 | .compile() 2398 | .run_async(globals, locals) 2399 | ) 2400 | 2401 | 2402 | def find_imports(source: str) -> list[str]: 2403 | """ 2404 | Finds the imports in a Python source code string 2405 | 2406 | Parameters 2407 | ---------- 2408 | source : str 2409 | The Python source code to inspect for imports. 2410 | 2411 | Returns 2412 | ------- 2413 | ``List[str]`` 2414 | A list of module names that are imported in ``source``. If ``source`` is not 2415 | syntactically correct Python code (after dedenting), returns an empty list. 2416 | 2417 | Examples 2418 | -------- 2419 | >>> from pyodide import find_imports 2420 | >>> source = "import numpy as np; import scipy.stats" 2421 | >>> find_imports(source) 2422 | ['numpy', 'scipy'] 2423 | """ 2424 | # handle mis-indented input from multi-line strings 2425 | source = dedent(source) 2426 | 2427 | try: 2428 | mod = ast.parse(source) 2429 | except SyntaxError: 2430 | return [] 2431 | imports = set() 2432 | for node in ast.walk(mod): 2433 | if isinstance(node, ast.Import): 2434 | for name in node.names: 2435 | node_name = name.name 2436 | imports.add(node_name.split(".")[0]) 2437 | elif isinstance(node, ast.ImportFrom): 2438 | module_name = node.module 2439 | if module_name is None: 2440 | continue 2441 | imports.add(module_name.split(".")[0]) 2442 | return list(sorted(imports)) 2443 | _pyodide/_core_docs.py0000644000000000000000000004223314310731640014024 0ustar rootrootfrom collections.abc import Callable, Iterable 2444 | from io import IOBase 2445 | from typing import Any 2446 | 2447 | # All docstrings for public `core` APIs should be extracted from here. We use 2448 | # the utilities in `docstring.py` and `docstring.c` to format them 2449 | # appropriately. 2450 | 2451 | # Sphinx uses __name__ to determine the paths and such. It looks better for it 2452 | # to refer to e.g., `pyodide.JsProxy` than `_pyodide._core_docs.JsProxy`. 2453 | _save_name = __name__ 2454 | __name__ = "pyodide" 2455 | 2456 | 2457 | # From jsproxy.c 2458 | class JsException(Exception): 2459 | """ 2460 | A wrapper around a JavaScript Error to allow it to be thrown in Python. 2461 | See :ref:`type-translations-errors`. 2462 | """ 2463 | 2464 | @property 2465 | def js_error(self) -> "JsProxy": 2466 | """The original JavaScript error""" 2467 | return JsProxy() 2468 | 2469 | 2470 | class ConversionError(Exception): 2471 | """An error thrown when conversion between JavaScript and Python fails.""" 2472 | 2473 | 2474 | class JsProxy: 2475 | """A proxy to make a JavaScript object behave like a Python object 2476 | 2477 | For more information see the :ref:`type-translations` documentation. In 2478 | particular, see 2479 | :ref:`the list of __dunder__ methods ` 2480 | that are (conditionally) implemented on :any:`JsProxy`. 2481 | """ 2482 | 2483 | @property 2484 | def js_id(self) -> int: 2485 | """An id number which can be used as a dictionary/set key if you want to 2486 | key on JavaScript object identity. 2487 | 2488 | If two `JsProxy` are made with the same backing JavaScript object, they 2489 | will have the same `js_id`. The reault is a "pseudorandom" 32 bit integer. 2490 | """ 2491 | return 0 2492 | 2493 | @property 2494 | def typeof(self) -> str: 2495 | """Returns the JavaScript type of the JsProxy. 2496 | 2497 | Corresponds to `typeof obj;` in JavaScript. You may also be interested 2498 | in the `constuctor` attribute which returns the type as an object. 2499 | """ 2500 | return "object" 2501 | 2502 | def object_entries(self) -> "JsProxy": 2503 | "The JavaScript API ``Object.entries(object)``" 2504 | 2505 | def object_keys(self) -> "JsProxy": 2506 | "The JavaScript API ``Object.keys(object)``" 2507 | 2508 | def object_values(self) -> "JsProxy": 2509 | "The JavaScript API ``Object.values(object)``" 2510 | 2511 | def new(self, *args: Any, **kwargs: Any) -> "JsProxy": 2512 | """Construct a new instance of the JavaScript object""" 2513 | 2514 | def to_py( 2515 | self, 2516 | *, 2517 | depth: int = -1, 2518 | default_converter: Callable[ 2519 | ["JsProxy", Callable[["JsProxy"], Any], Callable[["JsProxy", Any], None]], 2520 | Any, 2521 | ] 2522 | | None = None, 2523 | ) -> Any: 2524 | """Convert the :class:`JsProxy` to a native Python object as best as 2525 | possible. 2526 | 2527 | By default, does a deep conversion, if a shallow conversion is desired, 2528 | you can use ``proxy.to_py(depth=1)``. See 2529 | :ref:`type-translations-jsproxy-to-py` for more information. 2530 | 2531 | ``default_converter`` if present will be invoked whenever Pyodide does 2532 | not have some built in conversion for the object. 2533 | If ``default_converter`` raises an error, the error will be allowed to 2534 | propagate. Otherwise, the object returned will be used as the 2535 | conversion. ``default_converter`` takes three arguments. The first 2536 | argument is the value to be converted. 2537 | 2538 | Here are a couple examples of converter functions. In addition to the 2539 | normal conversions, convert ``Date`` to ``datetime``: 2540 | 2541 | .. code-block:: python 2542 | 2543 | from datetime import datetime 2544 | def default_converter(value, _ignored1, _ignored2): 2545 | if value.constructor.name == "Date": 2546 | return datetime.fromtimestamp(d.valueOf()/1000) 2547 | return value 2548 | 2549 | Don't create any JsProxies, require a complete conversion or raise an error: 2550 | 2551 | .. code-block:: python 2552 | 2553 | def default_converter(_value, _ignored1, _ignored2): 2554 | raise Exception("Failed to completely convert object") 2555 | 2556 | The second and third arguments are only needed for converting 2557 | containers. The second argument is a conversion function which is used 2558 | to convert the elements of the container with the same settings. The 2559 | third argument is a "cache" function which is needed to handle self 2560 | referential containers. Consider the following example. Suppose we have 2561 | a Javascript ``Pair`` class: 2562 | 2563 | .. code-block:: javascript 2564 | 2565 | class Pair { 2566 | constructor(first, second){ 2567 | this.first = first; 2568 | this.second = second; 2569 | } 2570 | } 2571 | 2572 | We can use the following ``default_converter`` to convert ``Pair`` to ``list``: 2573 | 2574 | .. code-block:: python 2575 | 2576 | def default_converter(value, convert, cache): 2577 | if value.constructor.name != "Pair": 2578 | return value 2579 | result = [] 2580 | cache(value, result); 2581 | result.append(convert(value.first)) 2582 | result.append(convert(value.second)) 2583 | return result 2584 | 2585 | Note that we have to cache the conversion of ``value`` before converting 2586 | ``value.first`` and ``value.second``. To see why, consider a self 2587 | referential pair: 2588 | 2589 | .. code-block:: javascript 2590 | 2591 | let p = new Pair(0, 0); 2592 | p.first = p; 2593 | 2594 | Without ``cache(value, result);``, converting ``p`` would lead to an 2595 | infinite recurse. With it, we can successfully convert ``p`` to a list 2596 | such that ``l[0] is l``. 2597 | """ 2598 | pass 2599 | 2600 | def then( 2601 | self, onfulfilled: Callable[[Any], Any], onrejected: Callable[[Any], Any] 2602 | ) -> "Promise": 2603 | """The ``Promise.then`` API, wrapped to manage the lifetimes of the 2604 | handlers. 2605 | 2606 | Present only if the wrapped JavaScript object has a "then" method. 2607 | Pyodide will automatically release the references to the handlers 2608 | when the promise resolves. 2609 | """ 2610 | 2611 | def catch(self, onrejected: Callable[[Any], Any], /) -> "Promise": 2612 | """The ``Promise.catch`` API, wrapped to manage the lifetimes of the 2613 | handler. 2614 | 2615 | Present only if the wrapped JavaScript object has a "then" method. 2616 | Pyodide will automatically release the references to the handler 2617 | when the promise resolves. 2618 | """ 2619 | 2620 | def finally_(self, onfinally: Callable[[Any], Any], /) -> "Promise": 2621 | """The ``Promise.finally`` API, wrapped to manage the lifetimes of 2622 | the handler. 2623 | 2624 | Present only if the wrapped JavaScript object has a "then" method. 2625 | Pyodide will automatically release the references to the handler 2626 | when the promise resolves. Note the trailing underscore in the name; 2627 | this is needed because ``finally`` is a reserved keyword in Python. 2628 | """ 2629 | 2630 | # There are no types for buffers: 2631 | # https://github.com/python/typing/issues/593 2632 | # https://bugs.python.org/issue27501 2633 | # This is just for docs so lets just make something up? 2634 | 2635 | # Argument should be a buffer. 2636 | # See https://github.com/python/typing/issues/593 2637 | def assign(self, rhs: Any, /) -> None: 2638 | """Assign from a Python buffer into the JavaScript buffer. 2639 | 2640 | Present only if the wrapped JavaScript object is an ArrayBuffer or 2641 | an ArrayBuffer view. 2642 | """ 2643 | 2644 | # Argument should be a buffer. 2645 | # See https://github.com/python/typing/issues/593 2646 | def assign_to(self, to: Any, /) -> None: 2647 | """Assign to a Python buffer from the JavaScript buffer. 2648 | 2649 | Present only if the wrapped JavaScript object is an ArrayBuffer or 2650 | an ArrayBuffer view. 2651 | """ 2652 | 2653 | def to_memoryview(self) -> memoryview: 2654 | """Convert a buffer to a memoryview. 2655 | 2656 | Copies the data once. This currently has the same effect as :any:`to_py`. 2657 | Present only if the wrapped Javascript object is an ArrayBuffer or 2658 | an ArrayBuffer view. 2659 | """ 2660 | 2661 | def to_bytes(self) -> bytes: 2662 | """Convert a buffer to a bytes object. 2663 | 2664 | Copies the data once. 2665 | Present only if the wrapped Javascript object is an ArrayBuffer or 2666 | an ArrayBuffer view. 2667 | """ 2668 | 2669 | def to_file(self, file: IOBase, /) -> None: 2670 | """Writes a buffer to a file. 2671 | 2672 | Will write the entire contents of the buffer to the current position of 2673 | the file. 2674 | 2675 | Present only if the wrapped Javascript object is an ArrayBuffer or an 2676 | ArrayBuffer view. 2677 | 2678 | Example 2679 | ------------ 2680 | >>> import pytest; pytest.skip() 2681 | >>> from js import Uint8Array 2682 | >>> x = Uint8Array.new(range(10)) 2683 | >>> with open('file.bin', 'wb') as fh: 2684 | ... x.to_file(fh) 2685 | which is equivalent to, 2686 | >>> with open('file.bin', 'wb') as fh: 2687 | ... data = x.to_bytes() 2688 | ... fh.write(data) 2689 | but the latter copies the data twice whereas the former only copies the 2690 | data once. 2691 | """ 2692 | 2693 | def from_file(self, file: IOBase, /) -> None: 2694 | """Reads from a file into a buffer. 2695 | 2696 | Will try to read a chunk of data the same size as the buffer from 2697 | the current position of the file. 2698 | 2699 | Present only if the wrapped Javascript object is an ArrayBuffer or an 2700 | ArrayBuffer view. 2701 | 2702 | Example 2703 | ------------ 2704 | >>> import pytest; pytest.skip() 2705 | >>> from js import Uint8Array 2706 | >>> # the JsProxy need to be pre-allocated 2707 | >>> x = Uint8Array.new(range(10)) 2708 | >>> with open('file.bin', 'rb') as fh: 2709 | ... x.read_file(fh) 2710 | which is equivalent to 2711 | >>> x = Uint8Array.new(range(10)) 2712 | >>> with open('file.bin', 'rb') as fh: 2713 | ... chunk = fh.read(size=x.byteLength) 2714 | ... x.assign(chunk) 2715 | but the latter copies the data twice whereas the former only copies the 2716 | data once. 2717 | """ 2718 | 2719 | def _into_file(self, file: IOBase, /) -> None: 2720 | """Will write the entire contents of a buffer into a file using 2721 | ``canOwn : true`` without any copy. After this, the buffer cannot be 2722 | used again. 2723 | 2724 | If ``file`` is not empty, its contents will be overwritten! 2725 | 2726 | Only ``MEMFS`` cares about the ``canOwn`` flag, other file systems will 2727 | just ignore it. 2728 | 2729 | Present only if the wrapped Javascript object is an ArrayBuffer or an 2730 | ArrayBuffer view. 2731 | 2732 | Example 2733 | ------------ 2734 | >>> import pytest; pytest.skip() 2735 | >>> from js import Uint8Array 2736 | >>> x = Uint8Array.new(range(10)) 2737 | >>> with open('file.bin', 'wb') as fh: 2738 | ... x._into_file(fh) 2739 | which is similar to 2740 | >>> with open('file.bin', 'wb') as fh: 2741 | ... data = x.to_bytes() 2742 | ... fh.write(data) 2743 | but the latter copies the data once whereas the former doesn't copy the 2744 | data. 2745 | """ 2746 | 2747 | def to_string(self, encoding: str | None = None) -> str: 2748 | """Convert a buffer to a string object. 2749 | 2750 | Copies the data twice. 2751 | 2752 | The encoding argument will be passed to the Javascript 2753 | [``TextDecoder``](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) 2754 | constructor. It should be one of the encodings listed in the table here: 2755 | `https://encoding.spec.whatwg.org/#names-and-labels`. The default 2756 | encoding is utf8. 2757 | 2758 | Present only if the wrapped Javascript object is an ArrayBuffer or 2759 | an ArrayBuffer view. 2760 | """ 2761 | 2762 | def extend(self, other: Iterable[Any]) -> None: 2763 | """Extend array by appending elements from the iterable. 2764 | 2765 | Present only if the wrapped Javascript object is an array. 2766 | """ 2767 | 2768 | 2769 | # from pyproxy.c 2770 | 2771 | 2772 | def create_once_callable(obj: Callable[..., Any], /) -> JsProxy: 2773 | """Wrap a Python callable in a JavaScript function that can be called once. 2774 | 2775 | After being called the proxy will decrement the reference count 2776 | of the Callable. The JavaScript function also has a ``destroy`` API that 2777 | can be used to release the proxy without calling it. 2778 | """ 2779 | return obj # type: ignore[return-value] 2780 | 2781 | 2782 | def create_proxy(obj: Any, /) -> JsProxy: 2783 | """Create a ``JsProxy`` of a ``PyProxy``. 2784 | 2785 | This allows explicit control over the lifetime of the ``PyProxy`` from 2786 | Python: call the ``destroy`` API when done. 2787 | """ 2788 | return obj 2789 | 2790 | 2791 | # from python2js 2792 | 2793 | 2794 | def to_js( 2795 | obj: Any, 2796 | /, 2797 | *, 2798 | depth: int = -1, 2799 | pyproxies: JsProxy | None = None, 2800 | create_pyproxies: bool = True, 2801 | dict_converter: Callable[[Iterable[JsProxy]], JsProxy] | None = None, 2802 | default_converter: Callable[ 2803 | [Any, Callable[[Any], JsProxy], Callable[[Any, JsProxy], None]], JsProxy 2804 | ] 2805 | | None = None, 2806 | ) -> JsProxy: 2807 | """Convert the object to JavaScript. 2808 | 2809 | This is similar to :any:`PyProxy.toJs`, but for use from Python. If the 2810 | object can be implicitly translated to JavaScript, it will be returned 2811 | unchanged. If the object cannot be converted into JavaScript, this 2812 | method will return a :any:`JsProxy` of a :any:`PyProxy`, as if you had 2813 | used :any:`pyodide.ffi.create_proxy`. 2814 | 2815 | See :ref:`type-translations-pyproxy-to-js` for more information. 2816 | 2817 | Parameters 2818 | ---------- 2819 | obj : Any 2820 | The Python object to convert 2821 | 2822 | depth : int, default=-1 2823 | The maximum depth to do the conversion. Negative numbers are treated 2824 | as infinite. Set this to 1 to do a shallow conversion. 2825 | 2826 | pyproxies: JsProxy, default = None 2827 | Should be a JavaScript ``Array``. If provided, any ``PyProxies`` generated 2828 | will be stored here. You can later use :any:`destroy_proxies` if you want 2829 | to destroy the proxies from Python (or from JavaScript you can just iterate 2830 | over the ``Array`` and destroy the proxies). 2831 | 2832 | create_pyproxies: bool, default=True 2833 | If you set this to False, :any:`to_js` will raise an error 2834 | 2835 | dict_converter: Callable[[Iterable[JsProxy]], JsProxy], default = None 2836 | This converter if provided receives a (JavaScript) iterable of 2837 | (JavaScript) pairs [key, value]. It is expected to return the 2838 | desired result of the dict conversion. Some suggested values for 2839 | this argument: 2840 | 2841 | js.Map.new -- similar to the default behavior 2842 | js.Array.from -- convert to an array of entries 2843 | js.Object.fromEntries -- convert to a JavaScript object 2844 | default_converter: Callable[[Any, Callable[[Any], JsProxy], Callable[[Any, JsProxy], None]], JsProxy], default=None 2845 | If present will be invoked whenever Pyodide does not have some built in 2846 | conversion for the object. If ``default_converter`` raises an error, the 2847 | error will be allowed to propagate. Otherwise, the object returned will 2848 | be used as the conversion. ``default_converter`` takes three arguments. 2849 | The first argument is the value to be converted. 2850 | 2851 | Here are a couple examples of converter functions. In addition to the 2852 | normal conversions, convert ``Date`` to ``datetime``: 2853 | 2854 | .. code-block:: python 2855 | 2856 | from datetime import datetime 2857 | from js import Date 2858 | def default_converter(value, _ignored1, _ignored2): 2859 | if isinstance(value, datetime): 2860 | return Date.new(value.timestamp() * 1000) 2861 | return value 2862 | 2863 | Don't create any PyProxies, require a complete conversion or raise an error: 2864 | 2865 | .. code-block:: python 2866 | 2867 | def default_converter(_value, _ignored1, _ignored2): 2868 | raise Exception("Failed to completely convert object") 2869 | 2870 | The second and third arguments are only needed for converting 2871 | containers. The second argument is a conversion function which is used 2872 | to convert the elements of the container with the same settings. The 2873 | third argument is a "cache" function which is needed to handle self 2874 | referential containers. Consider the following example. Suppose we have 2875 | a Python ``Pair`` class: 2876 | 2877 | .. code-block:: python 2878 | 2879 | class Pair: 2880 | def __init__(self, first, second): 2881 | self.first = first 2882 | self.second = second 2883 | 2884 | We can use the following ``default_converter`` to convert ``Pair`` to ``Array``: 2885 | 2886 | .. code-block:: python 2887 | 2888 | from js import Array 2889 | def default_converter(value, convert, cache): 2890 | if not isinstance(value, Pair): 2891 | return value 2892 | result = Array.new() 2893 | cache(value, result); 2894 | result.push(convert(value.first)) 2895 | result.push(convert(value.second)) 2896 | return result 2897 | 2898 | Note that we have to cache the conversion of ``value`` before converting 2899 | ``value.first`` and ``value.second``. To see why, consider a self 2900 | referential pair: 2901 | 2902 | .. code-block:: javascript 2903 | 2904 | p = Pair(0, 0); 2905 | p.first = p; 2906 | 2907 | Without ``cache(value, result);``, converting ``p`` would lead to an 2908 | infinite recurse. With it, we can successfully convert ``p`` to an Array 2909 | such that ``l[0] === l``. 2910 | """ 2911 | return obj 2912 | 2913 | 2914 | class Promise(JsProxy): 2915 | pass 2916 | 2917 | 2918 | def destroy_proxies(pyproxies: JsProxy, /) -> None: 2919 | """Destroy all PyProxies in a JavaScript array. 2920 | 2921 | pyproxies must be a JsProxy of type PyProxy[]. Intended for use with the 2922 | arrays created from the "pyproxies" argument of :any:`PyProxy.toJs` and 2923 | :any:`to_js`. This method is necessary because indexing the Array from 2924 | Python automatically unwraps the PyProxy into the wrapped Python object. 2925 | """ 2926 | pass 2927 | 2928 | 2929 | __name__ = _save_name 2930 | del _save_name 2931 | _pyodide/_importhook.py0000644000000000000000000001565714310731640014271 0ustar rootrootimport sys 2932 | from collections.abc import Sequence 2933 | from importlib.abc import Loader, MetaPathFinder 2934 | from importlib.machinery import ModuleSpec 2935 | from importlib.util import spec_from_loader 2936 | from types import ModuleType 2937 | from typing import Any 2938 | 2939 | 2940 | class JsFinder(MetaPathFinder): 2941 | def __init__(self) -> None: 2942 | self.jsproxies: dict[str, Any] = {} 2943 | 2944 | def find_spec( 2945 | self, 2946 | fullname: str, 2947 | path: Sequence[bytes | str] | None, 2948 | target: ModuleType | None = None, 2949 | ) -> ModuleSpec | None: 2950 | assert JsProxy is not None 2951 | [parent, _, child] = fullname.rpartition(".") 2952 | if parent: 2953 | parent_module = sys.modules[parent] 2954 | if not isinstance(parent_module, JsProxy): 2955 | # Not one of us. 2956 | return None 2957 | try: 2958 | jsproxy = getattr(parent_module, child) 2959 | except AttributeError: 2960 | raise ModuleNotFoundError( 2961 | f"No module named {fullname!r}", name=fullname 2962 | ) from None 2963 | if not isinstance(jsproxy, JsProxy): 2964 | raise ModuleNotFoundError( 2965 | f"No module named {fullname!r}", name=fullname 2966 | ) 2967 | else: 2968 | try: 2969 | jsproxy = self.jsproxies[fullname] 2970 | except KeyError: 2971 | return None 2972 | loader = JsLoader(jsproxy) 2973 | return spec_from_loader(fullname, loader, origin="javascript") 2974 | 2975 | def register_js_module(self, name: str, jsproxy: Any) -> None: 2976 | """ 2977 | Registers ``jsproxy`` as a JavaScript module named ``name``. The module 2978 | can then be imported from Python using the standard Python import 2979 | system. If another module by the same name has already been imported, 2980 | this won't have much effect unless you also delete the imported module 2981 | from ``sys.modules``. This is called by the JavaScript API 2982 | :any:`pyodide.registerJsModule`. 2983 | 2984 | Parameters 2985 | ---------- 2986 | name : str 2987 | Name of js module 2988 | 2989 | jsproxy : JsProxy 2990 | JavaScript object backing the module 2991 | """ 2992 | assert JsProxy is not None 2993 | if not isinstance(name, str): 2994 | raise TypeError( 2995 | f"Argument 'name' must be a str, not {type(name).__name__!r}" 2996 | ) 2997 | if not isinstance(jsproxy, JsProxy): 2998 | raise TypeError( 2999 | f"Argument 'jsproxy' must be a JsProxy, not {type(jsproxy).__name__!r}" 3000 | ) 3001 | self.jsproxies[name] = jsproxy 3002 | 3003 | def unregister_js_module(self, name: str) -> None: 3004 | """ 3005 | Unregisters a JavaScript module with given name that has been previously 3006 | registered with :any:`pyodide.registerJsModule` or 3007 | :any:`pyodide.ffi.register_js_module`. If a JavaScript module with that name 3008 | does not already exist, will raise an error. If the module has already 3009 | been imported, this won't have much effect unless you also delete the 3010 | imported module from ``sys.modules``. This is called by the JavaScript 3011 | API :any:`pyodide.unregisterJsModule`. 3012 | 3013 | Parameters 3014 | ---------- 3015 | name : str 3016 | Name of js module 3017 | """ 3018 | try: 3019 | del self.jsproxies[name] 3020 | except KeyError: 3021 | raise ValueError( 3022 | f"Cannot unregister {name!r}: no Javascript module with that name is registered" 3023 | ) from None 3024 | 3025 | 3026 | class JsLoader(Loader): 3027 | def __init__(self, jsproxy: Any) -> None: 3028 | self.jsproxy = jsproxy 3029 | 3030 | def create_module(self, spec: ModuleSpec) -> Any: 3031 | return self.jsproxy 3032 | 3033 | def exec_module(self, module: ModuleType) -> None: 3034 | pass 3035 | 3036 | # used by importlib.util.spec_from_loader 3037 | def is_package(self, fullname: str) -> bool: 3038 | return True 3039 | 3040 | 3041 | JsProxy: type | None = None 3042 | jsfinder: JsFinder = JsFinder() 3043 | register_js_module = jsfinder.register_js_module 3044 | unregister_js_module = jsfinder.unregister_js_module 3045 | 3046 | 3047 | def register_js_finder() -> None: 3048 | """A bootstrap function, called near the end of Pyodide initialization. 3049 | 3050 | It is called in ``loadPyodide`` in ``pyodide.js`` once ``_pyodide_core`` is ready 3051 | to set up the js import mechanism. 3052 | 3053 | 1. Put the right value into the global variable ``JsProxy`` so that 3054 | ``JsFinder.find_spec`` can decide whether parent module is a Js module. 3055 | 2. Add ``jsfinder`` to metapath to allow js imports. 3056 | 3057 | This needs to be a function to allow the late import from ``_pyodide_core``. 3058 | """ 3059 | import _pyodide_core 3060 | 3061 | global JsProxy 3062 | JsProxy = _pyodide_core.JsProxy 3063 | 3064 | for importer in sys.meta_path: 3065 | if isinstance(importer, JsFinder): 3066 | raise RuntimeError("JsFinder already registered") 3067 | 3068 | sys.meta_path.append(jsfinder) 3069 | 3070 | 3071 | class UnvendoredStdlibFinder(MetaPathFinder): 3072 | """ 3073 | A MetaPathFinder that handles unvendored and removed stdlib modules. 3074 | 3075 | This class simply raises an error if a stdlib module is unvendored or removed. 3076 | This needs to be added to the end of sys.meta_path, so if a unvendored stdlib 3077 | is already loaded via pyodide.loadPackage, it can be handled by the existing finder. 3078 | """ 3079 | 3080 | def __init__(self) -> None: 3081 | # `test`` is not a stdlib module, but we unvendors in anyway. 3082 | self.stdlibs = sys.stdlib_module_names | {"test"} 3083 | 3084 | # TODO: put list of unvendored stdlibs to somewhere else? 3085 | self.unvendored_stdlibs = { 3086 | "distutils", 3087 | "test", 3088 | "ssl", 3089 | "lzma", 3090 | } & self.stdlibs 3091 | 3092 | def find_spec( 3093 | self, 3094 | fullname: str, 3095 | path: Sequence[bytes | str] | None, 3096 | target: ModuleType | None = None, 3097 | ) -> ModuleSpec | None: 3098 | [parent, _, _] = fullname.partition(".") 3099 | 3100 | if not parent or parent in sys.modules or parent not in self.stdlibs: 3101 | return None 3102 | 3103 | if parent in self.unvendored_stdlibs: 3104 | raise ModuleNotFoundError( 3105 | f"The module '{parent}' is unvendored from the Python standard library in the Pyodide distribution, " 3106 | f'you can install it by calling: await micropip.install("{parent}"). ' 3107 | "See https://pyodide.org/en/stable/usage/wasm-constraints.html for more details." 3108 | ) 3109 | else: 3110 | raise ModuleNotFoundError( 3111 | f"The module '{parent}' is removed from the Python standard library in the Pyodide distribution " 3112 | "due to browser limitations. " 3113 | "See https://pyodide.org/en/stable/usage/wasm-constraints.html for more details." 3114 | ) 3115 | 3116 | 3117 | def register_unvendored_stdlib_finder() -> None: 3118 | """ 3119 | A function that adds UnvendoredStdlibFinder to the end of sys.meta_path. 3120 | 3121 | Note that this finder must be placed in the end of meta_paths 3122 | in order to prevent any unexpected side effects. 3123 | """ 3124 | 3125 | for importer in sys.meta_path: 3126 | if isinstance(importer, UnvendoredStdlibFinder): 3127 | raise RuntimeError("UnvendoredStdlibFinder already registered") 3128 | 3129 | sys.meta_path.append(UnvendoredStdlibFinder()) 3130 | -------------------------------------------------------------------------------- /runtime/pyparsing-3.0.9-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PFython/pyscript-local-runtime/f5b5efb0c33cadeff8716532f24a6daac636ff7c/runtime/pyparsing-3.0.9-py3-none-any.whl -------------------------------------------------------------------------------- /runtime/pyscript.css: -------------------------------------------------------------------------------- 1 | :not(:defined) { 2 | display: none 3 | } 4 | 5 | html{ 6 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 7 | line-height: 1.5; 8 | } 9 | 10 | .spinner::after { 11 | content: ''; 12 | box-sizing: border-box; 13 | width: 40px; 14 | height: 40px; 15 | position: absolute; 16 | top: calc(40% - 20px); 17 | left: calc(50% - 20px); 18 | border-radius: 50%; 19 | } 20 | 21 | .spinner.smooth::after { 22 | border-top: 4px solid rgba(255, 255, 255, 1); 23 | border-left: 4px solid rgba(255, 255, 255, 1); 24 | border-right: 4px solid rgba(255, 255, 255, 0); 25 | animation: spinner 0.6s linear infinite; 26 | } 27 | @keyframes spinner { 28 | to { 29 | transform: rotate(360deg); 30 | } 31 | } 32 | 33 | .label { 34 | text-align: center; 35 | width: 100%; 36 | display: block; 37 | color: rgba(255, 255, 255, 0.8); 38 | font-size: 0.8rem; 39 | margin-top: 6rem; 40 | } 41 | 42 | /* Pop-up second layer begin */ 43 | 44 | .py-overlay { 45 | position: fixed; 46 | display: flex; 47 | justify-content: center; 48 | align-items: center; 49 | color: white; 50 | top: 0; 51 | bottom: 0; 52 | left: 0; 53 | right: 0; 54 | background: rgba(0, 0, 0, 0.5); 55 | transition: opacity 500ms; 56 | visibility: hidden; 57 | color: visible; 58 | opacity: 1; 59 | } 60 | 61 | .py-overlay { 62 | visibility: visible; 63 | opacity: 1; 64 | } 65 | 66 | .py-pop-up { 67 | text-align: center; 68 | width: 600px; 69 | } 70 | 71 | .py-pop-up p { 72 | margin: 5px; 73 | } 74 | 75 | .py-pop-up a { 76 | position: absolute; 77 | color: white; 78 | text-decoration: none; 79 | font-size: 200%; 80 | top: 3.5%; 81 | right: 5%; 82 | } 83 | 84 | .py-box{ 85 | display: flex; 86 | flex-direction: row; 87 | justify-content: flex-start; 88 | } 89 | 90 | .py-box div.py-box-child * 91 | { 92 | max-width: 100%; 93 | } 94 | 95 | .py-repl-box{ 96 | flex-direction: column; 97 | } 98 | 99 | .editor-box{ 100 | --tw-border-opacity: 1; 101 | border-color: rgba(209, 213, 219, var(--tw-border-opacity)); 102 | border-width: 1px; 103 | position: relative; 104 | --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); 105 | --tw-ring-offset-width: 0px; 106 | --tw-ring-offset-color: #fff; 107 | --tw-ring-color: rgba(59, 130, 246, 0.5); 108 | --tw-ring-offset-shadow: 0 0 #0000; 109 | --tw-ring-shadow: 0 0 #0000; 110 | --tw-shadow: 0 0 #0000; 111 | position: relative; 112 | 113 | box-sizing: border-box; 114 | border-width: 1px; 115 | border-style: solid; 116 | border-color: rgb(209, 213, 219) 117 | } 118 | 119 | .editor-box:hover button{ 120 | opacity: 1; 121 | } 122 | 123 | .repl-play-button{ 124 | opacity: 0; 125 | bottom: 0.25rem; 126 | right: 0.25rem; 127 | position: absolute; 128 | padding: 0; 129 | line-height: inherit; 130 | color: inherit; 131 | cursor: pointer; 132 | background-color: transparent; 133 | background-image: none; 134 | -webkit-appearance: button; 135 | text-transform: none; 136 | font-family: inherit; 137 | font-size: 100%; 138 | margin: 0; 139 | text-rendering: auto; 140 | letter-spacing: normal; 141 | word-spacing: normal; 142 | line-height: normal; 143 | text-transform: none; 144 | text-indent: 0px; 145 | text-shadow: none; 146 | display: inline-block; 147 | text-align: center; 148 | align-items: flex-start; 149 | cursor: default; 150 | box-sizing: border-box; 151 | background-color: -internal-light-dark(rgb(239, 239, 239), rgb(59, 59, 59)); 152 | margin: 0em; 153 | padding: 1px 6px; 154 | border: 0; 155 | } 156 | 157 | .repl-play-button:hover{ 158 | opacity: 1; 159 | } 160 | 161 | .py-title{ 162 | text-transform: uppercase; 163 | text-align: center; 164 | } 165 | 166 | .py-title h1{ 167 | font-weight: 700; 168 | font-size: 1.875rem; 169 | } 170 | 171 | .py-input{ 172 | padding: 0.5rem; 173 | --tw-border-opacity: 1; 174 | border-color: rgba(209, 213, 219, var(--tw-border-opacity)); 175 | border-width: 1px; 176 | border-radius: 0.25rem; 177 | margin-right: 0.75rem; 178 | border-style: solid; 179 | width: auto; 180 | } 181 | 182 | .py-box input.py-input{ 183 | width: -webkit-fill-available; 184 | } 185 | 186 | .central-content{ 187 | max-width: 20rem; 188 | margin-left: auto; 189 | margin-right: auto; 190 | } 191 | 192 | input { 193 | text-rendering: auto; 194 | color: -internal-light-dark(black, white); 195 | letter-spacing: normal; 196 | word-spacing: normal; 197 | line-height: normal; 198 | text-transform: none; 199 | text-indent: 0px; 200 | text-shadow: none; 201 | display: inline-block; 202 | text-align: start; 203 | appearance: auto; 204 | -webkit-rtl-ordering: logical; 205 | cursor: text; 206 | background-color: -internal-light-dark(rgb(255, 255, 255), rgb(59, 59, 59)); 207 | margin: 0em; 208 | padding: 1px 2px; 209 | border-width: 2px; 210 | border-style: inset; 211 | border-color: -internal-light-dark(rgb(118, 118, 118), rgb(133, 133, 133)); 212 | border-image: initial; 213 | } 214 | 215 | .py-button{ 216 | --tw-text-opacity: 1; 217 | color: rgba(255, 255, 255, var(--tw-text-opacity)); 218 | padding: 0.5rem; 219 | --tw-bg-opacity: 1; 220 | background-color: rgba(37, 99, 235, var(--tw-bg-opacity)); 221 | --tw-border-opacity: 1; 222 | border-color: rgba(37, 99, 235, var(--tw-border-opacity)); 223 | border-width: 1px; 224 | border-radius: 0.25rem; 225 | } 226 | 227 | .py-li-element p{ 228 | margin: 5px; 229 | } 230 | 231 | .py-li-element p{ 232 | display: inline; 233 | } 234 | 235 | button, input, optgroup, select, textarea { 236 | font-family: inherit; 237 | font-size: 100%; 238 | line-height: 1.15; 239 | margin: 0; 240 | } 241 | 242 | .line-through { 243 | text-decoration: line-through; 244 | } 245 | -------------------------------------------------------------------------------- /runtime/repodata.json: -------------------------------------------------------------------------------- 1 | {"info": {"arch": "wasm32", "platform": "emscripten_3_1_14", "version": "0.21.3", "python": "3.10.2"}, "packages": {"asciitree": {"name": "asciitree", "version": "0.3.3", "file_name": "asciitree-0.3.3-py3-none-any.whl", "install_dir": "site", "sha256": "c734ae18f61e652ea03b35701b49d814ec5e81559efa45f20cf364903c86eebe", "depends": [], "imports": ["asciitree"]}, "astropy": {"name": "astropy", "version": "5.1", "file_name": "astropy-5.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "d0841fe317e885dce4e5de5ba1e62ada16ab5f25f56521e21dd69f7fb610959c", "depends": ["distutils", "packaging", "numpy", "pyerfa", "pyyaml"], "imports": ["astropy", "astropy.config", "astropy.constants", "astropy.convolution", "astropy.coordinates", "astropy.cosmology", "astropy.extern", "astropy.io", "astropy.modeling", "astropy.nddata", "astropy.samp", "astropy.stats", "astropy.table", "astropy.time", "astropy.timeseries", "astropy.uncertainty", "astropy.units", "astropy.utils", "astropy.visualization", "astropy.wcs"]}, "atomicwrites": {"name": "atomicwrites", "version": "1.4.0", "file_name": "atomicwrites-1.4.0-py2.py3-none-any.whl", "install_dir": "site", "sha256": "0db2d2f01c42f4d39fc929b28212b3fd689257008ab310e9d59e79c6a6c443c9", "depends": [], "imports": ["atomicwrites"]}, "attrs": {"name": "attrs", "version": "21.4.0", "file_name": "attrs-21.4.0-py2.py3-none-any.whl", "install_dir": "site", "sha256": "1cc07d883ef68c68e5b43d6df80ad6da5861ed840c49e7fa29ac542e9b012015", "depends": ["six"], "imports": ["attr"]}, "autograd": {"name": "autograd", "version": "1.4", "file_name": "autograd-1.4-py3-none-any.whl", "install_dir": "site", "sha256": "e6f6ce5bf859b6f534ad31f43efdde7e33b7906d7817d87e92b41162bf68bc29", "depends": ["numpy", "future"], "imports": ["autograd"], "unvendored_tests": true}, "autograd-tests": {"name": "autograd-tests", "version": "1.4", "depends": ["autograd"], "imports": [], "file_name": "autograd-tests.tar", "install_dir": "site", "sha256": "bd2e82e55cf351462189ea215410d82d1a2c75bef9cd5d6f13483d36c15c7aa9"}, "beautifulsoup4": {"name": "beautifulsoup4", "version": "4.11.1", "file_name": "beautifulsoup4-4.11.1-py3-none-any.whl", "install_dir": "site", "sha256": "cd5a0308b5f2286f34d607496114f1d53b869b6b1a6605e2bc1ee3d86dd62e66", "depends": ["soupsieve"], "imports": ["bs4"], "unvendored_tests": true}, "beautifulsoup4-tests": {"name": "beautifulsoup4-tests", "version": "4.11.1", "depends": ["beautifulsoup4"], "imports": [], "file_name": "beautifulsoup4-tests.tar", "install_dir": "site", "sha256": "327bd1f26eb1a5a14b52b4883ddc45168752f3db20b104e33142a7ec65cb2a64"}, "biopython": {"name": "biopython", "version": "1.79", "file_name": "biopython-1.79-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "cef42a4f2038805d35359a32fc397067feaaf701445a35d4cc08491a6af02937", "depends": ["numpy"], "imports": ["Bio"]}, "bitarray": {"name": "bitarray", "version": "2.5.1", "file_name": "bitarray-2.5.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "0da428994a905514992ec49e2cc2fbbf9561f634fc24dffb564138514223c5b1", "depends": [], "imports": ["bitarray"], "unvendored_tests": true}, "bitarray-tests": {"name": "bitarray-tests", "version": "2.5.1", "depends": ["bitarray"], "imports": [], "file_name": "bitarray-tests.tar", "install_dir": "site", "sha256": "89a3d0999e586824482297a58ccd0e2f10e68635f186d558d7ad2cf33f126f98"}, "bleach": {"name": "bleach", "version": "5.0.0", "file_name": "bleach-5.0.0-py3-none-any.whl", "install_dir": "site", "sha256": "0857b3c773e40aa4b6c0c98fcf725d2452057fc2e22bf46464fea2ab8f696bd1", "depends": ["webencodings", "packaging", "six"], "imports": ["bleach"]}, "bokeh": {"name": "bokeh", "version": "2.4.3", "file_name": "bokeh-2.4.3-py3-none-any.whl", "install_dir": "site", "sha256": "8e2683ee5f2428146194ca56c80d0fbaf560313ff8171ab6d2169178c7bd98e0", "depends": ["distutils", "numpy", "jinja2", "pillow", "python-dateutil", "six", "typing-extensions", "pyyaml"], "imports": ["bokeh"]}, "boost-histogram": {"name": "boost-histogram", "version": "1.3.1", "file_name": "boost_histogram-1.3.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "20d00668ca3ce245ea3d4b938177f4a88caa9ac7b8f0de2ddb39ba61d472193e", "depends": ["numpy"], "imports": ["boost_histogram"]}, "brotli": {"name": "brotli", "version": "1.0.9", "file_name": "Brotli-1.0.9-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "4ae38854b47d1902d05bb10fdbd7f88f7576982849290595ef429376f5b0ce0e", "depends": [], "imports": ["brotli"]}, "certifi": {"name": "certifi", "version": "2022.6.15", "file_name": "certifi-2022.6.15-py3-none-any.whl", "install_dir": "site", "sha256": "b6e4d5d10aa72d79146f30e1d9126b133d550c2e7e267af0f44acb9a76dbc355", "depends": [], "imports": ["certifi"]}, "cffi": {"name": "cffi", "version": "1.15.0", "file_name": "cffi-1.15.0-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "a4cb85a32d9a505420798659cf0670a0669898fe819da70f53ed527ccac62d73", "depends": ["pycparser"], "imports": ["cffi"]}, "cffi_example": {"name": "cffi_example", "version": "0.1", "file_name": "cffi_example-0.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "5d384569e22ea92ad636b262eaa868b2d18b7ceaaabc19070f57204b492b13c8", "depends": ["cffi"], "imports": ["cffi_example"]}, "cftime": {"name": "cftime", "version": "1.6.0", "file_name": "cftime-1.6.0-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "a334f8d95422085523258e8c8c409035b46e2ec891635cd17a30adee18e8862b", "depends": ["numpy"], "imports": ["cftime"]}, "clapack": {"name": "CLAPACK", "version": "3.2.1", "file_name": "CLAPACK-3.2.1.zip", "install_dir": "dynlib", "sha256": "4f390828cf162bbc208641c14157d84ff83ef62efe74c94e102052c0364e4d90", "shared_library": true, "depends": [], "imports": ["CLAPACK"]}, "cloudpickle": {"name": "cloudpickle", "version": "2.1.0", "file_name": "cloudpickle-2.1.0-py3-none-any.whl", "install_dir": "site", "sha256": "65020408381b9a8b6b3c13853d88a2a8aa3b21d27cdbcbcb6ef925018e857b69", "depends": [], "imports": ["cloudpickle"]}, "cmyt": {"name": "cmyt", "version": "1.0.4", "file_name": "cmyt-1.0.4-py3-none-any.whl", "install_dir": "site", "sha256": "dc0f728922da60ba3645186c6754caacea3cfdda58cd68d853ca22c0120bffc6", "depends": ["colorspacious", "matplotlib", "more-itertools", "numpy"], "imports": ["cmyt"], "unvendored_tests": true}, "cmyt-tests": {"name": "cmyt-tests", "version": "1.0.4", "depends": ["cmyt"], "imports": [], "file_name": "cmyt-tests.tar", "install_dir": "site", "sha256": "942cb995f1263b2c7840d7d6dff20cfd3c253029a609a420c3eebca94a740f89"}, "colorspacious": {"name": "colorspacious", "version": "1.1.2", "file_name": "colorspacious-1.1.2-py2.py3-none-any.whl", "install_dir": "site", "sha256": "756e48f0689f6423eec3eb4677bb86f94935b3119fbd3c2d1696a867ed70a1c9", "depends": ["numpy"], "imports": ["colorspacious"]}, "cpp-exceptions-test": {"name": "cpp-exceptions-test", "version": "0.1", "file_name": "cpp-exceptions-test-0.1.zip", "install_dir": "dynlib", "sha256": "4c1154a875aa2242d93288f5b9586bc68cdb234e2d613ab10dbebb4bda484f97", "shared_library": true, "depends": [], "imports": ["cpp-exceptions-test"]}, "cryptography": {"name": "cryptography", "version": "37.0.3", "file_name": "cryptography-37.0.3-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "9b333d372effb975aa8268bf2439050c9aaf6d79da4c080420a4f4cbae27006a", "depends": ["openssl", "six", "cffi"], "imports": ["cryptography", "cryptography.fernet", "cryptography.hazmat", "cryptography.utils", "cryptography.x509"]}, "cssselect": {"name": "cssselect", "version": "1.1.0", "file_name": "cssselect-1.1.0-py2.py3-none-any.whl", "install_dir": "site", "sha256": "602af676bcecdee4de9ce48c8ce1f6ea87f78acb1bb547fb65e5f430d5184040", "depends": [], "imports": ["cssselect"]}, "cycler": {"name": "cycler", "version": "0.11.0", "file_name": "cycler-0.11.0-py3-none-any.whl", "install_dir": "site", "sha256": "c7b38d6bc3c9c6d2c4cfa7080f230551e68d6a68bf93042052e52fc1db969c81", "depends": ["six"], "imports": ["cycler"]}, "cytoolz": {"name": "cytoolz", "version": "0.11.2", "file_name": "cytoolz-0.11.2-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "52836eaf5f7799fa23fb635b72f469290e33c0a7d65e95ce4c4e7858fa71dbf8", "depends": ["nose", "toolz"], "imports": ["cytoolz"], "unvendored_tests": true}, "cytoolz-tests": {"name": "cytoolz-tests", "version": "0.11.2", "depends": ["cytoolz"], "imports": [], "file_name": "cytoolz-tests.tar", "install_dir": "site", "sha256": "47f62883057eebe1c08f3bd54987b4d04cb293d5371629a5af4e83a5303d6d7d"}, "decorator": {"name": "decorator", "version": "5.1.1", "file_name": "decorator-5.1.1-py3-none-any.whl", "install_dir": "site", "sha256": "650982e3cc2727e576c28476f0e10bfdd4daa1743518d232aa1465b50ee72e75", "depends": [], "imports": ["decorator"]}, "demes": {"name": "demes", "version": "0.2.2", "file_name": "demes-0.2.2-py3-none-any.whl", "install_dir": "site", "sha256": "9a22fddbd470699c257797c551f38754327874c4d1452e350dbfe3b241837c52", "depends": ["attrs", "ruamel.yaml"], "imports": ["demes"]}, "distlib": {"name": "distlib", "version": "0.3.4", "file_name": "distlib-0.3.4-py2.py3-none-any.whl", "install_dir": "site", "sha256": "d015868654378d287fdd18a1df7c8a5c66483acbee6a8079cfddbf1fef07b1ef", "depends": [], "imports": ["distlib"]}, "distutils": {"name": "distutils", "version": "1.0", "file_name": "distutils.tar", "install_dir": "lib", "sha256": "26899ca0b7d1c3f7ce4f2e43f3a701d51ad81a32f46fc374daa7b3e6fbfcf581", "depends": [], "imports": ["distutils"]}, "docutils": {"name": "docutils", "version": "0.18.1", "file_name": "docutils-0.18.1-py2.py3-none-any.whl", "install_dir": "site", "sha256": "80e7ec52e38cca62ceb183569948f8c08b7d3ba4370395541dae21abe9777bef", "depends": [], "imports": ["docutils"]}, "fonttools": {"name": "fonttools", "version": "4.33.3", "file_name": "fonttools-4.33.3-py3-none-any.whl", "install_dir": "site", "sha256": "8f8dff37afdded23a232573472e5bbdfc6cd37d403bd10ffc4029000185cb55a", "depends": [], "imports": ["fontTools"]}, "fpcast-test": {"name": "fpcast-test", "version": "0.1", "file_name": "fpcast_test-0.1.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "62f50838af59664dbe5d53a47e755d5ac16d4428370c123e33d32493a4119663", "depends": [], "imports": ["fpcast_test"]}, "freesasa": {"name": "freesasa", "version": "2.1.0", "file_name": "freesasa-2.1.0-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "54a1a2a4ec5bf46ee3a371fd08a034170f8a005891c926b75749c8f5ce7bd7b8", "depends": [], "imports": ["freesasa"]}, "future": {"name": "future", "version": "0.18.2", "file_name": "future-0.18.2-py3-none-any.whl", "install_dir": "site", "sha256": "20245b3fdd8afc05ba875ba24e54dc3e34dc68634492e57d0223cdfe96bcbf45", "depends": [], "imports": ["future"], "unvendored_tests": true}, "future-tests": {"name": "future-tests", "version": "0.18.2", "depends": ["future"], "imports": [], "file_name": "future-tests.tar", "install_dir": "site", "sha256": "156e44ce503db2ce0c8e9c7d988b746cb3ff2a12f9c9683315a290388155e2c2"}, "galpy": {"name": "galpy", "version": "1.8.0", "file_name": "galpy-1.8.0-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "9e2595a13264cfea4f7352a4482906e313aa740f9b57978a626b7d27985d257e", "depends": ["numpy", "scipy", "matplotlib", "astropy", "future", "setuptools"], "imports": ["galpy", "galpy.potential", "galpy.orbit", "galpy.actionAngle", "galpy.df"]}, "geos": {"name": "geos", "version": "3.10.3", "file_name": "geos-3.10.3.zip", "install_dir": "dynlib", "sha256": "d333410afd3314b202644b8e163a99f6275594e3845feb7c794a46079e2fae2f", "shared_library": true, "depends": [], "imports": ["geos"]}, "gmpy2": {"name": "gmpy2", "version": "2.1.2", "file_name": "gmpy2-2.1.2-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "70426608c07f12e1f29d447388cf0d7fa2064479db6e50b44acd76eeb7f218cb", "depends": [], "imports": ["gmpy2"]}, "gsw": {"name": "gsw", "version": "3.4.0", "file_name": "gsw-3.4.0-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "597efb011a880c1bdd1a6817ab68abf84a8c6ae22b86dfba618febbc6f54a7fa", "depends": ["numpy"], "imports": ["gsw"], "unvendored_tests": true}, "gsw-tests": {"name": "gsw-tests", "version": "3.4.0", "depends": ["gsw"], "imports": [], "file_name": "gsw-tests.tar", "install_dir": "site", "sha256": "fb26a01105ba8bc3afcb8fad52ecb610b7ffcdf3960adb64352764b2aae142f2"}, "html5lib": {"name": "html5lib", "version": "1.1", "file_name": "html5lib-1.1-py2.py3-none-any.whl", "install_dir": "site", "sha256": "85d62f22867a7f2d0772e12cbd937293db288d8e81048089b3cc2f05b18813da", "depends": ["webencodings", "six"], "imports": ["html5lib"]}, "imageio": {"name": "imageio", "version": "2.19.3", "file_name": "imageio-2.19.3-py3-none-any.whl", "install_dir": "site", "sha256": "8083578d77691efce4b8c01fddd3f70ba76e62d7e5a92798537e6fa31ab22255", "depends": ["numpy", "pillow"], "imports": ["imageio"]}, "iniconfig": {"name": "iniconfig", "version": "1.1.1", "file_name": "iniconfig-1.1.1-py2.py3-none-any.whl", "install_dir": "site", "sha256": "c02b746b77ce65d040a0e865a0ff8d78ab2723c1c7b726babce0f35bca4de45d", "depends": [], "imports": ["iniconfig"]}, "jedi": {"name": "jedi", "version": "0.18.1", "file_name": "jedi-0.18.1-py2.py3-none-any.whl", "install_dir": "site", "sha256": "1e84dfbd31481652a8da216103ec30ce326c8f7491d1541698f0f7502f7fbffe", "depends": ["parso"], "imports": ["jedi"], "unvendored_tests": true}, "jedi-tests": {"name": "jedi-tests", "version": "0.18.1", "depends": ["jedi"], "imports": [], "file_name": "jedi-tests.tar", "install_dir": "site", "sha256": "4f5ebcbe581d728c109a78f569545a19f73bdf6dd1dd82f97947ac7c52ea1c35"}, "jinja2": {"name": "Jinja2", "version": "3.1.2", "file_name": "Jinja2-3.1.2-py3-none-any.whl", "install_dir": "site", "sha256": "d5e4adf74d525e8af788051311f280ea5b4751db505035fc24e0fd9473318f5e", "depends": ["markupsafe"], "imports": ["jinja2"]}, "joblib": {"name": "joblib", "version": "1.1.0", "file_name": "joblib-1.1.0-py2.py3-none-any.whl", "install_dir": "site", "sha256": "44a361dc938df87706d48b27a8383fdd0eb284b5f9793432aa4ec8994cbb8dd3", "depends": ["distutils"], "imports": ["joblib"], "unvendored_tests": true}, "joblib-tests": {"name": "joblib-tests", "version": "1.1.0", "depends": ["joblib"], "imports": [], "file_name": "joblib-tests.tar", "install_dir": "site", "sha256": "6e1b7a79bd8f7dee06eb6515737bc6b59d89332de105bf169a5f0552f137d168"}, "jsonschema": {"name": "jsonschema", "version": "4.6.0", "file_name": "jsonschema-4.6.0-py3-none-any.whl", "install_dir": "site", "sha256": "d4d031feff601c4c555fb0bdb25f082ec0f2c5ffdaf93c0aa60fc7380f7a8b85", "depends": ["attrs", "pyrsistent"], "imports": ["jsonschema"], "unvendored_tests": true}, "jsonschema-tests": {"name": "jsonschema-tests", "version": "4.6.0", "depends": ["jsonschema"], "imports": [], "file_name": "jsonschema-tests.tar", "install_dir": "site", "sha256": "bc57941f584478c0e6156d1f90eff6a75d8c9519ff4e60a6eb4b138b64585aa9"}, "kiwisolver": {"name": "kiwisolver", "version": "1.4.3", "file_name": "kiwisolver-1.4.3-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "5d36ad19e11c4bc8f392765a427e5a168fff6fc712c74e805d68c7ec1fd9817c", "depends": [], "imports": ["kiwisolver"]}, "lazy-object-proxy": {"name": "lazy-object-proxy", "version": "1.7.1", "file_name": "lazy_object_proxy-1.7.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "1eb616370e392ca7c364c03748f4b39e773b1a863b4673c6898e3479ae25da78", "depends": [], "imports": ["lazy_object_proxy"]}, "libmagic": {"name": "libmagic", "version": "5.42", "file_name": "libmagic-5.42.zip", "install_dir": "dynlib", "sha256": "408838e2f17bfc9009400ac54e5d29c9a3f8033f0cfbd75e73d1c59d9e5c8b0c", "shared_library": true, "depends": [], "imports": ["libmagic"]}, "logbook": {"name": "logbook", "version": "1.5.3", "file_name": "Logbook-1.5.3-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "f2b330c25a3c8b3378e3418f716d8c14770004b6f2cbcb4017af6c87e0700509", "depends": ["distutils", "setuptools"], "imports": ["logbook"]}, "lxml": {"name": "lxml", "version": "4.9.0", "file_name": "lxml-4.9.0-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "2350ad095c9597037f825e57c10a342c12c29fb5acb93564b44518e56c6f558c", "depends": ["beautifulsoup4", "cssselect", "html5lib"], "imports": ["lxml", "lxml.etree", "lxml.objectify"]}, "lzma": {"name": "lzma", "version": "1.0.0", "file_name": "lzma-1.0.0.zip", "install_dir": "lib", "sha256": "4767518cffecbd04fd061f3af79808938d47c5582c3b0752233668cb4b7cf664", "shared_library": true, "depends": [], "imports": ["lzma"]}, "markupsafe": {"name": "MarkupSafe", "version": "2.1.1", "file_name": "MarkupSafe-2.1.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "eb592c2e82638f8c837a375cf2b4b32d6c755f659e8691bff3d4877509f828d7", "depends": [], "imports": ["markupsafe"]}, "matplotlib": {"name": "matplotlib", "version": "3.5.2", "file_name": "matplotlib-3.5.2-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "3848a3c48799259a89b16c45a98049a21c9f8e1cf265d754d20620ebb144daa8", "depends": ["cycler", "distutils", "fonttools", "kiwisolver", "numpy", "packaging", "pillow", "pyparsing", "python-dateutil", "pytz"], "imports": ["matplotlib", "mpl_toolkits"], "unvendored_tests": true}, "matplotlib-tests": {"name": "matplotlib-tests", "version": "3.5.2", "depends": ["matplotlib"], "imports": [], "file_name": "matplotlib-tests.tar", "install_dir": "site", "sha256": "e89d34b6db45ed83c604d144e47b5c995d49aec028da128c2f216714394a3318"}, "micropip": {"name": "micropip", "version": "0.1", "file_name": "micropip-0.1-py3-none-any.whl", "install_dir": "site", "sha256": "1cf8f43f060c716052630251637f7c0f1192a2f4d33503496ddd9f198349534f", "depends": ["pyparsing", "packaging", "distutils"], "imports": ["micropip"]}, "mne": {"name": "mne", "version": "1.0.3", "file_name": "mne-1.0.3-py3-none-any.whl", "install_dir": "site", "sha256": "7404ef8b143476fff7a7e536b61a43729a9dc26bfb61978c3891794bee9a0755", "depends": ["distutils", "numpy", "scipy", "setuptools", "decorator"], "imports": ["mne"], "unvendored_tests": true}, "mne-tests": {"name": "mne-tests", "version": "1.0.3", "depends": ["mne"], "imports": [], "file_name": "mne-tests.tar", "install_dir": "site", "sha256": "d19aaa1def0cbe0786f9a730dda4aece5ae4dabee284192b938b6a0fe2f35d6b"}, "more-itertools": {"name": "more-itertools", "version": "8.13.0", "file_name": "more_itertools-8.13.0-py3-none-any.whl", "install_dir": "site", "sha256": "3c7ef76ffed9295aae5a610f38937652210cae446de86aa42ebe10835adc9dea", "depends": [], "imports": ["more_itertools"]}, "mpmath": {"name": "mpmath", "version": "1.2.1", "file_name": "mpmath-1.2.1-py3-none-any.whl", "install_dir": "site", "sha256": "1016b9799a78079fc859d4d0413cf1fcdff3776f75d50b245eb59b1619474c3a", "depends": [], "imports": ["mpmath"], "unvendored_tests": true}, "mpmath-tests": {"name": "mpmath-tests", "version": "1.2.1", "depends": ["mpmath"], "imports": [], "file_name": "mpmath-tests.tar", "install_dir": "site", "sha256": "d5c721e3a72738422bb27285afdef6ccf7be5ff4791250388d5b16bfb74eaf17"}, "msgpack": {"name": "msgpack", "version": "1.0.4", "file_name": "msgpack-1.0.4-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "c252e07df7d3ff7d8e6da065632858d3f891311b14ab437e8d7efd7f9d07e56e", "depends": [], "imports": ["msgpack"]}, "msprime": {"name": "msprime", "version": "1.2.0", "file_name": "msprime-1.2.0-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "084fc2bbb45e139b25bd70c817634d543d949bdd521d57922b2cd76e866dad9e", "depends": ["numpy", "newick", "tskit", "demes"], "imports": ["msprime"]}, "networkx": {"name": "networkx", "version": "2.8.4", "file_name": "networkx-2.8.4-py3-none-any.whl", "install_dir": "site", "sha256": "8513e89bf0cfcb995b34cbceff684bf83f01e241d35813f7a304c3a100d2c38b", "depends": ["decorator", "setuptools", "matplotlib", "numpy"], "imports": ["networkx", "networkx.algorithms", "networkx.algorithms.approximation", "networkx.algorithms.assortativity", "networkx.algorithms.bipartite", "networkx.algorithms.centrality", "networkx.algorithms.chordal", "networkx.algorithms.coloring", "networkx.algorithms.community", "networkx.algorithms.components", "networkx.algorithms.connectivity", "networkx.algorithms.flow", "networkx.algorithms.isomorphism", "networkx.algorithms.link_analysis", "networkx.algorithms.node_classification", "networkx.algorithms.operators", "networkx.algorithms.shortest_paths", "networkx.algorithms.traversal", "networkx.algorithms.tree", "networkx.classes", "networkx.drawing", "networkx.generators", "networkx.linalg", "networkx.readwrite", "networkx.readwrite.json_graph", "networkx.utils"], "unvendored_tests": true}, "networkx-tests": {"name": "networkx-tests", "version": "2.8.4", "depends": ["networkx"], "imports": [], "file_name": "networkx-tests.tar", "install_dir": "site", "sha256": "fe137e813d7e0d6563627a201fd2da7f727cdb64e07ebbf99280f79a9eead9f2"}, "newick": {"name": "newick", "version": "1.3.2", "file_name": "newick-1.3.2-py2.py3-none-any.whl", "install_dir": "site", "sha256": "831e5723347368d00e40478a2df8e5a3cc2aace6f1e6e95a515ff5346b2c6c94", "depends": [], "imports": ["newick"]}, "nlopt": {"name": "nlopt", "version": "2.7.0", "file_name": "nlopt-2.7.0-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "92124424bf88402e4d0d4ff113a53dbb28c5abe5c52b8f6c1fdabe39a29ddd02", "depends": ["numpy"], "imports": ["nlopt"]}, "nltk": {"name": "nltk", "version": "3.7", "file_name": "nltk-3.7-py3-none-any.whl", "install_dir": "site", "sha256": "0e9ea8e990d646b69e6cf43b537e1cd3bc343fbaf264d4cb17871c2e047c34d5", "depends": ["regex"], "imports": ["nltk"], "unvendored_tests": true}, "nltk-tests": {"name": "nltk-tests", "version": "3.7", "depends": ["nltk"], "imports": [], "file_name": "nltk-tests.tar", "install_dir": "site", "sha256": "cc82f65a6642b144aac9e1ffb28463a74bc5e3ce7b9d5447642de2a14ce8fe50"}, "nose": {"name": "nose", "version": "1.3.7", "file_name": "nose-1.3.7-py3-none-any.whl", "install_dir": "site", "sha256": "45f5c1fe920134a4ee47ea529aafc5e6e0909ac063c3b4ef992607b107c25714", "depends": ["setuptools"], "imports": ["nose"]}, "numcodecs": {"name": "numcodecs", "version": "0.9.1", "file_name": "numcodecs-0.9.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "897e3e3a3c4f53eba437eb4c2e64e1cbf496de4881d25d65110e27906c3e6062", "depends": ["numpy", "msgpack"], "imports": ["numcodecs"], "unvendored_tests": true}, "numcodecs-tests": {"name": "numcodecs-tests", "version": "0.9.1", "depends": ["numcodecs"], "imports": [], "file_name": "numcodecs-tests.tar", "install_dir": "site", "sha256": "8a8cd93ae6b5a151b6502fc9db95bbea2887e038503cb10baf579108360b4fa1"}, "numpy": {"name": "numpy", "version": "1.23.0", "file_name": "numpy-1.22.4-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "c099e586aea0d4bdee09cd4b2f82977e6c7b8bcb8b97fdeee2c66c419fa6ae23", "depends": [], "imports": ["numpy"], "unvendored_tests": true}, "numpy-tests": {"name": "numpy-tests", "version": "1.23.0", "depends": ["numpy"], "imports": [], "file_name": "numpy-tests.tar", "install_dir": "site", "sha256": "8603dc12b098e782773a9e596fa7777248111713c15febe388c6f22832915fb3"}, "opencv-python": {"name": "opencv-python", "version": "4.6.0.66", "file_name": "opencv_python-4.6.0.66-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "89edf5cff168c9bb03822331bb510ed8b9972c21ee55c67b0bdcb5c339b02883", "depends": ["numpy"], "imports": ["cv2"]}, "openssl": {"name": "openssl", "version": "1.1.1n", "file_name": "openssl-1.1.1n.zip", "install_dir": "dynlib", "sha256": "c5cd267f97352571c0947721609ee5da62da179143d9b15c9632ca3aaa810f19", "shared_library": true, "depends": [], "imports": ["openssl"]}, "optlang": {"name": "optlang", "version": "1.5.2", "file_name": "optlang-1.5.2-py2.py3-none-any.whl", "install_dir": "site", "sha256": "741281c442656a7d6cbbd9bbdc9c788210df9e8815450ef8a499f60b2b9aeecd", "depends": ["sympy", "six", "swiglpk"], "imports": ["optlang", "optlang.glpk_interface", "optlang.symbolics"], "unvendored_tests": true}, "optlang-tests": {"name": "optlang-tests", "version": "1.5.2", "depends": ["optlang"], "imports": [], "file_name": "optlang-tests.tar", "install_dir": "site", "sha256": "f913de27094634a35990fbbec790344a559080c9694363f9c42f910987e4ab85"}, "packaging": {"name": "packaging", "version": "21.3", "file_name": "packaging-21.3-py3-none-any.whl", "install_dir": "site", "sha256": "0e4f5e51b583761f76755341a17e36b5020ded6b55a17d0256234af703039d0c", "depends": ["pyparsing"], "imports": ["packaging"]}, "pandas": {"name": "pandas", "version": "1.4.2", "file_name": "pandas-1.4.2-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "10f521e2a592cbd06474a8cd906fc07542e5a7b5d42cb3a3785fa4f8ec004b44", "depends": ["distutils", "numpy", "python-dateutil", "pytz", "setuptools"], "imports": ["pandas"], "unvendored_tests": true}, "pandas-tests": {"name": "pandas-tests", "version": "1.4.2", "depends": ["pandas"], "imports": [], "file_name": "pandas-tests.tar", "install_dir": "site", "sha256": "3bd7d4721d45ad6b226e1023dc83040526e3dc9504e2ea37c4c556327da8ac37"}, "parso": {"name": "parso", "version": "0.8.3", "file_name": "parso-0.8.3-py2.py3-none-any.whl", "install_dir": "site", "sha256": "a7a735e8b6eeef0d928caf868b65d3b1875d697be292b258408d7edd0666478a", "depends": [], "imports": ["parso"]}, "patsy": {"name": "patsy", "version": "0.5.2", "file_name": "patsy-0.5.2-py2.py3-none-any.whl", "install_dir": "site", "sha256": "3a1d200882ec4fe6547ebae32b6423b910e403b8c3d4f778d12d9629e0bb5bc8", "depends": ["numpy", "six"], "imports": ["patsy"], "unvendored_tests": true}, "patsy-tests": {"name": "patsy-tests", "version": "0.5.2", "depends": ["patsy"], "imports": [], "file_name": "patsy-tests.tar", "install_dir": "site", "sha256": "0e323ad770144b25518b6022530fd92ce7b46c42cc6c6d571bfa98eaeb09f113"}, "pillow": {"name": "Pillow", "version": "9.1.1", "file_name": "PIL-9.1.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "0b0f1d91a3f211ee4c715d0c2d5f5867e7d781653826194492b2b18e33e7790e", "depends": [], "imports": ["PIL"]}, "pkgconfig": {"name": "pkgconfig", "version": "1.5.5", "file_name": "pkgconfig-1.5.5-py3-none-any.whl", "install_dir": "site", "sha256": "cd7b22854b11aea25c120dc23d63ee39efc983054c5c36dea88db7f3929fd09f", "depends": [], "imports": ["pkgconfig"]}, "pluggy": {"name": "pluggy", "version": "1.0.0", "file_name": "pluggy-1.0.0-py2.py3-none-any.whl", "install_dir": "site", "sha256": "ac3089d4f2f77b2e2a92bac3da0299f79a2b0c093594df85a06cd2a325529a49", "depends": [], "imports": ["pluggy"]}, "py": {"name": "py", "version": "1.11.0", "file_name": "py-1.11.0-py2.py3-none-any.whl", "install_dir": "site", "sha256": "6b393ac3c5565ca3cafbb54583b52c391b81bafed194364c050688119d079806", "depends": [], "imports": ["py", "py.code"]}, "pyb2d": {"name": "pyb2d", "version": "0.7.2", "file_name": "b2d-0.7.2-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "f848bd550292dec2029c0782981092f88de3e8b048f8016c091505f1171ac6d0", "depends": ["numpy", "pydantic", "setuptools"], "imports": ["b2d", "b2d.testbed"]}, "pyclipper": {"name": "pyclipper", "version": "1.3.0.post3", "file_name": "pyclipper-1.3.0.post3-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "063fa1ac68071b72f077ad6c36d7f464962e2fc01692a419696477f40e8a88c4", "depends": [], "imports": ["pyclipper"]}, "pycparser": {"name": "pycparser", "version": "2.21", "file_name": "pycparser-2.21-py2.py3-none-any.whl", "install_dir": "site", "sha256": "975cc56216261ddc24c53a36c6e21290a789fb8e6c06a669f9b2a1c71152e891", "depends": [], "imports": ["pycparser"]}, "pydantic": {"name": "pydantic", "version": "1.9.1", "file_name": "pydantic-1.9.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "45776d1d1b436705ca8c9da6dd8d500083745c0e5276914659450b8411bf6e01", "depends": ["typing-extensions"], "imports": ["pydantic"]}, "pyerfa": {"name": "pyerfa", "version": "2.0.0.1", "file_name": "pyerfa-2.0.0.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "8a885e356ae7c3911936b7f4f6ea7f0fa3b57dfa8ed275864273671856b0af4a", "depends": ["numpy"], "imports": ["erfa"], "unvendored_tests": true}, "pyerfa-tests": {"name": "pyerfa-tests", "version": "2.0.0.1", "depends": ["pyerfa"], "imports": [], "file_name": "pyerfa-tests.tar", "install_dir": "site", "sha256": "b5443a63bc54d28bae58f7faf9b354929a71da79a7d918d26ee1ed0e63210922"}, "pygments": {"name": "Pygments", "version": "2.12.0", "file_name": "Pygments-2.12.0-py3-none-any.whl", "install_dir": "site", "sha256": "392fed5326e45ba1b28f4d0816b2c354b8b65720ff5c35fcc93b141b6d9d81ac", "depends": [], "imports": ["pygments"]}, "pyparsing": {"name": "pyparsing", "version": "3.0.9", "file_name": "pyparsing-3.0.9-py3-none-any.whl", "install_dir": "site", "sha256": "883e53711624a9d4ff9208b214880d2c7359f4a9d86be85555e6ce0ca598bfa6", "depends": [], "imports": ["pyparsing"]}, "pyproj": {"name": "pyproj", "version": "3.3.1", "file_name": "pyproj-3.3.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "b37d9efa0fbcab05445c1b3f303be3af982506f3a3705e13ef7ba65226c95ee7", "depends": ["certifi"], "imports": ["pyproj"]}, "pyrsistent": {"name": "pyrsistent", "version": "0.18.1", "file_name": "pyrsistent-0.18.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "334a3b0dd66e84948bcdcfde0c094002b6647ecbe747ab03dd5f63aa49118a97", "depends": [], "imports": ["pyrsistent"]}, "pytest": {"name": "pytest", "version": "7.1.2", "file_name": "pytest-7.1.2-py3-none-any.whl", "install_dir": "site", "sha256": "a2fd43acb4056fddb59b0b1f8193f56e530bd95b52d9c47c900906db8d817434", "depends": ["atomicwrites", "attrs", "more-itertools", "pluggy", "py", "setuptools", "six", "iniconfig"], "imports": ["pytest"]}, "pytest-benchmark": {"name": "pytest-benchmark", "version": "3.4.1", "file_name": "pytest_benchmark-3.4.1-py2.py3-none-any.whl", "install_dir": "site", "sha256": "2843a35adbe980caa5499f3d0ff2853735c2b752a3fab250184ce45d344e202c", "depends": [], "imports": ["pytest_benchmark"]}, "python-dateutil": {"name": "python-dateutil", "version": "2.8.2", "file_name": "python_dateutil-2.8.2-py2.py3-none-any.whl", "install_dir": "site", "sha256": "c15076a670fbd8b4bb2d1a56ff2bf682e08aaf58ad1d2872ab2317810ca938f3", "depends": ["six"], "imports": ["dateutil"]}, "python-magic": {"name": "python-magic", "version": "0.4.27", "file_name": "python_magic-0.4.27-py2.py3-none-any.whl", "install_dir": "site", "sha256": "7a3f44b256a593ac2bd27a3a3c50488f041e5397a593deefb1ba217d1f955ac0", "depends": ["libmagic"], "imports": ["magic"]}, "python-sat": {"name": "python-sat", "version": "0.1.7.dev19", "file_name": "python_sat-0.1.7.dev19-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "243572d6d3e0abb41473d8a212cf62aca7410ee91b18b4f2656bdafc875aca9a", "depends": ["six"], "imports": ["pysat"]}, "python_solvespace": {"name": "python_solvespace", "version": "3.0.7", "file_name": "python_solvespace-3.0.7-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "39ac57303c1f74df2f320bd2eb862cc5656d5620e78324a6e6d2dbaccf7e20fe", "depends": [], "imports": ["python_solvespace"]}, "pytz": {"name": "pytz", "version": "2022.1", "file_name": "pytz-2022.1-py2.py3-none-any.whl", "install_dir": "site", "sha256": "0cbb2ce43468ba1c907c4b61cdd3a8b7762178dc17048fd43db690136f445537", "depends": [], "imports": ["pytz"]}, "pywavelets": {"name": "pywavelets", "version": "1.3.0", "file_name": "PyWavelets-1.3.0-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "20412d4636220557d9b45e3454639cebc50749ea194834221ed8c9cb53f5ca2a", "depends": ["distutils", "numpy", "matplotlib", "scipy"], "imports": ["pywt"], "unvendored_tests": true}, "pywavelets-tests": {"name": "pywavelets-tests", "version": "1.3.0", "depends": ["pywavelets"], "imports": [], "file_name": "pywavelets-tests.tar", "install_dir": "site", "sha256": "828fab05ea69cc8926ce51d783651615ead8fa67d75421a191906912c63cb551"}, "pyyaml": {"name": "pyyaml", "version": "6.0", "file_name": "PyYAML-6.0-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "5d1196cfe502a2e0f25f83404a4eb0d4d233498600b93484409f14350aaf84eb", "depends": [], "imports": ["yaml"]}, "rebound": {"name": "rebound", "version": "3.19.8", "file_name": "rebound-3.19.8-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "47680ff47fd1dbb67b6d5be771faffb9e46d48bb225729b627fe6d27d9814034", "depends": ["numpy"], "imports": ["rebound"]}, "reboundx": {"name": "reboundx", "version": "3.7.1", "file_name": "reboundx-3.7.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "c3a248973acb20b8f794a8cf86191498e10e3b5ae5a7fc078900c21adee4bd9a", "depends": ["rebound", "numpy"], "imports": ["reboundx"]}, "regex": {"name": "regex", "version": "2022.6.2", "file_name": "regex-2022.6.2-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "30c03c03f4c7b03d94422320219ce9d45693c6b0cfe0c698a8f1976cc74dc19b", "depends": [], "imports": ["regex"], "unvendored_tests": true}, "regex-tests": {"name": "regex-tests", "version": "2022.6.2", "depends": ["regex"], "imports": [], "file_name": "regex-tests.tar", "install_dir": "site", "sha256": "7b922688326d20f5d16f0e050364bfe0bbf13cc3396aacd6153b8de90552ea88"}, "retrying": {"name": "retrying", "version": "1.3.3", "file_name": "retrying-1.3.3-py3-none-any.whl", "install_dir": "site", "sha256": "e40f6697ac561a9c67735fc8ec5a436d53e5bfb97dca83df19e950ea0c85ccf2", "depends": ["six"], "imports": ["retrying"]}, "robotraconteur": {"name": "RobotRaconteur", "version": "0.15.1", "file_name": "RobotRaconteur-0.15.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "a4c92121a570b2d00bca60bce1e17e40e99b2aafb9c7f4e0b8d056a424ffec97", "depends": ["numpy"], "imports": ["RobotRaconteur"]}, "ruamel.yaml": {"name": "ruamel.yaml", "version": "0.17.21", "file_name": "ruamel.yaml-0.17.21-py3-none-any.whl", "install_dir": "site", "sha256": "523f7a69986c5c6a77db6378047cb0c7e1bcc535b62a1656ab916125234d3f66", "depends": [], "imports": ["ruamel", "ruamel.yaml"]}, "scikit-image": {"name": "scikit-image", "version": "0.19.3", "file_name": "scikit_image-0.19.3-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "1d85654e550a7bddffdeba6396669ca7d5d419e9ef3f1079098a78cd1cccaf03", "depends": ["distutils", "packaging", "numpy", "scipy", "matplotlib", "networkx", "pillow", "imageio", "pywavelets"], "imports": ["skimage"], "unvendored_tests": true}, "scikit-image-tests": {"name": "scikit-image-tests", "version": "0.19.3", "depends": ["scikit-image"], "imports": [], "file_name": "scikit-image-tests.tar", "install_dir": "site", "sha256": "29beedd9ce4bfaa8a744ea40f65c62ff2f96103c6dfac9758787dcb690e5cd22"}, "scikit-learn": {"name": "scikit-learn", "version": "1.1.1", "file_name": "scikit_learn-1.1.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "62cd3735261b72758bad7db37a82a51d6c94b3a430e56813ba3f55753e469de8", "depends": ["scipy", "joblib", "threadpoolctl"], "imports": ["sklearn", "sklearn.calibration", "sklearn.cluster", "sklearn.compose", "sklearn.covariance", "sklearn.cross_decomposition", "sklearn.datasets", "sklearn.decomposition", "sklearn.discriminant_analysis", "sklearn.dummy", "sklearn.ensemble", "sklearn.exceptions", "sklearn.externals", "sklearn.feature_extraction", "sklearn.feature_selection", "sklearn.gaussian_process", "sklearn.impute", "sklearn.isotonic", "sklearn.kernel_approximation", "sklearn.kernel_ridge", "sklearn.linear_model", "sklearn.manifold", "sklearn.metrics", "sklearn.mixture", "sklearn.model_selection", "sklearn.multiclass", "sklearn.multioutput", "sklearn.naive_bayes", "sklearn.neighbors", "sklearn.neural_network", "sklearn.pipeline", "sklearn.preprocessing", "sklearn.random_projection", "sklearn.semi_supervised", "sklearn.svm", "sklearn.tree", "sklearn.utils"], "unvendored_tests": true}, "scikit-learn-tests": {"name": "scikit-learn-tests", "version": "1.1.1", "depends": ["scikit-learn"], "imports": [], "file_name": "scikit-learn-tests.tar", "install_dir": "site", "sha256": "9a53745bb665cb381bc3c6deea91f90ff975313976f39c141e4170f1d1bbf536"}, "scipy": {"name": "scipy", "version": "1.8.1", "file_name": "scipy-1.8.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "236ae77fd70cc973f3efb5b0d4c7585c5494a6c374bee73bd8994332ab38ae2f", "depends": ["numpy", "clapack"], "imports": ["scipy", "scipy.cluster", "scipy.cluster.vq", "scipy.cluster.hierarchy", "scipy.constants", "scipy.fft", "scipy.fftpack", "scipy.integrate", "scipy.interpolate", "scipy.io", "scipy.io.arff", "scipy.io.matlab", "scipy.io.wavfile", "scipy.linalg", "scipy.linalg.blas", "scipy.linalg.cython_blas", "scipy.linalg.lapack", "scipy.linalg.cython_lapack", "scipy.linalg.interpolative", "scipy.misc", "scipy.ndimage", "scipy.odr", "scipy.optimize", "scipy.signal", "scipy.signal.windows", "scipy.sparse", "scipy.sparse.linalg", "scipy.sparse.csgraph", "scipy.spatial", "scipy.spatial.distance", "scipy.spatial.transform", "scipy.special", "scipy.stats", "scipy.stats.contingency", "scipy.stats.distributions", "scipy.stats.mstats", "scipy.stats.qmc"], "unvendored_tests": true}, "scipy-tests": {"name": "scipy-tests", "version": "1.8.1", "depends": ["scipy"], "imports": [], "file_name": "scipy-tests.tar", "install_dir": "site", "sha256": "375d93a46e1d95033a6eeeddba911cf6bc2bc3738cb2449b95cec2e7db0effb7"}, "setuptools": {"name": "setuptools", "version": "62.6.0", "file_name": "setuptools-62.6.0-py3-none-any.whl", "install_dir": "site", "sha256": "c3254cff321140625ae4fe0544e93b4f5082ede727ce302fe572ec6a4f24f856", "depends": ["distutils", "pyparsing"], "imports": ["setuptools", "pkg_resources"]}, "shapely": {"name": "shapely", "version": "1.8.2", "file_name": "Shapely-1.8.2-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "59b9ba438e5ca8d4c68655028a04c1e9473a8d7f4b1da2a9619b6a44742ae2d1", "depends": ["geos", "numpy"], "imports": ["shapely", "shapely.geos", "shapely.geometry", "shapely.ops", "shapely.prepared", "shapely.validation", "shapely.strtree"], "unvendored_tests": true}, "shapely-tests": {"name": "shapely-tests", "version": "1.8.2", "depends": ["shapely"], "imports": [], "file_name": "shapely-tests.tar", "install_dir": "site", "sha256": "6c12eff5cf8a6ce518a3a7cb0b1061ac997b0b73523206b43605bc812ab4160f"}, "sharedlib-test": {"name": "sharedlib-test", "version": "1.0", "file_name": "sharedlib-test-1.0.zip", "install_dir": "dynlib", "sha256": "f3e1702d9b64baec1cb23d76d69fcf8388f1bf47e26d6e32ca9792dbedf361f2", "shared_library": true, "depends": [], "imports": ["sharedlib-test"]}, "sharedlib-test-py": {"name": "sharedlib-test-py", "version": "1.0", "file_name": "sharedlib_test_py-1.0-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "2c53dfafe10d8a55876136838661366a4e8c9120d370abc086449c17079f3e26", "depends": ["sharedlib-test"], "imports": ["sharedlib_test"]}, "six": {"name": "six", "version": "1.16.0", "file_name": "six-1.16.0-py2.py3-none-any.whl", "install_dir": "site", "sha256": "1dfcde27d939cacebec05b19af21f3e03a85e18a91b23168e0294b98e0867518", "depends": [], "imports": ["six"]}, "soupsieve": {"name": "soupsieve", "version": "2.3.2.post1", "file_name": "soupsieve-2.3.2.post1-py3-none-any.whl", "install_dir": "site", "sha256": "739323d61fdb359d631b788796b73e35a9273c178d4f08fe91cafc2e089ba860", "depends": [], "imports": ["soupsieve"]}, "sparseqr": {"name": "sparseqr", "version": "1.2", "file_name": "sparseqr-1.2-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "a97d56ee22f3903e14921da81b21c98dba1e0580ef3f0ea48da42729419548fa", "depends": ["pycparser", "cffi", "numpy", "scipy", "suitesparse", "distutils"], "imports": ["sparseqr"]}, "sqlalchemy": {"name": "sqlalchemy", "version": "1.4.37", "file_name": "SQLAlchemy-1.4.37-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "3e7317c474b6af1a6a1d99069daa10dafc25f77d53b2010cdab2da49525c3c8f", "depends": [], "imports": ["sqlalchemy"], "unvendored_tests": true}, "sqlalchemy-tests": {"name": "sqlalchemy-tests", "version": "1.4.37", "depends": ["sqlalchemy"], "imports": [], "file_name": "sqlalchemy-tests.tar", "install_dir": "site", "sha256": "2eb224cad4e4fc92bfd46b21ba6a61e02db5a85578fa99c0a8a1f943a0607196"}, "ssl": {"name": "ssl", "version": "1.0.0", "file_name": "ssl-1.0.0.zip", "install_dir": "lib", "sha256": "babbce447cdd2281620b0e4e3a4318719333b113a5c3003f42d2bed684089856", "shared_library": true, "depends": ["openssl"], "imports": ["ssl"]}, "statsmodels": {"name": "statsmodels", "version": "0.13.2", "file_name": "statsmodels-0.13.2-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "b2b3475dbcc8e19b75db02a21529510e375fa574b237ea047c34df9f4442c5fa", "depends": ["distutils", "numpy", "scipy", "pandas", "patsy", "packaging"], "imports": ["statsmodels", "statsmodels.api"], "unvendored_tests": true}, "statsmodels-tests": {"name": "statsmodels-tests", "version": "0.13.2", "depends": ["statsmodels"], "imports": [], "file_name": "statsmodels-tests.tar", "install_dir": "site", "sha256": "5cf64af046df5e4ce735c05eb471e376d96d34d4e921bc456e10a2026728cd3e"}, "suitesparse": {"name": "suitesparse", "version": "5.11.0", "file_name": "suitesparse-5.11.0.zip", "install_dir": "dynlib", "sha256": "391e6fc6ec3dea295e66160c2d2d1968dbfbddc34d638044be5758aa83c9a2a6", "shared_library": true, "depends": ["clapack"], "imports": ["suitesparse"]}, "svgwrite": {"name": "svgwrite", "version": "1.4.2", "file_name": "svgwrite-1.4.2-py3-none-any.whl", "install_dir": "site", "sha256": "2ec36046c4a54e027fb5c7c48a4736aec040fcb26b2887c9337647ebadd24208", "depends": [], "imports": ["svgwrite"]}, "swiglpk": {"name": "swiglpk", "version": "5.0.3", "file_name": "swiglpk-5.0.2-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "550295cd6da02e6f0aa9f227a165d03685e8eabedfc9a14dc3874b2740774961", "depends": [], "imports": ["swiglpk"]}, "sympy": {"name": "sympy", "version": "1.10.1", "file_name": "sympy-1.10.1-py3-none-any.whl", "install_dir": "site", "sha256": "a770ba1b2ba5bca0cbf450c46b8fb8cd7451901c707db6c2ff34dadca9d6965c", "depends": ["distutils", "mpmath"], "imports": ["sympy"], "unvendored_tests": true}, "sympy-tests": {"name": "sympy-tests", "version": "1.10.1", "depends": ["sympy"], "imports": [], "file_name": "sympy-tests.tar", "install_dir": "site", "sha256": "a6e4bc8fb773f1d1b45a14f99dfeb27243fb9b6dadaaefb3452dea13b3283c79"}, "tblib": {"name": "tblib", "version": "1.7.0", "file_name": "pyodide_tblib-1.7.1-py3-none-any.whl", "install_dir": "site", "sha256": "9018c92a8a4c5e19c6e9088f3ec9a178957d6b416e60a56cd7fd2e763593f281", "depends": [], "imports": ["tblib"]}, "termcolor": {"name": "termcolor", "version": "1.1.0", "file_name": "termcolor-1.1.0-py3-none-any.whl", "install_dir": "site", "sha256": "e480a9d2a7cddb0a3190bfe5a3e265d36839f23c3cd5d36b964411c9ddbca4f9", "depends": [], "imports": ["termcolor"]}, "test": {"name": "test", "version": "1.0", "file_name": "test.tar", "install_dir": "lib", "sha256": "729d2c3abfc7eddbc177eee23ae9899bda3f4baadb5b7fca341f1b14aa6063ae", "depends": [], "imports": ["test"]}, "threadpoolctl": {"name": "threadpoolctl", "version": "3.1.0", "file_name": "threadpoolctl-3.1.0-py3-none-any.whl", "install_dir": "site", "sha256": "aef85c68aaa368dd7dfe0f3eeb281ec1950171de5dc823d6b2db7550c2dcda33", "depends": [], "imports": ["threadpoolctl"]}, "tomli": {"name": "tomli", "version": "2.0.1", "file_name": "tomli-2.0.1-py3-none-any.whl", "install_dir": "site", "sha256": "0ab16f8d6ec5492769eab929eed8499ce449264c3e04a1d5c454dd71a57374e9", "depends": [], "imports": ["tomli"]}, "tomli-w": {"name": "tomli-w", "version": "1.0.0", "file_name": "tomli_w-1.0.0-py3-none-any.whl", "install_dir": "site", "sha256": "378e99ff7051b723c2ff47a01354469e46f2341ca19cb89e3747af69d304bcc9", "depends": [], "imports": ["tomli_w"]}, "toolz": {"name": "toolz", "version": "0.11.2", "file_name": "toolz-0.11.2-py3-none-any.whl", "install_dir": "site", "sha256": "c374f8a6128c376036495b70c235027122c73e8491a133e7fe534ae35a4e0a46", "depends": [], "imports": ["toolz"], "unvendored_tests": true}, "toolz-tests": {"name": "toolz-tests", "version": "0.11.2", "depends": ["toolz"], "imports": [], "file_name": "toolz-tests.tar", "install_dir": "site", "sha256": "4f2a0fe6c33967b12bf0e0be2af3dce3a1cd67a7873691c84ca879fc328f4dd3"}, "tqdm": {"name": "tqdm", "version": "4.64.0", "file_name": "tqdm-4.64.0-py2.py3-none-any.whl", "install_dir": "site", "sha256": "b58ebfe8349042fdf3e0951d891cd77ab2e90a84f63180942fe18e148ad31bc0", "depends": [], "imports": ["tqdm"]}, "traits": {"name": "traits", "version": "6.3.2", "file_name": "traits-6.3.2-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "06c74461ea789c6c752a4f2565e9feb5b36aac3eb759936bbb063af56b86851f", "depends": [], "imports": ["traits"], "unvendored_tests": true}, "traits-tests": {"name": "traits-tests", "version": "6.3.2", "depends": ["traits"], "imports": [], "file_name": "traits-tests.tar", "install_dir": "site", "sha256": "a26a5e86171faed923cff24e1fa3793480bacdcb8a0d9725139eeb0c594887f4"}, "tskit": {"name": "tskit", "version": "0.4.1", "file_name": "tskit-0.4.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "24358864ba08994a32dc9e14f892f01045cfcc42668c73d36698ffa63a39ab49", "depends": ["numpy", "svgwrite", "jsonschema"], "imports": ["tskit"]}, "typing-extensions": {"name": "typing-extensions", "version": "4.2.0", "file_name": "typing_extensions-4.2.0-py3-none-any.whl", "install_dir": "site", "sha256": "eb5249151cfcbdaf0e5bb48db8321fe824e3e513d387b51d2d78801c4ddd4fbc", "depends": [], "imports": ["typing_extensions"]}, "uncertainties": {"name": "uncertainties", "version": "3.1.7", "file_name": "uncertainties-3.1.7-py2.py3-none-any.whl", "install_dir": "site", "sha256": "7f6a1cdd4eb6d1939beecd6ee9c3f2a1ea67edf3837f6223da8c7daec24745ee", "depends": ["future"], "imports": ["uncertainties"], "unvendored_tests": true}, "uncertainties-tests": {"name": "uncertainties-tests", "version": "3.1.7", "depends": ["uncertainties"], "imports": [], "file_name": "uncertainties-tests.tar", "install_dir": "site", "sha256": "af4e7f192e7903feeca596c853d601c74bf3a489fe661398fea1314559819fbd"}, "unyt": {"name": "unyt", "version": "2.8.0", "file_name": "unyt-2.8.0-py2.py3-none-any.whl", "install_dir": "site", "sha256": "2c41c2c069a07eaa9cb4169f8ac79c660e1f367a304b07d67ad845055df800b8", "depends": ["numpy", "sympy"], "imports": ["unyt"], "unvendored_tests": true}, "unyt-tests": {"name": "unyt-tests", "version": "2.8.0", "depends": ["unyt"], "imports": [], "file_name": "unyt-tests.tar", "install_dir": "site", "sha256": "84ad280044a488dd6a9fc2e2640a2f896eb919016b8783284f4cf47a87d98741"}, "webencodings": {"name": "webencodings", "version": "0.5.1", "file_name": "webencodings-0.5.1-py2.py3-none-any.whl", "install_dir": "site", "sha256": "01a53e1c6172a2ae188bd4f5217d552cfe34b2c5668abf1ad4b4209beca1058d", "depends": [], "imports": ["webencodings"]}, "wrapt": {"name": "wrapt", "version": "1.14.1", "file_name": "wrapt-1.14.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "0d5204f16ac04be5fe2e4107a323e24c5dc325e638d02ab0a0ed3611f82ef2b9", "depends": [], "imports": ["wrapt"]}, "xarray": {"name": "xarray", "version": "2022.3.0", "file_name": "xarray-2022.3.0-py3-none-any.whl", "install_dir": "site", "sha256": "eb225990cf12360939a20b4fb95d3a0768ccbd5d3216dbdf87810ff0c6a1095d", "depends": ["numpy", "packaging", "pandas"], "imports": ["xarray"], "unvendored_tests": true}, "xarray-tests": {"name": "xarray-tests", "version": "2022.3.0", "depends": ["xarray"], "imports": [], "file_name": "xarray-tests.tar", "install_dir": "site", "sha256": "ef25af408de4669f7c4c83687315d6ffdbaa7ac8fb0b290c099b6e3a56b79f24"}, "xgboost": {"name": "xgboost", "version": "1.6.1", "file_name": "xgboost-1.6.1-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "133659cc7af462e773eb170ce766785432487dfd5b083823adf02db590ff22b8", "depends": ["numpy", "scipy", "setuptools"], "imports": ["xgboost"]}, "xlrd": {"name": "xlrd", "version": "2.0.1", "file_name": "xlrd-2.0.1-py2.py3-none-any.whl", "install_dir": "site", "sha256": "e3afa903a1b80a418b7cb4c7591195d3fc1226cb711b1819814f7ffc654e0678", "depends": [], "imports": ["xlrd"]}, "yt": {"name": "yt", "version": "4.0.4", "file_name": "yt-4.0.4-cp310-cp310-emscripten_3_1_14_wasm32.whl", "install_dir": "site", "sha256": "1065f0b6981b07f20d07591d7f93f7ed9746459097589256b6d13b9abfaaed53", "depends": ["numpy", "matplotlib", "sympy", "setuptools", "packaging", "unyt", "cmyt", "colorspacious", "tqdm", "tomli", "tomli-w"], "imports": ["yt"]}, "zarr": {"name": "zarr", "version": "2.11.3", "file_name": "zarr-2.11.3-py3-none-any.whl", "install_dir": "site", "sha256": "725164fc5f420465349d7e798e7334fa3dbc81131401a6c5bef5647dd0977f1a", "depends": ["numpy", "asciitree", "numcodecs"], "imports": ["zarr"], "unvendored_tests": true}, "zarr-tests": {"name": "zarr-tests", "version": "2.11.3", "depends": ["zarr"], "imports": [], "file_name": "zarr-tests.tar", "install_dir": "site", "sha256": "9861fa6431e07085aa8151be1ba06eed7451199e576019793ed7bba1cd9b380a"}}} 2 | -------------------------------------------------------------------------------- /runtime/setup.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import requests 3 | from urllib.request import urlopen 4 | from pathlib import Path 5 | from log2d import Log 6 | 7 | dir_path = Path(__file__).parent 8 | 9 | # Adding """""" instead of "" for downloads because I was facing syntax error. 10 | 11 | downloads = """ 12 | https://pyscript.net/latest/pyscript.js 13 | https://pyscript.net/latest/pyscript.css 14 | https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js 15 | https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide_py.tar 16 | https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.asm.js 17 | https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.asm.data 18 | https://cdn.jsdelivr.net/pyodide/v0.21.3/full/repodata.json 19 | https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.asm.wasm 20 | https://cdn.jsdelivr.net/pyodide/v0.21.3/full/packaging-21.3-py3-none-any.whl 21 | https://cdn.jsdelivr.net/pyodide/v0.21.3/full/distutils.tar 22 | https://cdn.jsdelivr.net/pyodide/v0.21.3/full/micropip-0.1-py3-none-any.whl 23 | https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyparsing-3.0.9-py3-none-any.whl 24 | """.split() 25 | 26 | if __name__ == "__main__": 27 | Log("setup") 28 | for url in downloads: 29 | try: 30 | with requests.get(url, stream=True) as r: 31 | r.raise_for_status() 32 | filename = dir_path / url.split("/")[-1] 33 | with open(filename, 'wb') as f: 34 | for chunk in r.iter_content(chunk_size=8192): 35 | # If you have chunk encoded response uncomment if 36 | # and set chunk_size parameter to None. 37 | #if chunk: 38 | f.write(chunk) 39 | Log.setup.info(f"Downloaded: {filename.name}") 40 | except Exception as E: 41 | Log.setup.error(f"Failed to download: {filename.name}\n{E}") 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /runtime/setup.sh: -------------------------------------------------------------------------------- 1 | wget https://pyscript.net/latest/pyscript.js 2 | wget https://pyscript.net/latest/pyscript.css 3 | wget https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js 4 | wget https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide_py.tar 5 | wget https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.asm.js 6 | wget https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.asm.data 7 | wget https://cdn.jsdelivr.net/pyodide/v0.21.3/full/repodata.json 8 | wget https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.asm.wasm 9 | wget https://cdn.jsdelivr.net/pyodide/v0.21.3/full/packaging-21.3-py3-none-any.whl 10 | wget https://cdn.jsdelivr.net/pyodide/v0.21.3/full/distutils.tar 11 | wget https://cdn.jsdelivr.net/pyodide/v0.21.3/full/micropip-0.1-py3-none-any.whl 12 | wget https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyparsing-3.0.9-py3-none-any.whl 13 | --------------------------------------------------------------------------------