fix: bundle seaborn and give black its dependencies in the Pyodide lock (#30148)

seaborn was listed among the Pyodide distribution packages, but it is not part of that distribution, so the build wrote no wheel for it. Since seaborn is on the code interpreter's package list, it was quietly downloaded from pypi.org in the user's browser instead, which is what bundling the wheels is meant to avoid. It now takes the PyPI wheel path, like openpyxl.

The code editor's Format button was broken for non-admin users, with or without internet, because black's bundled lock entry declared no dependencies and the hand-written list next to the caller was missing packaging. black's dependencies are now declared in the lock, so that list goes back to black alone and every future caller gets them too.

Both of these shipped unnoticed because nothing checked that a listed package produced a wheel. The build now fails when one did not, naming it.

Verified offline with the network cut at undici's dispatcher: seaborn, black, the Excel roundtrip and the other listed packages all install from the bundled wheels, and a deleted wheel, a missing lock entry or a non-canonical key each fail the build.

Fixes #30145
This commit is contained in:
Classic298
2026-09-18 19:30:54 -04:00
committed by GitHub
parent 677341578d
commit 6d73780fd1
3 changed files with 65 additions and 13 deletions
+40 -3
View File
@@ -11,7 +11,6 @@ const packages = [
'regex',
'sympy',
'tiktoken',
'seaborn',
'pytz',
'black',
'openai'
@@ -22,9 +21,21 @@ const packages = [
// Packages already provided by the Pyodide distribution (click, platformdirs,
// typing_extensions, etc.) do NOT need to be listed here.
// Spell them canonically (dashed): that is the only form pyodide resolves lock entries by.
const pypiPackages = ['black', 'pathspec', 'mypy-extensions', 'pytokens', 'openpyxl', 'et-xmlfile'];
const pypiPackages = [
'black',
'pathspec',
'mypy-extensions',
'pytokens',
'openpyxl',
'et-xmlfile',
'seaborn'
];
const pypiDepends = { openpyxl: ['et-xmlfile'] };
const pypiDepends = {
black: ['click', 'mypy-extensions', 'packaging', 'pathspec', 'platformdirs', 'pytokens'],
openpyxl: ['et-xmlfile'],
seaborn: ['matplotlib', 'numpy', 'pandas']
};
import { loadPyodide } from 'pyodide';
import { setGlobalDispatcher, ProxyAgent } from 'undici';
@@ -196,6 +207,31 @@ async function downloadPyPIWheels() {
console.log('Updated pyodide-lock.json with PyPI packages');
}
// A package with no bundled wheel is installed from PyPI in the user's browser instead.
async function verifyBundledWheels() {
const lockPath = 'static/pyodide/pyodide-lock.json';
const lockData = JSON.parse(await readFile(lockPath, 'utf-8'));
const missing = [];
for (const pkg of new Set([...packages, ...pypiPackages, ...Object.values(pypiDepends).flat()])) {
const entry = lockData.packages[pkg.toLowerCase().replace(/[-_.]+/g, '-')];
if (!entry) {
missing.push(pkg);
continue;
}
try {
await access(`static/pyodide/${entry.file_name}`);
} catch {
missing.push(pkg);
}
}
if (missing.length) {
throw new Error(`No wheel bundled for: ${missing.join(', ')}`);
}
console.log('All listed packages are bundled');
}
initNetworkProxyFromEnv();
if (process.env.USE_SLIM === 'true') {
// Rebuild generated assets so a previous full build cannot leave bundled wheels behind.
@@ -217,4 +253,5 @@ if (process.env.USE_SLIM === 'true') {
await downloadPackages();
await copyPyodide();
await downloadPyPIWheels();
await verifyBundledWheels();
}
+1 -9
View File
@@ -122,15 +122,7 @@ print(black.format_str("""${code.replace(/\\/g, '\\\\').replace(/`/g, '\\`').rep
print("${endTag}")
`;
// black's pyodide-lock entry declares no dependencies, so micropip installs none of them
const packages = [
'black',
'click',
'mypy_extensions',
'pathspec',
'platformdirs',
'pytokens'
];
const packages = ['black'];
function handleMessage(event) {
const { id: eventId, stdout, stderr } = event.data;
+24 -1
View File
@@ -5117,7 +5117,14 @@
"imports": [
"black"
],
"depends": []
"depends": [
"click",
"mypy-extensions",
"packaging",
"pathspec",
"platformdirs",
"pytokens"
]
},
"pathspec": {
"name": "pathspec",
@@ -5180,6 +5187,22 @@
"et_xmlfile"
],
"depends": []
},
"seaborn": {
"name": "seaborn",
"version": "0.13.2",
"file_name": "seaborn-0.13.2-py3-none-any.whl",
"install_dir": "site",
"sha256": "636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987",
"package_type": "package",
"imports": [
"seaborn"
],
"depends": [
"matplotlib",
"numpy",
"pandas"
]
}
}
}