Spaces:
Build error
Build error
File size: 6,353 Bytes
2bb1e37 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | const packages = [
'micropip',
'packaging',
'requests',
'beautifulsoup4',
'numpy',
'pandas',
'matplotlib',
'scikit-learn',
'scipy',
'regex',
'sympy',
'tiktoken',
'seaborn',
'pytz',
'black',
'openai',
'openpyxl'
];
// Pure-Python packages whose wheels must be downloaded from PyPI and saved into
// static/pyodide/ so that the browser can install them offline via micropip.
// Packages already provided by the Pyodide distribution (click, platformdirs,
// typing_extensions, etc.) do NOT need to be listed here.
const pypiPackages = ['black', 'pathspec', 'mypy_extensions', 'pytokens'];
import { loadPyodide } from 'pyodide';
import { setGlobalDispatcher, ProxyAgent } from 'undici';
import { writeFile, readFile, copyFile, readdir, rmdir, access } from 'fs/promises';
/**
* Loading network proxy configurations from the environment variables.
* And the proxy config with lowercase name has the highest priority to use.
*/
function initNetworkProxyFromEnv() {
// we assume all subsequent requests in this script are HTTPS:
// https://cdn.jsdelivr.net
// https://pypi.org
// https://files.pythonhosted.org
const allProxy = process.env.all_proxy || process.env.ALL_PROXY;
const httpsProxy = process.env.https_proxy || process.env.HTTPS_PROXY;
const httpProxy = process.env.http_proxy || process.env.HTTP_PROXY;
const preferedProxy = httpsProxy || allProxy || httpProxy;
/**
* use only http(s) proxy because socks5 proxy is not supported currently:
* @see https://github.com/nodejs/undici/issues/2224
*/
if (!preferedProxy || !preferedProxy.startsWith('http')) return;
let preferedProxyURL;
try {
preferedProxyURL = new URL(preferedProxy).toString();
} catch {
console.warn(`Invalid network proxy URL: "${preferedProxy}"`);
return;
}
const dispatcher = new ProxyAgent({ uri: preferedProxyURL });
setGlobalDispatcher(dispatcher);
console.log(`Initialized network proxy "${preferedProxy}" from env`);
}
async function downloadPackages() {
console.log('Setting up pyodide + micropip');
let pyodide;
try {
pyodide = await loadPyodide({
packageCacheDir: 'static/pyodide'
});
} catch (err) {
console.error('Failed to load Pyodide:', err);
return;
}
const packageJson = JSON.parse(await readFile('package.json'));
const pyodideVersion = packageJson.dependencies.pyodide.replace('^', '');
try {
const pyodidePackageJson = JSON.parse(await readFile('static/pyodide/package.json'));
const pyodidePackageVersion = pyodidePackageJson.version.replace('^', '');
if (pyodideVersion !== pyodidePackageVersion) {
console.log('Pyodide version mismatch, removing static/pyodide directory');
await rmdir('static/pyodide', { recursive: true });
}
} catch (err) {
console.log('Pyodide package not found, proceeding with download.', err);
}
try {
console.log('Loading micropip package');
await pyodide.loadPackage('micropip');
const micropip = pyodide.pyimport('micropip');
console.log('Downloading Pyodide packages:', packages);
try {
for (const pkg of packages) {
console.log(`Installing package: ${pkg}`);
await micropip.install(pkg);
}
} catch (err) {
console.error('Package installation failed:', err);
return;
}
console.log('Pyodide packages downloaded, freezing into lock file');
try {
const lockFile = await micropip.freeze();
await writeFile('static/pyodide/pyodide-lock.json', lockFile);
} catch (err) {
console.error('Failed to write lock file:', err);
}
} catch (err) {
console.error('Failed to load or install micropip:', err);
}
}
async function copyPyodide() {
console.log('Copying Pyodide files into static directory');
// Copy all files from node_modules/pyodide to static/pyodide
for await (const entry of await readdir('node_modules/pyodide')) {
await copyFile(`node_modules/pyodide/${entry}`, `static/pyodide/${entry}`);
}
}
/**
* Download pure-Python wheels from PyPI and save them into static/pyodide/.
* Also injects entries into pyodide-lock.json so that micropip resolves these
* packages from the local server instead of fetching them from the internet.
*/
async function downloadPyPIWheels() {
const lockPath = 'static/pyodide/pyodide-lock.json';
let lockData;
try {
lockData = JSON.parse(await readFile(lockPath, 'utf-8'));
} catch {
console.warn('Could not read pyodide-lock.json, skipping PyPI wheel download');
return;
}
for (const pkg of pypiPackages) {
console.log(`Fetching PyPI metadata for: ${pkg}`);
const res = await fetch(`https://pypi.org/pypi/${pkg}/json`);
if (!res.ok) {
console.error(`Failed to fetch PyPI metadata for ${pkg}: ${res.status}`);
continue;
}
const meta = await res.json();
const version = meta.info.version;
const files = meta.urls || [];
// Find the pure-Python wheel (py3-none-any)
const wheel = files.find(
(f) => f.filename.endsWith('.whl') && f.filename.includes('py3-none-any')
);
if (!wheel) {
console.warn(`No pure-Python wheel found for ${pkg}==${version}, skipping`);
continue;
}
const dest = `static/pyodide/${wheel.filename}`;
// Download wheel if not already present
try {
await access(dest);
console.log(` Already exists: ${wheel.filename}`);
} catch {
console.log(` Downloading: ${wheel.filename}`);
const wheelRes = await fetch(wheel.url);
if (!wheelRes.ok) {
console.error(` Failed to download ${wheel.filename}: ${wheelRes.status}`);
continue;
}
const buffer = Buffer.from(await wheelRes.arrayBuffer());
await writeFile(dest, buffer);
console.log(` Saved: ${dest} (${buffer.length} bytes)`);
}
// Inject into pyodide-lock.json so micropip resolves locally
const normalizedName = pkg.replace(/-/g, '_');
if (!lockData.packages[normalizedName]) {
lockData.packages[normalizedName] = {
name: normalizedName,
version: version,
file_name: wheel.filename,
install_dir: 'site',
sha256: wheel.digests?.sha256 || '',
package_type: 'package',
imports: [normalizedName],
depends: []
};
console.log(` Added ${normalizedName}==${version} to pyodide-lock.json`);
}
}
await writeFile(lockPath, JSON.stringify(lockData, null, 2));
console.log('Updated pyodide-lock.json with PyPI packages');
}
initNetworkProxyFromEnv();
await downloadPackages();
await copyPyodide();
await downloadPyPIWheels();
|