kopia lustrzana https://github.com/simonw/datasette
register_commands() plugin hook, closes #1449
rodzic
3655bb49a4
commit
30c18576d6
|
@ -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
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
Ładowanie…
Reference in New Issue