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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | 
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 |
66 |
67 |
--------------------------------------------------------------------------------
/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/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/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/pyodide_py.tar:
--------------------------------------------------------------------------------
1 | pyodide/ 0000755 0000000 0000000 00000000000 14310733215 011210 5 ustar root root pyodide/_core.py 0000644 0000000 0000000 00000001163 14310731640 012652 0 ustar root root import 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.py 0000644 0000000 0000000 00000000730 14310731640 013221 0 ustar root root from 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.py 0000644 0000000 0000000 00000002624 14310731640 013045 0 ustar root root import 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.py 0000644 0000000 0000000 00000001332 14310731640 012473 0 ustar root root from 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.py 0000644 0000000 0000000 00000043121 14310731640 013225 0 ustar root root import 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/ 0000755 0000000 0000000 00000000000 14310733215 011754 5 ustar root root pyodide/ffi/__init__.py 0000644 0000000 0000000 00000000726 14310731640 014072 0 ustar root root from _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.py 0000644 0000000 0000000 00000006463 14310731640 014202 0 ustar root root from 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.py 0000644 0000000 0000000 00000016350 14310731640 012546 0 ustar root root import 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__.py 0000644 0000000 0000000 00000005131 14310731640 013321 0 ustar root root # 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.py 0000644 0000000 0000000 00000024336 14310731640 014652 0 ustar root root import 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.py 0000644 0000000 0000000 00000035425 14310731640 013242 0 ustar root root import 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/ 0000755 0000000 0000000 00000000000 14310733215 011347 5 ustar root root _pyodide/__init__.py 0000644 0000000 0000000 00000001110 14310731640 013451 0 ustar root root # _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.py 0000644 0000000 0000000 00000003252 14310731640 013717 0 ustar root root from 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.py 0000644 0000000 0000000 00000044743 14310731640 013006 0 ustar root root """
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.py 0000644 0000000 0000000 00000042233 14310731640 014024 0 ustar root root from 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.py 0000644 0000000 0000000 00000015657 14310731640 014271 0 ustar root root import 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 |
--------------------------------------------------------------------------------