From 01fe5b740171bfaea3752fc5754431dac53777e3 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 11 Aug 2020 15:31:47 -0700 Subject: [PATCH] datasette install / datasette uninstall commands, closes #925 --- datasette/cli.py | 25 +++++++++++++++++++++++++ docs/plugins.rst | 16 ++++++++++++++++ tests/test_cli.py | 17 +++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/datasette/cli.py b/datasette/cli.py index 287195a5..27040767 100644 --- a/datasette/cli.py +++ b/datasette/cli.py @@ -231,6 +231,31 @@ def package( call(args) +@cli.command() +@click.argument("packages", nargs=-1, required=True) +def install(packages): + "Install Python packages - e.g. Datasette plugins - into the same environment as Datasett" + from pip._internal.cli.main import main + + try: + main(["install"] + list(packages)) + except SystemExit as e: + pass + + +@cli.command() +@click.argument("packages", nargs=-1, required=True) +@click.option("-y", "--yes", is_flag=True, help="Don't ask for confirmation") +def uninstall(packages, yes): + "Uninstall Python packages (e.g. plugins) from the Datasette environment" + from pip._internal.cli.main import main + + try: + main(["uninstall"] + list(packages) + (["-y"] if yes else [])) + except SystemExit as e: + pass + + @cli.command() @click.argument("files", type=click.Path(exists=True), nargs=-1) @click.option( diff --git a/docs/plugins.rst b/docs/plugins.rst index 03972c7a..e67c77b3 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -35,10 +35,26 @@ Installing plugins If a plugin has been packaged for distribution using setuptools you can use the plugin by installing it alongside Datasette in the same virtual environment or Docker container. +You can install plugins using the ``datasette install`` command:: + + datasette install datasette-vega + +You can uninstall plugins with ``datasette uninstall``:: + + datasette uninstall datasette-vega + +These ommands are thin wrappers around ``pip install`` and ``pip uninstall``, which ensure they run ``pip`` in the same virtual environment as Datasette itself. + +One-off plugins using --plugins-dir +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + You can also define one-off per-project plugins by saving them as ``plugin_name.py`` functions in a ``plugins/`` folder and then passing that folder to ``datasette`` using the ``--plugins-dir`` option:: datasette mydb.db --plugins-dir=plugins/ +Deploying plugins using datasette publish +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + The ``datasette publish`` and ``datasette package`` commands both take an optional ``--install`` argument. You can use this one or more times to tell Datasette to ``pip install`` specific plugins as part of the process:: datasette publish cloudrun mydb.db --install=datasette-vega diff --git a/tests/test_cli.py b/tests/test_cli.py index 90aa990d..83305dc4 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -12,6 +12,7 @@ import json import pathlib import pytest import textwrap +from unittest import mock def test_inspect_cli(app_client): @@ -107,3 +108,19 @@ def test_metadata_yaml(): client.ds = ds response = client.get("/-/metadata.json") assert {"title": "Hello from YAML"} == response.json + + +@mock.patch("pip._internal.cli.main.main") +def test_install(main): + runner = CliRunner() + runner.invoke(cli, ["install", "datasette-mock-plugin", "datasette-mock-plugin2"]) + main.assert_called_once_with( + ["install", "datasette-mock-plugin", "datasette-mock-plugin2"] + ) + + +@mock.patch("pip._internal.cli.main.main") +def test_uninstall(main): + runner = CliRunner() + runner.invoke(cli, ["uninstall", "datasette-mock-plugin", "-y"]) + main.assert_called_once_with(["uninstall", "datasette-mock-plugin", "-y"])