mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Split out new 'Writing plugins' page, refs #687
This commit is contained in:
parent
1f42379089
commit
c32af6f693
4 changed files with 166 additions and 175 deletions
|
|
@ -51,6 +51,7 @@ Contents
|
||||||
introspection
|
introspection
|
||||||
custom_templates
|
custom_templates
|
||||||
plugins
|
plugins
|
||||||
|
writing_plugins
|
||||||
plugin_hooks
|
plugin_hooks
|
||||||
internals
|
internals
|
||||||
contributing
|
contributing
|
||||||
|
|
|
||||||
|
|
@ -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.
|
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
|
.. code-block:: python
|
||||||
|
|
||||||
|
|
|
||||||
184
docs/plugins.rst
184
docs/plugins.rst
|
|
@ -33,53 +33,21 @@ Things you can do with plugins include:
|
||||||
Installing plugins
|
Installing plugins
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
If a plugin has been packaged for distribution using setuptools you can use
|
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.
|
||||||
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
|
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::
|
||||||
``plugin_name.py`` functions in a ``plugins/`` folder and then passing that
|
|
||||||
folder to ``datasette serve``.
|
|
||||||
|
|
||||||
The ``datasette publish`` and ``datasette package`` commands both take an
|
datasette mydb.db --plugins-dir=plugins/
|
||||||
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
|
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::
|
||||||
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-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 \
|
datasette publish cloudrun mydb.db \
|
||||||
--install=datasette-plugin-demos \
|
|
||||||
--install=https://url-to-my-package.zip
|
--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:
|
.. _plugins_installed:
|
||||||
|
|
||||||
Seeing what plugins are 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.
|
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 <https://pypi.org/>`_ for use by
|
|
||||||
other people, read the PyPA guide to `Packaging and distributing projects
|
|
||||||
<https://packaging.python.org/tutorials/distributing-packages/>`_.
|
|
||||||
|
|
||||||
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 <https://github.com/simonw/datasette-plugin-demos/tree/0ccf9e6189e923046047acd7878d1d19a2cccbb1>`_
|
|
||||||
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:
|
.. _plugins_configuration:
|
||||||
|
|
||||||
|
|
@ -288,47 +166,3 @@ If you are publishing your data using the :ref:`datasette publish <cli_publish>`
|
||||||
--install=datasette-auth-github \
|
--install=datasette-auth-github \
|
||||||
--plugin-secret datasette-auth-github client_id your_client_id \
|
--plugin-secret datasette-auth-github client_id your_client_id \
|
||||||
--plugin-secret datasette-auth-github client_secret your_client_secret
|
--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.
|
|
||||||
|
|
|
||||||
156
docs/writing_plugins.rst
Normal file
156
docs/writing_plugins.rst
Normal file
|
|
@ -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 <https://pypi.org/>`__) 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 <https://pypi.org/>`_ for use by other people, read the PyPA guide to `Packaging and distributing projects <https://packaging.python.org/tutorials/distributing-packages/>`_.
|
||||||
|
|
||||||
|
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 <https://github.com/simonw/datasette-plugin-demos/tree/0ccf9e6189e923046047acd7878d1d19a2cccbb1>`_ 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.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue