From c32af6f693692afbf282d279af420f06b9d14cdf Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 21 Jun 2020 19:37:48 -0700 Subject: [PATCH] Split out new 'Writing plugins' page, refs #687 --- docs/index.rst | 1 + docs/plugin_hooks.rst | 2 +- docs/plugins.rst | 184 ++------------------------------------- docs/writing_plugins.rst | 156 +++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 176 deletions(-) create mode 100644 docs/writing_plugins.rst diff --git a/docs/index.rst b/docs/index.rst index 20a55b2c..6f3c9574 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -51,6 +51,7 @@ Contents introspection custom_templates plugins + writing_plugins plugin_hooks internals contributing diff --git a/docs/plugin_hooks.rst b/docs/plugin_hooks.rst index 44797d92..2de0cccc 100644 --- a/docs/plugin_hooks.rst +++ b/docs/plugin_hooks.rst @@ -9,7 +9,7 @@ Each plugin can implement one or more hooks using the ``@hookimpl`` decorator ag When you implement a plugin hook you can accept any or all of the parameters that are documented as being passed to that hook. -For example, you can implement the ``render_cell`` plugin hook like thiseven though the full documented hook signature is ``render_cell(value, column, table, database, datasette)``: +For example, you can implement the ``render_cell`` plugin hook like this even though the full documented hook signature is ``render_cell(value, column, table, database, datasette)``: .. code-block:: python diff --git a/docs/plugins.rst b/docs/plugins.rst index 09a6e4f5..03972c7a 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -33,53 +33,21 @@ Things you can do with plugins include: 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. +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 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 serve``. +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:: -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. You can -use the name of a package on PyPI or any of the other valid arguments to ``pip -install`` such as a URL to a ``.zip`` file:: + datasette mydb.db --plugins-dir=plugins/ + +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 + +You can use the name of a package on PyPI or any of the other valid arguments to ``pip install`` such as a URL to a ``.zip`` file:: datasette publish cloudrun mydb.db \ - --install=datasette-plugin-demos \ --install=https://url-to-my-package.zip -.. _plugins_writing_one_off: - -Writing one-off plugins ------------------------ - -The easiest way to write a plugin is to create a ``my_plugin.py`` file and -drop it into your ``plugins/`` directory. Here is an example plugin, which -adds a new custom SQL function called ``hello_world()`` which takes no -arguments and returns the string ``Hello world!``. - -.. code-block:: python - - from datasette import hookimpl - - @hookimpl - def prepare_connection(conn): - conn.create_function('hello_world', 0, lambda: 'Hello world!') - -If you save this in ``plugins/my_plugin.py`` you can then start Datasette like -this:: - - datasette serve mydb.db --plugins-dir=plugins/ - -Now you can navigate to http://localhost:8001/mydb and run this SQL:: - - select hello_world(); - -To see the output of your plugin. - .. _plugins_installed: Seeing what plugins are installed @@ -131,96 +99,6 @@ If you run ``datasette plugins --all`` it will include default plugins that ship You can add the ``--plugins-dir=`` option to include any plugins found in that directory. -Packaging a plugin ------------------- - -Plugins can be packaged using Python setuptools. You can see an example of a -packaged plugin at https://github.com/simonw/datasette-plugin-demos - -The example consists of two files: a ``setup.py`` file that defines the plugin: - -.. code-block:: python - - from setuptools import setup - - VERSION = '0.1' - - setup( - name='datasette-plugin-demos', - description='Examples of plugins for Datasette', - author='Simon Willison', - url='https://github.com/simonw/datasette-plugin-demos', - license='Apache License, Version 2.0', - version=VERSION, - py_modules=['datasette_plugin_demos'], - entry_points={ - 'datasette': [ - 'plugin_demos = datasette_plugin_demos' - ] - }, - install_requires=['datasette'] - ) - -And a Python module file, ``datasette_plugin_demos.py``, that implements the -plugin: - -.. code-block:: python - - from datasette import hookimpl - import random - - - @hookimpl - def prepare_jinja2_environment(env): - env.filters['uppercase'] = lambda u: u.upper() - - - @hookimpl - def prepare_connection(conn): - conn.create_function('random_integer', 2, random.randint) - - -Having built a plugin in this way you can turn it into an installable package -using the following command:: - - python3 setup.py sdist - -This will create a ``.tar.gz`` file in the ``dist/`` directory. - -You can then install your new plugin into a Datasette virtual environment or -Docker container using ``pip``:: - - pip install datasette-plugin-demos-0.1.tar.gz - -To learn how to upload your plugin to `PyPI `_ for use by -other people, read the PyPA guide to `Packaging and distributing projects -`_. - -Static assets -------------- - -If your plugin has a ``static/`` directory, Datasette will automatically -configure itself to serve those static assets from the following path:: - - /-/static-plugins/NAME_OF_PLUGIN_PACKAGE/yourfile.js - -See `the datasette-plugin-demos repository `_ -for an example of how to create a package that includes a static folder. - -Custom templates ----------------- - -If your plugin has a ``templates/`` directory, Datasette will attempt to load -templates from that directory before it uses its own default templates. - -The priority order for template loading is: - -* templates from the ``--template-dir`` argument, if specified -* templates from the ``templates/`` directory in any installed plugins -* default templates that ship with Datasette - -See :ref:`customization` for more details on how to write custom templates, -including which filenames to use to customize which parts of the Datasette UI. .. _plugins_configuration: @@ -288,47 +166,3 @@ If you are publishing your data using the :ref:`datasette publish ` --install=datasette-auth-github \ --plugin-secret datasette-auth-github client_id your_client_id \ --plugin-secret datasette-auth-github client_secret your_client_secret - -.. _plugins_plugin_config: - -Writing plugins that accept configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When you are writing plugins, you can access plugin configuration like this using the ``datasette.plugin_config()`` method. If you know you need plugin configuration for a specific table, you can access it like this:: - - plugin_config = datasette.plugin_config( - "datasette-cluster-map", database="sf-trees", table="Street_Tree_List" - ) - -This will return the ``{"latitude_column": "lat", "longitude_column": "lng"}`` in the above example. - -If it cannot find the requested configuration at the table layer, it will fall back to the database layer and then the root layer. For example, a user may have set the plugin configuration option like so:: - - { - "databases: { - "sf-trees": { - "plugins": { - "datasette-cluster-map": { - "latitude_column": "xlat", - "longitude_column": "xlng" - } - } - } - } - } - -In this case, the above code would return that configuration for ANY table within the ``sf-trees`` database. - -The plugin configuration could also be set at the top level of ``metadata.json``:: - - { - "title": "This is the top-level title in metadata.json", - "plugins": { - "datasette-cluster-map": { - "latitude_column": "xlat", - "longitude_column": "xlng" - } - } - } - -Now that ``datasette-cluster-map`` plugin configuration will apply to every table in every database. diff --git a/docs/writing_plugins.rst b/docs/writing_plugins.rst new file mode 100644 index 00000000..d8ede5f3 --- /dev/null +++ b/docs/writing_plugins.rst @@ -0,0 +1,156 @@ +.. _writing_plugins: + +Writing plugins +=============== + +You can write one-off plugins that apply to just one Datasette instance, or you can write plugins which can be installed using ``pip`` and can be shipped to the Python Package Index (`PyPI `__) for other people to install. + +.. _plugins_writing_one_off: + +Writing one-off plugins +----------------------- + +The easiest way to write a plugin is to create a ``my_plugin.py`` file and drop it into your ``plugins/`` directory. Here is an example plugin, which adds a new custom SQL function called ``hello_world()`` which takes no arguments and returns the string ``Hello world!``. + +.. code-block:: python + + from datasette import hookimpl + + @hookimpl + def prepare_connection(conn): + conn.create_function('hello_world', 0, lambda: 'Hello world!') + +If you save this in ``plugins/my_plugin.py`` you can then start Datasette like this:: + + datasette serve mydb.db --plugins-dir=plugins/ + +Now you can navigate to http://localhost:8001/mydb and run this SQL:: + + select hello_world(); + +To see the output of your plugin. + +Packaging a plugin +------------------ + +Plugins can be packaged using Python setuptools. You can see an example of a packaged plugin at https://github.com/simonw/datasette-plugin-demos + +The example consists of two files: a ``setup.py`` file that defines the plugin: + +.. code-block:: python + + from setuptools import setup + + VERSION = '0.1' + + setup( + name='datasette-plugin-demos', + description='Examples of plugins for Datasette', + author='Simon Willison', + url='https://github.com/simonw/datasette-plugin-demos', + license='Apache License, Version 2.0', + version=VERSION, + py_modules=['datasette_plugin_demos'], + entry_points={ + 'datasette': [ + 'plugin_demos = datasette_plugin_demos' + ] + }, + install_requires=['datasette'] + ) + +And a Python module file, ``datasette_plugin_demos.py``, that implements the plugin: + +.. code-block:: python + + from datasette import hookimpl + import random + + + @hookimpl + def prepare_jinja2_environment(env): + env.filters['uppercase'] = lambda u: u.upper() + + + @hookimpl + def prepare_connection(conn): + conn.create_function('random_integer', 2, random.randint) + + +Having built a plugin in this way you can turn it into an installable package using the following command:: + + python3 setup.py sdist + +This will create a ``.tar.gz`` file in the ``dist/`` directory. + +You can then install your new plugin into a Datasette virtual environment or Docker container using ``pip``:: + + pip install datasette-plugin-demos-0.1.tar.gz + +To learn how to upload your plugin to `PyPI `_ for use by other people, read the PyPA guide to `Packaging and distributing projects `_. + +Static assets +------------- + +If your plugin has a ``static/`` directory, Datasette will automatically configure itself to serve those static assets from the following path:: + + /-/static-plugins/NAME_OF_PLUGIN_PACKAGE/yourfile.js + +See `the datasette-plugin-demos repository `_ for an example of how to create a package that includes a static folder. + +Custom templates +---------------- + +If your plugin has a ``templates/`` directory, Datasette will attempt to load templates from that directory before it uses its own default templates. + +The priority order for template loading is: + +* templates from the ``--template-dir`` argument, if specified +* templates from the ``templates/`` directory in any installed plugins +* default templates that ship with Datasette + +See :ref:`customization` for more details on how to write custom templates, including which filenames to use to customize which parts of the Datasette UI. + +.. _plugins_plugin_config: + +Writing plugins that accept configuration +----------------------------------------- + +When you are writing plugins, you can access plugin configuration like this using the ``datasette plugin_config()`` method. If you know you need plugin configuration for a specific table, you can access it like this:: + + plugin_config = datasette.plugin_config( + "datasette-cluster-map", database="sf-trees", table="Street_Tree_List" + ) + +This will return the ``{"latitude_column": "lat", "longitude_column": "lng"}`` in the above example. + +If it cannot find the requested configuration at the table layer, it will fall back to the database layer and then the root layer. For example, a user may have set the plugin configuration option like so:: + + { + "databases: { + "sf-trees": { + "plugins": { + "datasette-cluster-map": { + "latitude_column": "xlat", + "longitude_column": "xlng" + } + } + } + } + } + +In this case, the above code would return that configuration for ANY table within the ``sf-trees`` database. + +The plugin configuration could also be set at the top level of ``metadata.json``:: + + { + "title": "This is the top-level title in metadata.json", + "plugins": { + "datasette-cluster-map": { + "latitude_column": "xlat", + "longitude_column": "xlng" + } + } + } + +Now that ``datasette-cluster-map`` plugin configuration will apply to every table in every database.