register_commands() plugin hook, closes #1449

pull/1453/head
Simon Willison 2021-08-27 18:39:42 -07:00
rodzic 3655bb49a4
commit 30c18576d6
4 zmienionych plików z 109 dodań i 1 usunięć

Wyświetl plik

@ -595,6 +595,9 @@ def serve(
uvicorn.run(ds.app(), **uvicorn_kwargs)
pm.hook.register_commands(cli=cli)
async def check_databases(ds):
# Run check_connection against every connected database
# to confirm they are all usable

Wyświetl plik

@ -79,6 +79,11 @@ def register_routes(datasette):
"""Register URL routes: return a list of (regex, view_function) pairs"""
@hookspec
def register_commands(cli):
"""Register additional CLI commands, e.g. 'datasette mycommand ...'"""
@hookspec
def actor_from_request(datasette, request):
"""Return an actor dictionary based on the incoming request"""

Wyświetl plik

@ -587,6 +587,51 @@ See :ref:`writing_plugins_designing_urls` for tips on designing the URL routes u
Examples: `datasette-auth-github <https://datasette.io/plugins/datasette-auth-github>`__, `datasette-psutil <https://datasette.io/plugins/datasette-psutil>`__
.. _plugin_register_commands:
register_commands(cli)
----------------------
``cli`` - the root Datasette `Click command group <https://click.palletsprojects.com/en/latest/commands/#callback-invocation>`__
Use this to register additional CLI commands
Register additional CLI commands that can be run using ``datsette yourcommand ...``. This provides a mechanism by which plugins can add new CLI commands to Datasette.
This example registers a new ``datasette verify file1.db file2.db`` command that checks if the provided file paths are valid SQLite databases:
.. code-block:: python
from datasette import hookimpl
import click
import sqlite3
@hookimpl
def register_commands(cli):
@cli.command()
@click.argument("files", type=click.Path(exists=True), nargs=-1)
def verify(files):
"Verify that files can be opened by Datasette"
for file in files:
conn = sqlite3.connect(str(file))
try:
conn.execute("select * from sqlite_master")
except sqlite3.DatabaseError:
raise click.ClickException("Invalid database: {}".format(file))
The new command can then be executed like so::
datasette verify fixtures.db
Help text (from the docstring for the function plus any defined Click arguments or options) will become available using::
datasette verify --help
Plugins can register multiple commands by making multiple calls to the ``@cli.command()`` decorator.Consult the `Click documentation <https://click.palletsprojects.com/>`__ for full details on how to build a CLI command, including how to define arguments and options.
Note that ``register_commands()`` plugins cannot used with the :ref:`--plugins-dir mechanism <writing_plugins_one_off>` - they need to be installed into the same virtual environment as Datasette using ``pip install``. Provided it has a ``setup.py`` file (see :ref:`writing_plugins_packaging`) you can run ``pip install`` directly against the directory in which you are developing your plugin like so::
pip install -e path/to/my/datasette-plugin
.. _plugin_register_facet_classes:
register_facet_classes()

Wyświetl plik

@ -6,13 +6,15 @@ from .fixtures import (
TEMP_PLUGIN_SECRET_FILE,
TestClient as _TestClient,
) # noqa
from click.testing import CliRunner
from datasette.app import Datasette
from datasette import cli
from datasette import cli, hookimpl
from datasette.plugins import get_plugins, DEFAULT_PLUGINS, pm
from datasette.utils.sqlite import sqlite3
from datasette.utils import CustomRow
from jinja2.environment import Template
import base64
import importlib
import json
import os
import pathlib
@ -902,3 +904,56 @@ def test_hook_get_metadata(app_client):
assert "Hello from local metadata" == meta["databases"]["from-local"]["title"]
assert "Hello from the plugin hook" == meta["databases"]["from-hook"]["title"]
pm.hook.get_metadata = og_pm_hook_get_metadata
def _extract_commands(output):
lines = output.split("Commands:\n", 1)[1].split("\n")
return {line.split()[0].replace("*", "") for line in lines if line.strip()}
def test_hook_register_commands():
# Without the plugin should have seven commands
runner = CliRunner()
result = runner.invoke(cli.cli, "--help")
commands = _extract_commands(result.output)
assert commands == {
"serve",
"inspect",
"install",
"package",
"plugins",
"publish",
"uninstall",
}
# Now install a plugin
class VerifyPlugin:
__name__ = "VerifyPlugin"
@hookimpl
def register_commands(self, cli):
@cli.command()
def verify():
pass
@cli.command()
def unverify():
pass
pm.register(VerifyPlugin(), name="verify")
importlib.reload(cli)
result2 = runner.invoke(cli.cli, "--help")
commands2 = _extract_commands(result2.output)
assert commands2 == {
"serve",
"inspect",
"install",
"package",
"plugins",
"publish",
"uninstall",
"verify",
"unverify",
}
pm.unregister(name="verify")
importlib.reload(cli)