Skip to content

Commit 6fa88ae

Browse files
Merge pull request #154 from pyscript/remote-package
Add `experimental_remote_packages` config flag for remote packages
2 parents 3565503 + f086129 commit 6fa88ae

File tree

6 files changed

+119
-29
lines changed

6 files changed

+119
-29
lines changed

docs/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

esm/interpreter/_remote_package.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import fetch from '@webreflection/fetch';
2+
3+
import { toml } from '../3rd-party.js';
4+
5+
const { parse } = JSON;
6+
7+
const href = (key, pkg) => new URL(key, pkg).href;
8+
9+
const addPath = (target, key, value) => {
10+
if (key in target)
11+
throw new Error(`Duplicated path: ${key}`);
12+
target[key] = value;
13+
};
14+
15+
const addPaths = (target, source, pkg) => {
16+
for (const key in source)
17+
addPath(target, href(key, pkg), source[key]);
18+
};
19+
20+
const pollute = (t_js_modules, s_js_modules, name, pkg) => {
21+
const source = s_js_modules[name];
22+
if (source) {
23+
t_js_modules[name] ??= {};
24+
addPaths(t_js_modules[name], source, pkg);
25+
}
26+
};
27+
28+
const remote = async (
29+
config,
30+
packages = config.packages,
31+
set = new Set(),
32+
) => {
33+
const repackaged = [];
34+
for (const pkg of packages) {
35+
// avoid re-processing already processed packages
36+
if (set.has(pkg)) continue;
37+
set.add(pkg);
38+
const isTOML = pkg.endsWith('.toml');
39+
if (isTOML || pkg.endsWith('.json')) {
40+
const text = await fetch(pkg).text();
41+
const {
42+
name,
43+
files,
44+
js_modules,
45+
packages,
46+
} = isTOML ? await toml(text) : parse(text);
47+
48+
if (set.has(name))
49+
throw new Error(`Unable to process ${name} @ ${pkg}`);
50+
51+
set.add(name);
52+
53+
if (packages) {
54+
// process nested packages from the remote config
55+
repackaged.push(...(await remote(config, packages, set)));
56+
}
57+
58+
if (js_modules) {
59+
config.js_modules ??= {};
60+
pollute(config.js_modules, js_modules, 'main', pkg);
61+
pollute(config.js_modules, js_modules, 'worker', pkg);
62+
}
63+
64+
if (files) {
65+
config.files ??= {};
66+
addPaths(config.files, files, pkg);
67+
}
68+
}
69+
else repackaged.push(pkg);
70+
}
71+
return repackaged;
72+
};
73+
74+
export default remote;

esm/interpreter/micropython.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import mip from '../python/mip.js';
88
import { zip } from '../3rd-party.js';
99

1010
import { initializeNativeFS } from './_nativefs.js';
11+
import _remote_package from './_remote_package.js';
1112

1213
const type = 'micropython';
1314

@@ -44,6 +45,11 @@ export default {
4445
// Install Micropython Package
4546
this.writeFile(interpreter, './mip.py', mip);
4647
if (config.packages) {
48+
if (config.experimental_remote_packages) {
49+
progress('Loading remote packages');
50+
config.packages = await _remote_package(config);
51+
progress('Loaded remote packages');
52+
}
4753
progress('Loading packages');
4854
await py_imports(config.packages.map(fixedRelative, baseURL));
4955
progress('Loaded packages');

esm/interpreter/pyodide.js

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createProgress, writeFile } from './_utils.js';
22
import { getFormat, loader, loadProgress, registerJSModule, run, runAsync, runEvent } from './_python.js';
33
import { stdio } from './_io.js';
44
import { IDBMapSync, isArray, fixedRelative, js_modules } from '../utils.js';
5+
import _remote_package from './_remote_package.js';
56

67
const type = 'pyodide';
78
const toJsOptions = { dict_converter: Object.fromEntries };
@@ -84,46 +85,55 @@ export default {
8485
// https://github.com/pyodide/pyodide/issues/5736
8586
const save = config.packages_cache !== 'never' && version !== '0.28.0';
8687
await storage.sync();
88+
progress('Loaded Storage');
8789
// packages_cache = 'never' means: erase the whole DB
8890
if (!save) storage.clear();
8991
// otherwise check if cache is known
90-
else if (packages) {
91-
// packages_cache = 'passthrough' means: do not use micropip.install
92-
if (config.packages_cache === 'passthrough') {
93-
options.packages = packages;
94-
packages = null;
95-
storage.clear();
92+
if (packages) {
93+
if (config.experimental_remote_packages) {
94+
progress('Loading remote packages');
95+
config.packages = (packages = await _remote_package(config, packages));
96+
progress('Loaded remote packages');
9697
}
97-
else {
98-
packages = packages.sort();
99-
// packages are uniquely stored as JSON key
100-
const key = stringify(packages);
101-
if (storage.has(key)) {
102-
const value = storage.get(key);
98+
if (save) {
99+
// packages_cache = 'passthrough' means: do not use micropip.install
100+
if (config.packages_cache === 'passthrough') {
101+
options.packages = packages;
102+
packages = null;
103+
storage.clear();
104+
}
105+
else {
106+
packages = packages.sort();
107+
// packages are uniquely stored as JSON key
108+
const key = stringify(packages);
109+
if (storage.has(key)) {
110+
const value = storage.get(key);
103111

104-
// versions are not currently understood by pyodide when
105-
// a lockFileURL is used instead of micropip.install(packages)
106-
// https://github.com/pyodide/pyodide/issues/5135#issuecomment-2441038644
107-
// https://github.com/pyscript/pyscript/issues/2245
108-
options.packages = packages.map(name => name.split(/[>=<]=/)[0]);
112+
// versions are not currently understood by pyodide when
113+
// a lockFileURL is used instead of micropip.install(packages)
114+
// https://github.com/pyodide/pyodide/issues/5135#issuecomment-2441038644
115+
// https://github.com/pyscript/pyscript/issues/2245
116+
options.packages = packages.map(name => name.split(/[>=<]=/)[0]);
109117

110-
if (version.startsWith('0.27')) {
111-
const blob = new Blob([value], { type: 'application/json' });
112-
options.lockFileURL = URL.createObjectURL(blob);
113-
}
114-
else {
115-
options.lockFileContents = value;
116-
}
118+
if (version.startsWith('0.27')) {
119+
const blob = new Blob([value], { type: 'application/json' });
120+
options.lockFileURL = URL.createObjectURL(blob);
121+
}
122+
else {
123+
options.lockFileContents = value;
124+
}
117125

118-
packages = null;
126+
packages = null;
127+
}
119128
}
120129
}
121130
}
122-
progress('Loaded Storage');
123131
const { stderr, stdout, get } = stdio();
132+
progress('Loading interpreter');
124133
const interpreter = await get(
125134
loadPyodide({ stderr, stdout, ...options }),
126135
);
136+
progress('Loaded interpreter');
127137
globalThis[js_modules].set('-T-', this.transform.bind(this, interpreter));
128138
if (config.debug) interpreter.setDebug(true);
129139
const py_imports = importPackages.bind(interpreter);

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,6 @@
9595
"to-json-callback": "^0.1.1"
9696
},
9797
"worker": {
98-
"blob": "sha256-1jyBlknYo68LxBQF5bk5wMmmxqX19PGLdaNSxKljS08="
98+
"blob": "sha256-u4s/ss3EOnkTX4hrZ/GUovLYI9GxTeDHu+26khZiSnA="
9999
}
100100
}

0 commit comments

Comments
 (0)