feat: bundle python-pptx and python-docx for the Pyodide code interpreter (#30382)

Asked for a PowerPoint or Word file, the model had no library for it, so it followed the prompt's "use an alternative approach" and wrote the OOXML zip by hand. The sandbox reported success and Office refused to open the result.

python-pptx and python-docx are now vendored the same way as openpyxl: their wheels (plus xlsxwriter) go through the PyPI wheel path, lxml joins the Pyodide distribution list so its wasm wheel is cached, and importing pptx or docx installs them from the bundled wheels. No prompt change is needed, since the app installs on import.

static/pyodide grows by about 3 MB (58 to 61 MB) and verifyBundledWheels() passes.

Verified in headless Chromium with pypi.org, files.pythonhosted.org and the jsDelivr CDN blocked: both packages install from the local wheels only, and a deck and a document built in the sandbox reopen with the native libraries. openpyxl, seaborn, black, pandas, matplotlib and requests still install offline. Without the change both installs fail offline.

Fixes #30361
This commit is contained in:
Classic298 2026-09-24 05:50:39 +02:00 • committed by GitHub
parent 744ce6cbfe
commit e13e523f00
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 59 additions and 5 deletions

View file

@ -13,7 +13,8 @@ const packages = [
'tiktoken',
'pytz',
'black',
'openai'
'openai',
'lxml'
];
// Pure-Python packages whose wheels must be downloaded from PyPI and saved into
@ -28,13 +29,18 @@ const pypiPackages = [
'pytokens',
'openpyxl',
'et-xmlfile',
'seaborn'
'seaborn',
'python-pptx',
'python-docx',
'xlsxwriter'
];
const pypiDepends = {
black: ['click', 'mypy-extensions', 'packaging', 'pathspec', 'platformdirs', 'pytokens'],
openpyxl: ['et-xmlfile'],
seaborn: ['matplotlib', 'numpy', 'pandas']
seaborn: ['matplotlib', 'numpy', 'pandas'],
'python-pptx': ['lxml', 'pillow', 'xlsxwriter', 'typing-extensions'],
'python-docx': ['lxml', 'typing-extensions']
};
import { loadPyodide } from 'pyodide';

View file

@ -235,7 +235,9 @@
/\bimport\s+tiktoken\b|\bfrom\s+tiktoken\b/.test(code) ? 'tiktoken' : null,
/\bimport\s+pytz\b|\bfrom\s+pytz\b/.test(code) ? 'pytz' : null,
/\bimport\s+openpyxl\b|\bfrom\s+openpyxl\b/.test(code) ? 'openpyxl' : null,
/\.(read|to)_excel\(|\.Excel(Writer|File)\(/.test(code) ? 'openpyxl' : null
/\.(read|to)_excel\(|\.Excel(Writer|File)\(/.test(code) ? 'openpyxl' : null,
/\bimport\s+pptx\b|\bfrom\s+pptx\b/.test(code) ? 'python-pptx' : null,
/\bimport\s+docx\b|\bfrom\s+docx\b/.test(code) ? 'python-docx' : null
].filter(Boolean);
console.log(packages);

View file

@ -313,7 +313,9 @@
/\bimport\s+tiktoken\b|\bfrom\s+tiktoken\b/.test(code) ? 'tiktoken' : null,
/\bimport\s+pytz\b|\bfrom\s+pytz\b/.test(code) ? 'pytz' : null,
/\bimport\s+openpyxl\b|\bfrom\s+openpyxl\b/.test(code) ? 'openpyxl' : null,
/\.(read|to)_excel\(|\.Excel(Writer|File)\(/.test(code) ? 'openpyxl' : null
/\.(read|to)_excel\(|\.Excel(Writer|File)\(/.test(code) ? 'openpyxl' : null,
/\bimport\s+pptx\b|\bfrom\s+pptx\b/.test(code) ? 'python-pptx' : null,
/\bimport\s+docx\b|\bfrom\s+docx\b/.test(code) ? 'python-docx' : null
].filter(Boolean);
const worker = getOrCreateWorker();

View file

@ -5203,6 +5203,50 @@
"numpy",
"pandas"
]
},
"python-pptx": {
"name": "python-pptx",
"version": "1.0.2",
"file_name": "python_pptx-1.0.2-py3-none-any.whl",
"install_dir": "site",
"sha256": "160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba",
"package_type": "package",
"imports": [
"python_pptx"
],
"depends": [
"lxml",
"pillow",
"xlsxwriter",
"typing-extensions"
]
},
"python-docx": {
"name": "python-docx",
"version": "1.2.0",
"file_name": "python_docx-1.2.0-py3-none-any.whl",
"install_dir": "site",
"sha256": "3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7",
"package_type": "package",
"imports": [
"python_docx"
],
"depends": [
"lxml",
"typing-extensions"
]
},
"xlsxwriter": {
"name": "xlsxwriter",
"version": "3.2.9",
"file_name": "xlsxwriter-3.2.9-py3-none-any.whl",
"install_dir": "site",
"sha256": "9a5db42bc5dff014806c58a20b9eae7322a134abb6fce3c92c181bfb275ec5b3",
"package_type": "package",
"imports": [
"xlsxwriter"
],
"depends": []
}
}
}