From 36e77e100632573e1cf907aba9462debac7928e9 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 21 Jun 2020 17:33:48 -0700 Subject: [PATCH] Move plugin hooks docs to plugin_hooks.rst, refs #687 --- docs/index.rst | 1 + docs/plugin_hooks.rst | 888 +++++++++++++++++++++++++++++++++++++++++ docs/plugins.rst | 889 ------------------------------------------ 3 files changed, 889 insertions(+), 889 deletions(-) create mode 100644 docs/plugin_hooks.rst diff --git a/docs/index.rst b/docs/index.rst index fa5d7f87..20a55b2c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -51,6 +51,7 @@ Contents introspection custom_templates plugins + plugin_hooks internals contributing changelog diff --git a/docs/plugin_hooks.rst b/docs/plugin_hooks.rst new file mode 100644 index 00000000..19f076b9 --- /dev/null +++ b/docs/plugin_hooks.rst @@ -0,0 +1,888 @@ +.. _plugin_hooks: + +Plugin hooks +============ + +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 a ``render_cell`` plugin hook like this even though the hook definition defines more parameters than just ``value`` and ``column``: + +.. code-block:: python + + @hookimpl + def render_cell(value, column): + if column == "stars": + return "*" * int(value) + +The full list of available plugin hooks is as follows. + +.. _plugin_hook_prepare_connection: + +prepare_connection(conn, database, datasette) +--------------------------------------------- + +``conn`` - sqlite3 connection object + The connection that is being opened + +``database`` - string + The name of the database + +``datasette`` - :ref:`internals_datasette` + You can use this to access plugin configuration options via ``datasette.plugin_config(your_plugin_name)`` + +This hook is called when a new SQLite database connection is created. You can +use it to `register custom SQL functions `_, +aggregates and collations. For example: + +.. code-block:: python + + from datasette import hookimpl + import random + + @hookimpl + def prepare_connection(conn): + conn.create_function('random_integer', 2, random.randint) + +This registers a SQL function called ``random_integer`` which takes two +arguments and can be called like this:: + + select random_integer(1, 10); + +Examples: `datasette-jellyfish `_, `datasette-jq `_, `datasette-haversine `__, `datasette-rure `__ + +.. _plugin_hook_prepare_jinja2_environment: + +prepare_jinja2_environment(env) +------------------------------- + +``env`` - jinja2 Environment + The template environment that is being prepared + +This hook is called with the Jinja2 environment that is used to evaluate +Datasette HTML templates. You can use it to do things like `register custom +template filters `_, for +example: + +.. code-block:: python + + from datasette import hookimpl + + @hookimpl + def prepare_jinja2_environment(env): + env.filters['uppercase'] = lambda u: u.upper() + +You can now use this filter in your custom templates like so:: + + Table name: {{ table|uppercase }} + +.. _plugin_hook_extra_css_urls: + +extra_css_urls(template, database, table, datasette) +---------------------------------------------------- + +``template`` - string + The template that is being rendered, e.g. ``database.html`` + +``database`` - string or None + The name of the database + +``table`` - string or None + The name of the table + +``datasette`` - :ref:`internals_datasette` + You can use this to access plugin configuration options via ``datasette.plugin_config(your_plugin_name)`` + +Return a list of extra CSS URLs that should be included on the page. These can +take advantage of the CSS class hooks described in :ref:`customization`. + +This can be a list of URLs: + +.. code-block:: python + + from datasette import hookimpl + + @hookimpl + def extra_css_urls(): + return [ + 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css' + ] + +Or a list of dictionaries defining both a URL and an +`SRI hash `_: + +.. code-block:: python + + from datasette import hookimpl + + @hookimpl + def extra_css_urls(): + return [{ + 'url': 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css', + 'sri': 'sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4', + }] + +Examples: `datasette-cluster-map `_, `datasette-vega `_ + +.. _plugin_hook_extra_js_urls: + +extra_js_urls(template, database, table, datasette) +--------------------------------------------------- + +Same arguments as ``extra_css_urls``. + +This works in the same way as ``extra_css_urls()`` but for JavaScript. You can +return either a list of URLs or a list of dictionaries: + +.. code-block:: python + + from datasette import hookimpl + + @hookimpl + def extra_js_urls(): + return [{ + 'url': 'https://code.jquery.com/jquery-3.3.1.slim.min.js', + 'sri': 'sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo', + }] + +You can also return URLs to files from your plugin's ``static/`` directory, if +you have one: + +.. code-block:: python + + from datasette import hookimpl + + @hookimpl + def extra_js_urls(): + return [ + '/-/static-plugins/your-plugin/app.js' + ] + +Examples: `datasette-cluster-map `_, `datasette-vega `_ + +.. _plugin_hook_publish_subcommand: + +publish_subcommand(publish) +--------------------------- + +``publish`` - Click publish command group + The Click command group for the ``datasette publish`` subcommand + +This hook allows you to create new providers for the ``datasette publish`` +command. Datasette uses this hook internally to implement the default ``now`` +and ``heroku`` subcommands, so you can read +`their source `_ +to see examples of this hook in action. + +Let's say you want to build a plugin that adds a ``datasette publish my_hosting_provider --api_key=xxx mydatabase.db`` publish command. Your implementation would start like this: + +.. code-block:: python + + from datasette import hookimpl + from datasette.publish.common import add_common_publish_arguments_and_options + import click + + + @hookimpl + def publish_subcommand(publish): + @publish.command() + @add_common_publish_arguments_and_options + @click.option( + "-k", + "--api_key", + help="API key for talking to my hosting provider", + ) + def my_hosting_provider( + files, + metadata, + extra_options, + branch, + template_dir, + plugins_dir, + static, + install, + plugin_secret, + version_note, + secret, + title, + license, + license_url, + source, + source_url, + about, + about_url, + api_key, + ): + # Your implementation goes here + +Examples: `datasette-publish-fly `_, `datasette-publish-now `_ + +.. _plugin_hook_render_cell: + +render_cell(value, column, table, database, datasette) +------------------------------------------------------ + +Lets you customize the display of values within table cells in the HTML table view. + +``value`` - string, integer or None + The value that was loaded from the database + +``column`` - string + The name of the column being rendered + +``table`` - string or None + The name of the table - or ``None`` if this is a custom SQL query + +``database`` - string + The name of the database + +``datasette`` - :ref:`internals_datasette` + You can use this to access plugin configuration options via ``datasette.plugin_config(your_plugin_name)`` + +If your hook returns ``None``, it will be ignored. Use this to indicate that your hook is not able to custom render this particular value. + +If the hook returns a string, that string will be rendered in the table cell. + +If you want to return HTML markup you can do so by returning a ``jinja2.Markup`` object. + +Datasette will loop through all available ``render_cell`` hooks and display the value returned by the first one that does not return ``None``. + +Here is an example of a custom ``render_cell()`` plugin which looks for values that are a JSON string matching the following format:: + + {"href": "https://www.example.com/", "label": "Name"} + +If the value matches that pattern, the plugin returns an HTML link element: + +.. code-block:: python + + from datasette import hookimpl + import jinja2 + import json + + + @hookimpl + def render_cell(value): + # Render {"href": "...", "label": "..."} as link + if not isinstance(value, str): + return None + stripped = value.strip() + if not stripped.startswith("{") and stripped.endswith("}"): + return None + try: + data = json.loads(value) + except ValueError: + return None + if not isinstance(data, dict): + return None + if set(data.keys()) != {"href", "label"}: + return None + href = data["href"] + if not ( + href.startswith("/") or href.startswith("http://") + or href.startswith("https://") + ): + return None + return jinja2.Markup('{label}'.format( + href=jinja2.escape(data["href"]), + label=jinja2.escape(data["label"] or "") or " " + )) + +Examples: `datasette-render-binary `_, `datasette-render-markdown `_ + +.. _plugin_hook_extra_body_script: + +extra_body_script(template, database, table, view_name, datasette) +------------------------------------------------------------------ + +Extra JavaScript to be added to a ``