mirror of
https://github.com/pyscript/pyscript.git
synced 2026-03-31 17:03:24 -04:00
Improve UX when we can't install a package (#1000)
This commit is contained in:
@@ -8,6 +8,7 @@ This reference guide contains the error codes you might find and a description o
|
||||
| Error code | Description | Recommendation |
|
||||
|------------|--------------------------------|--------------------|
|
||||
| PY1000 | Invalid configuration supplied | Confirm that your `py-config` tag is using a valid `TOML` or `JSON` syntax and is using the correct configuration type. |
|
||||
| PY1001 | Unable to install package(s) | Confirm that the package contains a pure Python 3 wheel or the name of the package is correct. |
|
||||
| PY9000 | Top level await is deprecated | Create a coroutine with your code and schedule it with `asyncio.ensure_future` or similar |
|
||||
|
||||
|
||||
@@ -26,3 +27,13 @@ These error codes are related to any exception raised when trying to fetch a res
|
||||
| PY0404 | The page you are trying to fetch does not exist. |
|
||||
| PY0500 | The server encountered an internal error. |
|
||||
| PY0503 | The server is currently unavailable. |
|
||||
|
||||
## PY1001
|
||||
|
||||
Pyscript cannot install the package(s) you specified in your `py-config` tag. This can happen for a few reasons:
|
||||
|
||||
- The package does not exist
|
||||
- The package does not contain a pure Python 3 wheel
|
||||
- An error occurred while trying to install the package
|
||||
|
||||
An error banner should appear on your page with the error code and a description of the error or a traceback. You can also check the developer console for more information.
|
||||
|
||||
@@ -21,6 +21,7 @@ export enum ErrorCode {
|
||||
FETCH_SERVER_ERROR = "PY0500",
|
||||
FETCH_UNAVAILABLE_ERROR = "PY0503",
|
||||
BAD_CONFIG = "PY1000",
|
||||
MICROPIP_INSTALL_ERROR = "PY1001",
|
||||
TOP_LEVEL_AWAIT = "PY9000"
|
||||
}
|
||||
|
||||
@@ -48,6 +49,15 @@ export class FetchError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class InstallError extends UserError {
|
||||
errorCode: ErrorCode;
|
||||
constructor(errorCode: ErrorCode, message: string) {
|
||||
super(errorCode, message)
|
||||
this.name = "InstallError";
|
||||
}
|
||||
}
|
||||
|
||||
export function _createAlertBanner(
|
||||
message: string,
|
||||
level: 'error' | 'warning' = 'error',
|
||||
|
||||
@@ -227,8 +227,10 @@ be removed from the Python global namespace in the following release. \
|
||||
To avoid errors in future releases use import from pyscript instead. For instance: \
|
||||
from pyscript import micropip, Element, console, document`);
|
||||
|
||||
logger.info('Packages to install: ', this.config.packages);
|
||||
await runtime.installPackage(this.config.packages);
|
||||
if (this.config.packages) {
|
||||
logger.info('Packages to install: ', this.config.packages);
|
||||
await runtime.installPackage(this.config.packages);
|
||||
}
|
||||
await this.fetchPaths(runtime);
|
||||
|
||||
//This may be unnecessary - only useful if plugins try to import files fetch'd in fetchPaths()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Runtime } from './runtime';
|
||||
import { getLogger } from './logger';
|
||||
import { InstallError, ErrorCode } from './exceptions'
|
||||
import type { loadPyodide as loadPyodideDeclaration, PyodideInterface, PyProxy } from 'pyodide';
|
||||
import { robustFetch } from './fetch';
|
||||
import type { AppConfig } from './pyconfig';
|
||||
@@ -67,8 +68,10 @@ export class PyodideRuntime extends Runtime {
|
||||
|
||||
this.globals = this.interpreter.globals;
|
||||
|
||||
// XXX: ideally, we should load micropip only if we actually need it
|
||||
await this.loadPackage('micropip');
|
||||
if (this.config.packages) {
|
||||
logger.info("Found packages in configuration to install. Loading micropip...")
|
||||
await this.loadPackage('micropip');
|
||||
}
|
||||
logger.info('pyodide loaded and initialized');
|
||||
}
|
||||
|
||||
@@ -89,8 +92,41 @@ export class PyodideRuntime extends Runtime {
|
||||
if (package_name.length > 0) {
|
||||
logger.info(`micropip install ${package_name.toString()}`);
|
||||
const micropip = this.globals.get('micropip') as Micropip;
|
||||
await micropip.install(package_name);
|
||||
micropip.destroy();
|
||||
try {
|
||||
await micropip.install(package_name);
|
||||
micropip.destroy();
|
||||
} catch(e) {
|
||||
let exceptionMessage = `Unable to install package(s) '` + package_name +`'.`
|
||||
|
||||
// If we can't fetch `package_name` micropip.install throws a huge
|
||||
// Python traceback in `e.message` this logic is to handle the
|
||||
// error and throw a more sensible error message instead of the
|
||||
// huge traceback.
|
||||
if (e.message.includes("Can't find a pure Python 3 wheel")) {
|
||||
exceptionMessage += (
|
||||
` Reason: Can't find a pure Python 3 Wheel for package(s) '` + package_name +
|
||||
`'. See: https://pyodide.org/en/stable/usage/faq.html#micropip-can-t-find-a-pure-python-wheel ` +
|
||||
`for more information.`
|
||||
)
|
||||
} else if (e.message.includes("Can't fetch metadata")) {
|
||||
exceptionMessage += (
|
||||
" Unable to find package in PyPI. " +
|
||||
"Please make sure you have entered a correct package name."
|
||||
)
|
||||
} else {
|
||||
exceptionMessage += (
|
||||
` Reason: ${e.message as string}. Please open an issue at ` +
|
||||
`https://github.com/pyscript/pyscript/issues/new if you require help or ` +
|
||||
`you think it's a bug.`)
|
||||
}
|
||||
|
||||
logger.error(e);
|
||||
|
||||
throw new InstallError(
|
||||
ErrorCode.MICROPIP_INSTALL_ERROR,
|
||||
exceptionMessage
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,43 @@ class TestBasic(PyScriptTest):
|
||||
"hello asciitree", # printed by us
|
||||
]
|
||||
|
||||
def test_non_existent_package(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-config>
|
||||
packages = ["nonexistendright"]
|
||||
</py-config>
|
||||
""",
|
||||
wait_for_pyscript=False,
|
||||
)
|
||||
|
||||
expected_alert_banner_msg = (
|
||||
"(PY1001): Unable to install package(s) 'nonexistendright'. "
|
||||
"Unable to find package in PyPI. Please make sure you have "
|
||||
"entered a correct package name."
|
||||
)
|
||||
|
||||
alert_banner = self.page.wait_for_selector(".alert-banner")
|
||||
assert expected_alert_banner_msg in alert_banner.inner_text()
|
||||
|
||||
def test_no_python_wheel(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-config>
|
||||
packages = ["opsdroid"]
|
||||
</py-config>
|
||||
""",
|
||||
wait_for_pyscript=False,
|
||||
)
|
||||
|
||||
expected_alert_banner_msg = (
|
||||
"(PY1001): Unable to install package(s) 'opsdroid'. "
|
||||
"Reason: Can't find a pure Python 3 Wheel for package(s) 'opsdroid'"
|
||||
)
|
||||
|
||||
alert_banner = self.page.wait_for_selector(".alert-banner")
|
||||
assert expected_alert_banner_msg in alert_banner.inner_text()
|
||||
|
||||
def test_dynamically_add_py_script_tag(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user