mirror of
https://github.com/simonw/sqlite-utils.git
synced 2026-07-23 01:14:31 +02:00
parent
bff240032d
commit
b379a2a0c3
11 changed files with 224 additions and 2 deletions
|
|
@ -11,6 +11,8 @@ This page lists the ``--help`` for every ``sqlite-utils`` CLI sub-command.
|
|||
|
||||
.. [[[cog
|
||||
from sqlite_utils import cli
|
||||
import sys
|
||||
sys._called_from_test = True
|
||||
from click.testing import CliRunner
|
||||
import textwrap
|
||||
commands = list(cli.cli.commands.keys())
|
||||
|
|
@ -1500,4 +1502,19 @@ See :ref:`cli_spatialite_indexes`.
|
|||
-h, --help Show this message and exit.
|
||||
|
||||
|
||||
.. _cli_ref_plugins:
|
||||
|
||||
plugins
|
||||
=======
|
||||
|
||||
::
|
||||
|
||||
Usage: sqlite-utils plugins [OPTIONS]
|
||||
|
||||
List installed plugins
|
||||
|
||||
Options:
|
||||
-h, --help Show this message and exit.
|
||||
|
||||
|
||||
.. [[[end]]]
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ Contents
|
|||
installation
|
||||
cli
|
||||
python-api
|
||||
plugins
|
||||
reference
|
||||
cli-reference
|
||||
contributing
|
||||
|
|
|
|||
109
docs/plugins.rst
Normal file
109
docs/plugins.rst
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
.. _plugins:
|
||||
|
||||
=========
|
||||
Plugins
|
||||
=========
|
||||
|
||||
``sqlite-utils`` supports plugins, which can be used to add extra features to the software.
|
||||
|
||||
Plugins can add new commands, for example ``sqlite-utils some-command ...``
|
||||
|
||||
Plugins can be installed using the ``sqlite-utils install`` command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sqlite-utils install sqlite-utils-name-of-plugin
|
||||
|
||||
You can see a JSON list of plugins that have been installed by running this:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sqlite-utils plugins
|
||||
|
||||
.. _plugins_building:
|
||||
|
||||
Building a plugin
|
||||
-----------------
|
||||
|
||||
Plugins are created in a directory named after the plugin. To create a "hello world" plugin, first create a ``hello-world`` directory:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
mkdir hello-world
|
||||
cd hello-world
|
||||
|
||||
In that folder create two files. The first is a ``pyproject.toml`` file describing the plugin:
|
||||
|
||||
.. code-block:: toml
|
||||
|
||||
[project]
|
||||
name = "sqlite-utils-hello-world"
|
||||
version = "0.1"
|
||||
|
||||
[project.entry-points.sqlite_utils]
|
||||
hello_world = "sqlite_utils_hello_world"
|
||||
|
||||
The ```[project.entry-points.sqlite_utils]`` section tells ``sqlite-tils`` which module to load when executing the plugin.
|
||||
|
||||
Then create ``sqlite_utils_hello_world.py`` with the following content:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import click
|
||||
import sqlite_utils
|
||||
|
||||
@sqlite_utils.hookimpl
|
||||
def register_commands(cli):
|
||||
@cli.command()
|
||||
def hello_world():
|
||||
"Say hello world"
|
||||
click.echo("Hello world!")
|
||||
|
||||
Install the plugin in "editable" mode - so you can make changes to the code and have them picked up instantly by ``sqlite-utils`` - like this:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sqlite-utils install -e .
|
||||
|
||||
Or pass the path to your plugin directory:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sqlite-utils install -e `/dev/sqlite-utils-hello-world
|
||||
|
||||
Now, running this should execute your new command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sqlite-utils hello-world
|
||||
|
||||
Your command will also be listed in the output of ``sqlite-utils --help``.
|
||||
|
||||
.. _plugins_hooks:
|
||||
|
||||
Plugin hooks
|
||||
------------
|
||||
|
||||
Plugin hooks allow ``sqlite-utils`` to be customized. There is currently one hook.
|
||||
|
||||
.. _plugins_hooks_register_commands:
|
||||
|
||||
register_commands(cli)
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This hook can be used to register additional commands with the ``sqlite-utils`` CLI. It is called with the ``cli`` object, which is a ``click.Group`` instance.
|
||||
|
||||
Example implementation:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import click
|
||||
import sqlite_utils
|
||||
|
||||
@sqlite_utils.hookimpl
|
||||
def register_commands(cli):
|
||||
@cli.command()
|
||||
def hello_world():
|
||||
"Say hello world"
|
||||
click.echo("Hello world!")
|
||||
|
||||
2
setup.py
2
setup.py
|
|
@ -29,6 +29,7 @@ setup(
|
|||
"click-default-group-wheel",
|
||||
"tabulate",
|
||||
"python-dateutil",
|
||||
"pluggy",
|
||||
],
|
||||
extras_require={
|
||||
"test": ["pytest", "black", "hypothesis", "cogapp"],
|
||||
|
|
@ -45,6 +46,7 @@ setup(
|
|||
"types-click",
|
||||
"types-tabulate",
|
||||
"types-python-dateutil",
|
||||
"types-pluggy",
|
||||
"data-science-types",
|
||||
],
|
||||
"flake8": ["flake8"],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
from .db import Database
|
||||
from .utils import suggest_column_types
|
||||
from .hookspecs import hookimpl
|
||||
from .hookspecs import hookspec
|
||||
|
||||
__all__ = ["Database", "suggest_column_types"]
|
||||
__all__ = ["Database", "suggest_column_types", "hookimpl", "hookspec"]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import pathlib
|
|||
from runpy import run_module
|
||||
import sqlite_utils
|
||||
from sqlite_utils.db import AlterError, BadMultiValues, DescIndex, NoTable
|
||||
from sqlite_utils.plugins import pm, get_plugins
|
||||
from sqlite_utils.utils import maximize_csv_field_size_limit
|
||||
from sqlite_utils import recipes
|
||||
import textwrap
|
||||
|
|
@ -3078,6 +3079,15 @@ def create_spatial_index(db_path, table, column_name, load_extension):
|
|||
db[table].create_spatial_index(column_name)
|
||||
|
||||
|
||||
@cli.command(name="plugins")
|
||||
def plugins_list():
|
||||
"List installed plugins"
|
||||
click.echo(json.dumps(get_plugins(), indent=2))
|
||||
|
||||
|
||||
pm.hook.register_commands(cli=cli)
|
||||
|
||||
|
||||
def _render_common(title, values):
|
||||
if values is None:
|
||||
return ""
|
||||
|
|
|
|||
10
sqlite_utils/hookspecs.py
Normal file
10
sqlite_utils/hookspecs.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from pluggy import HookimplMarker
|
||||
from pluggy import HookspecMarker
|
||||
|
||||
hookspec = HookspecMarker("sqlite_utils")
|
||||
hookimpl = HookimplMarker("sqlite_utils")
|
||||
|
||||
|
||||
@hookspec
|
||||
def register_commands(cli):
|
||||
"""Register additional CLI commands, e.g. 'sqlite-utils mycommand ...'"""
|
||||
26
sqlite_utils/plugins.py
Normal file
26
sqlite_utils/plugins.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import pluggy
|
||||
import sys
|
||||
from . import hookspecs
|
||||
|
||||
pm = pluggy.PluginManager("sqlite_utils")
|
||||
pm.add_hookspecs(hookspecs)
|
||||
|
||||
if not hasattr(sys, "_called_from_test"):
|
||||
# Only load plugins if not running tests
|
||||
pm.load_setuptools_entrypoints("sqlite_utils")
|
||||
|
||||
|
||||
def get_plugins():
|
||||
plugins = []
|
||||
plugin_to_distinfo = dict(pm.list_plugin_distinfo())
|
||||
for plugin in pm.get_plugins():
|
||||
plugin_info = {
|
||||
"name": plugin.__name__,
|
||||
"hooks": [h.name for h in pm.get_hookcallers(plugin)],
|
||||
}
|
||||
distinfo = plugin_to_distinfo.get(plugin)
|
||||
if distinfo:
|
||||
plugin_info["version"] = distinfo.version
|
||||
plugin_info["name"] = distinfo.project_name
|
||||
plugins.append(plugin_info)
|
||||
return plugins
|
||||
|
|
@ -8,6 +8,12 @@ create table Gosh2 (c1 text, c2 text, c3 text);
|
|||
"""
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
import sys
|
||||
|
||||
sys._called_from_test = True
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fresh_db():
|
||||
return Database(memory=True)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ recipes_re = re.compile(r"r\.(\w+)\(")
|
|||
|
||||
@pytest.fixture(scope="session")
|
||||
def documented_commands():
|
||||
rst = (docs_path / "cli.rst").read_text()
|
||||
rst = ""
|
||||
for doc in ("cli.rst", "plugins.rst"):
|
||||
rst += (docs_path / doc).read_text()
|
||||
return {
|
||||
command
|
||||
for command in commands_re.findall(rst)
|
||||
|
|
|
|||
37
tests/test_plugins.py
Normal file
37
tests/test_plugins.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
from click.testing import CliRunner
|
||||
import click
|
||||
import importlib
|
||||
from sqlite_utils import cli, hookimpl, plugins
|
||||
|
||||
|
||||
def test_register_commands():
|
||||
importlib.reload(cli)
|
||||
assert plugins.get_plugins() == []
|
||||
|
||||
class HelloWorldPlugin:
|
||||
__name__ = "HelloWorldPlugin"
|
||||
|
||||
@hookimpl
|
||||
def register_commands(self, cli):
|
||||
@cli.command(name="hello-world")
|
||||
def hello_world():
|
||||
"Print hello world"
|
||||
click.echo("Hello world!")
|
||||
|
||||
try:
|
||||
plugins.pm.register(HelloWorldPlugin(), name="HelloWorldPlugin")
|
||||
importlib.reload(cli)
|
||||
|
||||
assert plugins.get_plugins() == [
|
||||
{"name": "HelloWorldPlugin", "hooks": ["register_commands"]}
|
||||
]
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli.cli, ["hello-world"])
|
||||
assert result.exit_code == 0
|
||||
assert result.output == "Hello world!\n"
|
||||
|
||||
finally:
|
||||
plugins.pm.unregister(name="HelloWorldPlugin")
|
||||
importlib.reload(cli)
|
||||
assert plugins.get_plugins() == []
|
||||
Loading…
Add table
Add a link
Reference in a new issue