fix: pass wheel filename through extraction chain to fix micropip install
micropip requires the full PEP 427 wheel filename (name-version-py-abi-plat.whl) — writing the file as bincio.whl caused InvalidWheelFilename. The wheel URL from /api/wheel/version now provides the basename; it flows through fetchWheelBase64 → extractFile → WebView where the file is written with the correct name and _wheel_path is set as a Pyodide global before PY_INSTALL_WHEEL runs.
This commit is contained in:
@@ -97,12 +97,13 @@ export default function ImportScreen() {
|
|||||||
// allows local-network HTTP (NSAllowsLocalNetworking=true in Info.plist).
|
// allows local-network HTTP (NSAllowsLocalNetworking=true in Info.plist).
|
||||||
const instanceUrl = await getInstanceUrl(dbCtx);
|
const instanceUrl = await getInstanceUrl(dbCtx);
|
||||||
setState({ status: 'loading', msg: 'Fetching Bincio engine…' });
|
setState({ status: 'loading', msg: 'Fetching Bincio engine…' });
|
||||||
const wheelBase64 = await fetchWheelBase64(instanceUrl);
|
const { base64: wheelBase64, filename: wheelFilename } = await fetchWheelBase64(instanceUrl);
|
||||||
|
|
||||||
const result = await extractFile(
|
const result = await extractFile(
|
||||||
name,
|
name,
|
||||||
base64,
|
base64,
|
||||||
wheelBase64,
|
wheelBase64,
|
||||||
|
wheelFilename,
|
||||||
(msg) => setState({ status: 'loading', msg }),
|
(msg) => setState({ status: 'loading', msg }),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -215,21 +216,25 @@ async function getInstanceUrl(db: ReturnType<typeof useSQLiteContext>): Promise<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// In-memory cache so repeated imports in one session don't re-download the wheel.
|
// In-memory cache so repeated imports in one session don't re-download the wheel.
|
||||||
let _cachedWheelBase64: string | null = null;
|
let _cachedWheel: { base64: string; filename: string } | null = null;
|
||||||
|
|
||||||
async function fetchWheelBase64(instanceUrl: string): Promise<string> {
|
async function fetchWheelBase64(instanceUrl: string): Promise<{ base64: string; filename: string }> {
|
||||||
if (_cachedWheelBase64) return _cachedWheelBase64;
|
if (_cachedWheel) return _cachedWheel;
|
||||||
|
|
||||||
const base = instanceUrl || 'https://bincio.org';
|
const base = instanceUrl || 'https://bincio.org';
|
||||||
|
|
||||||
// Ask the instance for the canonical wheel URL (handles both dev and prod layouts).
|
// Ask the instance for the canonical wheel URL (handles both dev and prod layouts).
|
||||||
let wheelUrl = `${base}/api/wheel/download`;
|
let wheelUrl = `${base}/api/wheel/download`;
|
||||||
|
let wheelFilename = 'bincio-0.1.0-py3-none-any.whl';
|
||||||
try {
|
try {
|
||||||
const vr = await fetch(`${base}/api/wheel/version`, { signal: AbortSignal.timeout(5000) });
|
const vr = await fetch(`${base}/api/wheel/version`, { signal: AbortSignal.timeout(5000) });
|
||||||
if (vr.ok) {
|
if (vr.ok) {
|
||||||
const d = await vr.json() as { api_url?: string; url?: string };
|
const d = await vr.json() as { api_url?: string; url?: string };
|
||||||
const path = d.api_url ?? d.url ?? '/api/wheel/download';
|
const path = d.api_url ?? d.url ?? '/api/wheel/download';
|
||||||
wheelUrl = path.startsWith('http') ? path : `${base}${path}`;
|
wheelUrl = path.startsWith('http') ? path : `${base}${path}`;
|
||||||
|
// Extract the filename from the URL path (last segment after final /)
|
||||||
|
const urlBasename = wheelUrl.split('/').pop() ?? '';
|
||||||
|
if (urlBasename.endsWith('.whl')) wheelFilename = urlBasename;
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
@@ -237,8 +242,8 @@ async function fetchWheelBase64(instanceUrl: string): Promise<string> {
|
|||||||
const resp = await fetch(wheelUrl);
|
const resp = await fetch(wheelUrl);
|
||||||
if (!resp.ok) throw new Error(`Could not download Bincio engine (${resp.status}). Is the instance running?`);
|
if (!resp.ok) throw new Error(`Could not download Bincio engine (${resp.status}). Is the instance running?`);
|
||||||
const buf = await resp.arrayBuffer();
|
const buf = await resp.arrayBuffer();
|
||||||
_cachedWheelBase64 = arrayBufferToBase64(buf);
|
_cachedWheel = { base64: arrayBufferToBase64(buf), filename: wheelFilename };
|
||||||
return _cachedWheelBase64;
|
return _cachedWheel;
|
||||||
}
|
}
|
||||||
|
|
||||||
function arrayBufferToBase64(buf: ArrayBuffer): string {
|
function arrayBufferToBase64(buf: ArrayBuffer): string {
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ const PY_INSTALL_PACKAGES = [
|
|||||||
// emfs:// is Pyodide's Emscripten-FS URL scheme — the only reliable way to
|
// emfs:// is Pyodide's Emscripten-FS URL scheme — the only reliable way to
|
||||||
// install a wheel from bytes without an http/https URL (blob: URLs are not
|
// install a wheel from bytes without an http/https URL (blob: URLs are not
|
||||||
// recognised by micropip and cause an InvalidRequirement parse error).
|
// recognised by micropip and cause an InvalidRequirement parse error).
|
||||||
|
// _wheel_path is set as a Pyodide global before this runs.
|
||||||
const PY_INSTALL_WHEEL = [
|
const PY_INSTALL_WHEEL = [
|
||||||
'import micropip',
|
'import micropip',
|
||||||
'await micropip.install("emfs:///tmp/bincio.whl", deps=False)',
|
'await micropip.install("emfs://" + _wheel_path, deps=False)',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
const PY_EXTRACT = [
|
const PY_EXTRACT = [
|
||||||
@@ -94,6 +95,7 @@ window._bincioExtract = async function(params) {
|
|||||||
var filename = params.filename;
|
var filename = params.filename;
|
||||||
var base64 = params.base64;
|
var base64 = params.base64;
|
||||||
var wheelBase64 = params.wheelBase64; // pre-fetched by React Native (avoids ATS/HTTP issues)
|
var wheelBase64 = params.wheelBase64; // pre-fetched by React Native (avoids ATS/HTTP issues)
|
||||||
|
var wheelFilename = params.wheelFilename; // e.g. "bincio-0.1.0-py3-none-any.whl"
|
||||||
|
|
||||||
function post(m) { _post(Object.assign({}, m, { reqId: reqId })); }
|
function post(m) { _post(Object.assign({}, m, { reqId: reqId })); }
|
||||||
|
|
||||||
@@ -118,7 +120,9 @@ window._bincioExtract = async function(params) {
|
|||||||
if (!wheelReady) {
|
if (!wheelReady) {
|
||||||
post({ type: 'progress', msg: 'Loading Bincio…' });
|
post({ type: 'progress', msg: 'Loading Bincio…' });
|
||||||
var wheelBytes = Uint8Array.from(atob(wheelBase64), function(c) { return c.charCodeAt(0); });
|
var wheelBytes = Uint8Array.from(atob(wheelBase64), function(c) { return c.charCodeAt(0); });
|
||||||
pyodide.FS.writeFile('/tmp/bincio.whl', wheelBytes);
|
var wheelPath = '/tmp/' + wheelFilename;
|
||||||
|
pyodide.FS.writeFile(wheelPath, wheelBytes);
|
||||||
|
pyodide.globals.set('_wheel_path', wheelPath);
|
||||||
await pyodide.runPythonAsync(_PY_INSTALL_WHEEL);
|
await pyodide.runPythonAsync(_PY_INSTALL_WHEEL);
|
||||||
wheelReady = true;
|
wheelReady = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export function extractFile(
|
|||||||
filename: string,
|
filename: string,
|
||||||
base64: string,
|
base64: string,
|
||||||
wheelBase64: string,
|
wheelBase64: string,
|
||||||
|
wheelFilename: string,
|
||||||
onStatus: (msg: string) => void = () => {},
|
onStatus: (msg: string) => void = () => {},
|
||||||
): Promise<ExtractionResult> {
|
): Promise<ExtractionResult> {
|
||||||
if (isExtracting) return Promise.reject(new Error('Another extraction is already in progress'));
|
if (isExtracting) return Promise.reject(new Error('Another extraction is already in progress'));
|
||||||
@@ -69,7 +70,7 @@ export function extractFile(
|
|||||||
|
|
||||||
isExtracting = true;
|
isExtracting = true;
|
||||||
const reqId = String(++reqCounter);
|
const reqId = String(++reqCounter);
|
||||||
const args = JSON.stringify({ reqId, filename, base64, wheelBase64 });
|
const args = JSON.stringify({ reqId, filename, base64, wheelBase64, wheelFilename });
|
||||||
|
|
||||||
return new Promise<ExtractionResult>((resolve, reject) => {
|
return new Promise<ExtractionResult>((resolve, reject) => {
|
||||||
pending.set(reqId, {
|
pending.set(reqId, {
|
||||||
|
|||||||
Reference in New Issue
Block a user