datasette.utils.StartupError() now becomes a click exception, closes #2624

This commit is contained in:
Simon Willison 2026-01-06 07:58:18 -08:00
commit 757ce92baf
3 changed files with 39 additions and 5 deletions

View file

@ -666,7 +666,10 @@ def serve(
return ds
# Run the "startup" plugin hooks
run_sync(ds.invoke_startup)
try:
run_sync(ds.invoke_startup)
except StartupError as e:
raise click.ClickException(e.args[0])
# Run async soundness checks - but only if we're not under pytest
run_sync(lambda: check_databases(ds))
@ -815,7 +818,10 @@ def create_token(
ds = Datasette(secret=secret, plugins_dir=plugins_dir)
# Run ds.invoke_startup() in an event loop
run_sync(ds.invoke_startup)
try:
run_sync(ds.invoke_startup)
except StartupError as e:
raise click.ClickException(e.args[0])
# Warn about any unknown actions
actions = []

View file

@ -965,12 +965,13 @@ Here is an example that validates required plugin configuration. The server will
.. code-block:: python
from datasette.utils import StartupError
@hookimpl
def startup(datasette):
config = datasette.plugin_config("my-plugin") or {}
assert (
"required-setting" in config
), "my-plugin requires setting required-setting"
if "required-setting" not in config:
raise StartupError("my-plugin requires setting required-setting")
You can also return an async function, which will be awaited on startup. Use this option if you need to execute any database queries, for example this function which creates the ``my_table`` database table if it does not yet exist:
@ -994,6 +995,7 @@ Potential use-cases:
* Run some initialization code for the plugin
* Create database tables that a plugin needs on startup
* Validate the configuration for a plugin on startup, and raise an error if it is invalid
* Raise a ``datasette.utils.StartupError("message")`` exception to prevent Datasette from starting and display that message to the user.
.. note::

View file

@ -304,6 +304,32 @@ def test_plugin_s_overwrite():
)
def test_startup_error_from_plugin_is_click_exception(tmp_path):
plugins_dir = tmp_path / "plugins"
plugins_dir.mkdir()
(plugins_dir / "startup_error.py").write_text(
"from datasette import hookimpl\n"
"from datasette.utils import StartupError\n"
"\n"
"@hookimpl\n"
"def startup(datasette):\n"
' raise StartupError("boom")\n',
"utf-8",
)
runner = CliRunner()
result = runner.invoke(
cli,
[
"--plugins-dir",
str(plugins_dir),
"--get",
"/",
],
)
assert result.exit_code == 1
assert "Error: boom" in result.output
def test_setting_type_validation():
runner = CliRunner()
result = runner.invoke(cli, ["--setting", "default_page_size", "dog"])