@@ -78,6 +78,40 @@ function pyStr(s: string): string {
7878 return JSON . stringify ( s ) ;
7979}
8080
81+ /**
82+ * Turn a raw micropip failure into a web-version-aware message.
83+ *
84+ * The web app runs Python through Pyodide, which can only install
85+ * pure-Python wheels (or packages Pyodide ships pre-built). Toolboxes with
86+ * compiled/native code fail here even though they install fine in the
87+ * standalone (pip-backed) PathView. `_pv_install_micropip` tags those
88+ * failures with `PV_INCOMPATIBLE`, so we give a useful hint instead of
89+ * surfacing a raw traceback. Genuine failures pass through unchanged.
90+ */
91+ function reframePyodideInstallError ( spec : string , err : unknown ) : Error {
92+ const raw = err instanceof Error ? err . message : String ( err ) ;
93+ if ( ! raw . includes ( 'PV_INCOMPATIBLE' ) ) {
94+ // Network error, bad spec, etc. — pass through, just strip our tag.
95+ return new Error ( raw . replace ( / P V _ I N S T A L L _ E R R O R : \s * / , '' ) ) ;
96+ }
97+ const detail = raw . split ( 'PV_INCOMPATIBLE:' ) . pop ( ) ?. trim ( ) || raw ;
98+ return new Error (
99+ `"${ spec } " can't be installed in the PathView web app.\n` +
100+ `\n` +
101+ `The web version runs Python in your browser via Pyodide, which can\n` +
102+ `only install pure-Python packages (or packages Pyodide ships\n` +
103+ `pre-built). This toolbox needs compiled or native code that isn't\n` +
104+ `available in the browser.\n` +
105+ `\n` +
106+ `To use it, install the standalone PathView desktop app:\n` +
107+ ` pip install pathview\n` +
108+ ` pathview\n` +
109+ `It runs a real Python environment and can install any pip package.\n` +
110+ `\n` +
111+ `micropip: ${ detail } `
112+ ) ;
113+ }
114+
81115/**
82116 * Install a package. Skips when `importPath` is given and the module is
83117 * already importable (saves a round-trip + download).
@@ -94,8 +128,13 @@ export async function installPackage(spec: string, importPath?: string): Promise
94128 }
95129 const backend = getBackendType ( ) ;
96130 if ( backend === 'pyodide' ) {
97- // runPythonAsync supports top-level await for micropip.install
98- await exec ( `await _pv_install_micropip(${ pyStr ( spec ) } )` ) ;
131+ // runPythonAsync supports top-level await for micropip.install.
132+ // Reframe Pyodide-incompatibility failures into an actionable hint.
133+ try {
134+ await exec ( `await _pv_install_micropip(${ pyStr ( spec ) } )` ) ;
135+ } catch ( e ) {
136+ throw reframePyodideInstallError ( spec , e ) ;
137+ }
99138 } else {
100139 // Flask / remote: real CPython, use subprocess pip (sync)
101140 await exec ( `_pv_install_pip(${ pyStr ( spec ) } )` ) ;
0 commit comments