插件钩子

Datasette 插件使用插件钩子来自定义 Datasette 的行为。这些钩子由 pluggy 插件系统提供支持。

每个插件都可以使用 @hookimpl 装饰器针对一个与本页记录的钩子之一同名的函数来实现一个或多个钩子。

当你实现一个插件钩子时,你可以接受任何或所有被记录为传递给该钩子的参数。

例如,你可以像这样实现 render_cell 插件钩子,即使完整的记录钩子签名是 render_cell(row, value, column, table, database, datasette)

@hookimpl
def render_cell(value, column):
    if column == "stars":
        return "*" * int(value)

prepare_connection(conn, database, datasette)

conn - sqlite3 连接对象

正在打开的连接

database - 字符串

数据库的名称

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项

当创建新的 SQLite 数据库连接时,将调用此钩子。你可以使用它来注册自定义 SQL 函数、聚合和排序规则。例如

from datasette import hookimpl
import random


@hookimpl
def prepare_connection(conn):
    conn.create_function(
        "random_integer", 2, random.randint
    )

这将注册一个名为 random_integer 的 SQL 函数,该函数接受两个参数并可以这样调用

select random_integer(1, 10);

示例:datasette-jellyfish, datasette-jq, datasette-haversine, datasette-rure

prepare_jinja2_environment(env, datasette)

env - jinja2 Environment

正在准备的模板环境

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项

调用此钩子时会传入用于评估 Datasette HTML 模板的 Jinja2 环境。你可以使用它来注册自定义模板过滤器等操作,例如

from datasette import hookimpl


@hookimpl
def prepare_jinja2_environment(env):
    env.filters["uppercase"] = lambda u: u.upper()

现在你可以在你的自定义模板中像这样使用此过滤器

Table name: {{ table|uppercase }}

如果此函数需要运行任何异步代码,它可以返回一个可等待函数。

示例:datasette-edit-templates

extra_template_vars(template, database, table, columns, view_name, request, datasette)

应在渲染的模板上下文中提供的额外模板变量。

template - 字符串

正在渲染的模板,例如 database.html

database - 字符串或 None

数据库的名称,如果页面与数据库不对应(例如根页面),则为 None

table - 字符串或 None

表的名称,如果页面不对应表格,则为 None

columns - 字符串列表或 None

将在此页面上显示的数据库列的名称。如果页面不包含表,则为 None

view_name - 字符串

正在显示的视图名称。(indexdatabasetablerow 是最重要的几个。)

request - 请求对象 或 None

当前的 HTTP 请求。如果请求对象不可用,则可以为 None

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项

此钩子可以返回以下三种不同类型之一

字典

如果你返回一个字典,其键和值将合并到模板上下文中。

返回字典的函数

如果你返回一个函数,它将被执行。如果它返回一个字典,这些值将被合并到模板上下文中。

返回一个返回字典的可等待函数

你还可以返回一个函数,该函数返回一个返回字典的可等待函数。

Datasette 在异步模式下运行 Jinja2,这意味着你可以将可等待函数添加到模板作用域,并且在模板渲染时它们将自动被等待。

这里有一个示例插件,它向模板上下文添加一个 "user_agent" 变量,其中包含当前请求的 User-Agent 头部

@hookimpl
def extra_template_vars(request):
    return {"user_agent": request.headers.get("user-agent")}

此示例返回一个可等待函数,该函数向上下文添加一个 hidden_table_names 列表

@hookimpl
def extra_template_vars(datasette, database):
    async def hidden_table_names():
        if database:
            db = datasette.databases[database]
            return {
                "hidden_table_names": await db.hidden_table_names()
            }
        else:
            return {}

    return hidden_table_names

这里有一个示例,它添加了一个 sql_first(sql_query) 函数,该函数执行 SQL 语句并返回结果的第一行的第一列

@hookimpl
def extra_template_vars(datasette, database):
    async def sql_first(sql, dbname=None):
        dbname = (
            dbname
            or database
            or next(iter(datasette.databases.keys()))
        )
        result = await datasette.execute(dbname, sql)
        return result.rows[0][0]

    return {"sql_first": sql_first}

然后你可以在模板中像这样使用这个新函数

SQLite version: {{ sql_first("select sqlite_version()") }}

示例:datasette-search-all, datasette-template-sql

extra_css_urls(template, database, table, columns, view_name, request, datasette)

此钩子接受与 extra_template_vars(...) 相同的参数

返回应包含在页面上的额外 CSS URL 列表。这些 URL 可以利用 自定义页面和模板 中描述的 CSS 类钩子。

这可以是一个 URL 列表

from datasette import hookimpl


@hookimpl
def extra_css_urls():
    return [
        "https://stackpath.bootstrap.ac.cn/bootstrap/4.1.0/css/bootstrap.min.css"
    ]

或者一个字典列表,定义 URL 和 SRI 哈希

@hookimpl
def extra_css_urls():
    return [
        {
            "url": "https://stackpath.bootstrap.ac.cn/bootstrap/4.1.0/css/bootstrap.min.css",
            "sri": "sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4",
        }
    ]

此函数还可以返回一个可等待函数,这在需要运行任何异步代码时非常有用

@hookimpl
def extra_css_urls(datasette):
    async def inner():
        db = datasette.get_database()
        results = await db.execute(
            "select url from css_files"
        )
        return [r[0] for r in results]

    return inner

示例:datasette-cluster-map, datasette-vega

extra_js_urls(template, database, table, columns, view_name, request, datasette)

此钩子接受与 extra_template_vars(...) 相同的参数

此钩子的工作方式与 extra_css_urls() 相同,但用于 JavaScript。你可以返回一个 URL 列表、一个字典列表或一个返回这些内容的可等待函数

from datasette import hookimpl


@hookimpl
def extra_js_urls():
    return [
        {
            "url": "https://code.jqueryjs.cn/jquery-3.3.1.slim.min.js",
            "sri": "sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo",
        }
    ]

如果你的插件有 static/ 目录,你也可以返回指向该目录中文件的 URL

@hookimpl
def extra_js_urls():
    return ["/-/static-plugins/your-plugin/app.js"]

请注意,此处的 your-plugin 应该是使用连字符连接的插件名称 - 这是在 /-/plugins 调试页面列表上显示的名称。

如果你的代码使用JavaScript 模块,你应该包含 "module": True 键。有关更多详细信息,请参阅自定义 CSS 和 JavaScript

@hookimpl
def extra_js_urls():
    return [
        {
            "url": "/-/static-plugins/your-plugin/app.js",
            "module": True,
        }
    ]

示例:datasette-cluster-map, datasette-vega

extra_body_script(template, database, table, columns, view_name, request, datasette)

应添加到页面 <body> 元素末尾的 <script> 块中的额外 JavaScript。

此钩子接受与 extra_template_vars(...) 相同的参数

可以使用 templatedatabasetableview_name 选项,根据正在渲染的模板以及正在处理的数据库或表返回不同的代码。

提供 datasette 实例主要是为了让你可以使用上面记录的 datasette.plugin_config(plugin_name) 方法查询可能已设置的任何插件配置选项。

此函数可以返回包含 JavaScript 的字符串,或如下所述的字典,或者一个返回字符串或字典的函数或可等待函数。

如果你想指定代码应放置在 <script type="module">...</script> 元素中,请使用字典

@hookimpl
def extra_body_script():
    return {
        "module": True,
        "script": "console.log('Your JavaScript goes here...')",
    }

这将在你的页面末尾添加以下内容

<script type="module">console.log('Your JavaScript goes here...')</script>

示例:datasette-cluster-map

publish_subcommand(publish)

publish - Click 发布命令组

datasette publish 子命令的 Click 命令组

此钩子允许你为 datasette publish 命令创建新的提供程序。Datasette 内部使用此钩子来实现默认的 cloudrunheroku 子命令,因此你可以阅读它们的源代码,查看此钩子的实际示例。

假设你想构建一个插件,添加一个 datasette publish my_hosting_provider --api_key=xxx mydatabase.db 发布命令。你的实现将像这样开始

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,
    ): ...

示例:datasette-publish-fly, datasette-publish-vercel

render_cell(row, value, column, table, database, datasette)

允许你在 HTML 表格视图中自定义表格单元格内值的显示方式。

row - sqlite.Row

正在渲染的值所属的 SQLite 行对象

value - 字符串、整数、浮点数、字节或 None

从数据库加载的值

column - 字符串

正在渲染的列的名称

table - 字符串或 None

表的名称 - 如果这是自定义 SQL 查询,则为 None

database - 字符串

数据库的名称

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项,或执行 SQL 查询。

如果你的钩子返回 None,它将被忽略。用此指示你的钩子无法自定义渲染此特定值。

如果钩子返回一个字符串,该字符串将在表格单元格中渲染。

如果你想返回 HTML 标记,可以通过返回 jinja2.Markup 对象来做到。

你还可以返回一个返回值的可等待函数。

Datasette 将循环遍历所有可用的 render_cell 钩子,并显示第一个不返回 None 的钩子返回的值。

这里是一个自定义 render_cell() 插件的示例,它查找与以下格式匹配的 JSON 字符串值

{"href": "https://www.example.com/", "label": "Name"}

如果值与该模式匹配,则插件返回一个 HTML 链接元素

from datasette import hookimpl
import markupsafe
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 markupsafe.Markup(
        '<a href="{href}">{label}</a>'.format(
            href=markupsafe.escape(data["href"]),
            label=markupsafe.escape(data["label"] or "")
            or "&nbsp;",
        )
    )

示例:datasette-render-binary, datasette-render-markdown, datasette-json-html

register_output_renderer(datasette)

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项

注册一个新的输出渲染器,以自定义格式输出数据。钩子函数应返回一个字典或一个字典列表,其形状如下

@hookimpl
def register_output_renderer(datasette):
    return {
        "extension": "test",
        "render": render_demo,
        "can_render": can_render_demo,  # Optional
    }

这将注册 render_demo,以便在请求扩展名为 .test 的路径时调用(例如 /database.test/database/table.test/database/table/row.test)。

render_demo 是一个 Python 函数。它可以是普通函数或 async def render_demo() 可等待函数,取决于它是否需要进行任何异步调用。

can_render_demo 是一个 Python 函数(或 async def 函数),它接受与 render_demo 相同的参数,但只返回 TrueFalse。它让 Datasette 知道当前的 SQL 查询是否可以由插件表示 - 从而影响用户界面是否显示指向此输出格式的链接。如果你从字典中省略 "can_render" 键,则每个查询都将被视为受插件支持。

收到请求时,将使用以下零个或多个参数调用 "render" 回调函数。Datasette 将检查你的回调函数并传递与其函数签名匹配的参数。

datasette - Datasette 类

用于访问插件配置和执行查询。

columns - 字符串列表

此查询返回的列的名称。

rows - sqlite3.Row 对象列表

查询返回的行。

sql - 字符串

执行的 SQL 查询。

query_name - 字符串或 None

如果这是预设查询的执行,则为该查询的名称。

database - 字符串

数据库的名称。

table - 字符串或 None

正在渲染的表或视图(如果存在)。

request - 请求对象

当前的 HTTP 请求。

view_name - 字符串

正在调用的当前视图名称。indexdatabasetablerow 是最重要的几个。

如果回调函数无法渲染数据,可以返回 None,或者返回一个将返回给调用者的响应类

它还可以返回一个包含以下键的字典。此格式自 Datasette 0.49 起已弃用,并将在 Datasette 1.0 中移除。

body - 字符串或字节,可选

响应正文,默认为空

content_type - 字符串,可选

Content-Type 头部,默认为 text/plain

status_code - 整数,可选

HTTP 状态码,默认为 200

headers - 字典,可选

将在响应中返回的额外 HTTP 头部。

输出渲染器回调函数的示例

def render_demo():
    return Response.text("Hello World")

这里有一个更复杂的示例

async def render_demo(datasette, columns, rows):
    db = datasette.get_database()
    result = await db.execute("select sqlite_version()")
    first_row = " | ".join(columns)
    lines = [first_row]
    lines.append("=" * len(first_row))
    for row in rows:
        lines.append(" | ".join(row))
    return Response(
        "\n".join(lines),
        content_type="text/plain; charset=utf-8",
        headers={"x-sqlite-version": result.first()[0]},
    )

这里是一个 can_render 函数的示例,它仅在查询结果包含 atom_idatom_titleatom_updated 列时返回 True

def can_render_demo(columns):
    return {
        "atom_id",
        "atom_title",
        "atom_updated",
    }.issubset(columns)

示例:datasette-atom, datasette-ics, datasette-geojson, datasette-copyable

register_routes(datasette)

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项

注册额外的视图函数,以便为指定的 URL 路由执行。

返回一个 (regex, view_function) 对的列表,例如这样

from datasette import hookimpl, Response
import html


async def hello_from(request):
    name = request.url_vars["name"]
    return Response.html(
        "Hello from {}".format(html.escape(name))
    )


@hookimpl
def register_routes():
    return [(r"^/hello-from/(?P<name>.*)$", hello_from)]

视图函数可以接受多个不同的可选参数。根据其命名参数,会将相应的参数传递给你的函数 - 这是一种依赖注入形式。

可选视图函数参数如下

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项,或执行 SQL 查询。

request - 请求对象

当前的 HTTP 请求。

scope - 字典

传入的 ASGI scope 字典。

send - 函数

ASGI send 函数。

receive - 函数

ASGI receive 函数。

视图函数可以是普通函数或 async def 函数,取决于它是否需要使用任何 await API。

函数可以返回一个响应类,或者什么都不返回,而是直接使用 ASGI send 函数响应请求(仅适用于高级用法)。

它也可以引发 datasette.NotFound 异常以返回 404 未找到错误,或引发 datasette.Forbidden 异常以返回 403 禁止访问。

有关设计插件使用的 URL 路由的技巧,请参阅为插件设计 URL

示例:datasette-auth-github, datasette-psutil

register_commands(cli)

cli - 根 Datasette Click 命令组

使用此钩子注册额外的命令行命令

注册可以使用 datsette yourcommand ... 运行的额外命令行命令。这提供了一种机制,插件可以通过它向 Datasette 添加新的命令行命令。

此示例注册了一个新的 datasette verify file1.db file2.db 命令,用于检查提供的文件路径是否是有效的 SQLite 数据库

from datasette import hookimpl
import click
import sqlite3


@hookimpl
def register_commands(cli):
    @cli.command()
    @click.argument(
        "files", type=click.Path(exists=True), nargs=-1
    )
    def verify(files):
        "Verify that files can be opened by Datasette"
        for file in files:
            conn = sqlite3.connect(str(file))
            try:
                conn.execute("select * from sqlite_master")
            except sqlite3.DatabaseError:
                raise click.ClickException(
                    "Invalid database: {}".format(file)
                )

然后可以像这样执行新命令

datasette verify fixtures.db

帮助文本(来自函数的 docstring 以及任何定义的 Click 参数或选项)将通过以下方式可用

datasette verify --help

插件可以通过多次调用 @cli.command() 装饰器来注册多个命令。请查阅Click 文档,了解如何构建命令行命令的完整详细信息,包括如何定义参数和选项。

请注意,register_commands() 插件不能与--plugins-dir 机制一起使用 - 它们需要使用 pip install 安装到与 Datasette 相同的虚拟环境中。只要它有一个 setup.py 文件(参见打包插件),你就可以直接针对你正在开发插件的目录运行 pip install,像这样

pip install -e path/to/my/datasette-plugin

示例:datasette-auth-passwords, datasette-verify

register_facet_classes()

返回要注册的其他 Facet 子类列表。

警告

此插件钩子的设计不稳定,可能会发生变化。请参阅问题 830

每个 Facet 子类实现一种新的分面操作类型。该类应该看起来像这样

class SpecialFacet(Facet):
    # This key must be unique across all facet classes:
    type = "special"

    async def suggest(self):
        # Use self.sql and self.params to suggest some facets
        suggested_facets = []
        suggested_facets.append(
            {
                "name": column,  # Or other unique name
                # Construct the URL that will enable this facet:
                "toggle_url": self.ds.absolute_url(
                    self.request,
                    path_with_added_args(
                        self.request, {"_facet": column}
                    ),
                ),
            }
        )
        return suggested_facets

    async def facet_results(self):
        # This should execute the facet operation and return results, again
        # using self.sql and self.params as the starting point
        facet_results = []
        facets_timed_out = []
        facet_size = self.get_facet_size()
        # Do some calculations here...
        for column in columns_selected_for_facet:
            try:
                facet_results_values = []
                # More calculations...
                facet_results_values.append(
                    {
                        "value": value,
                        "label": label,
                        "count": count,
                        "toggle_url": self.ds.absolute_url(
                            self.request, toggle_path
                        ),
                        "selected": selected,
                    }
                )
                facet_results.append(
                    {
                        "name": column,
                        "results": facet_results_values,
                        "truncated": len(facet_rows_results)
                        > facet_size,
                    }
                )
            except QueryInterrupted:
                facets_timed_out.append(column)

        return facet_results, facets_timed_out

请参阅datasette/facets.py,了解这些类如何工作的示例。

然后可以使用插件钩子来注册新的分面类,像这样

@hookimpl
def register_facet_classes():
    return [SpecialFacet]

asgi_wrapper(datasette)

返回一个将应用于 Datasette ASGI 应用程序的 ASGI 中间件包装函数。

这是一个非常强大的钩子。你可以使用它来操作整个 Datasette 响应,甚至可以配置由你自己的自定义代码处理的新 URL 路由。

你可以直接根据低层规范编写 ASGI 代码,也可以使用 Starlette 等 ASGI 框架提供的中间件工具。

此示例插件添加了一个 x-databases HTTP 头部,列出当前附加的数据库

from datasette import hookimpl
from functools import wraps


@hookimpl
def asgi_wrapper(datasette):
    def wrap_with_databases_header(app):
        @wraps(app)
        async def add_x_databases_header(
            scope, receive, send
        ):
            async def wrapped_send(event):
                if event["type"] == "http.response.start":
                    original_headers = (
                        event.get("headers") or []
                    )
                    event = {
                        "type": event["type"],
                        "status": event["status"],
                        "headers": original_headers
                        + [
                            [
                                b"x-databases",
                                ", ".join(
                                    datasette.databases.keys()
                                ).encode("utf-8"),
                            ]
                        ],
                    }
                await send(event)

            await app(scope, receive, wrapped_send)

        return add_x_databases_header

    return wrap_with_databases_header

示例:datasette-cors, datasette-pyinstrument, datasette-total-page-time

startup(datasette)

当 Datasette 应用服务器首次启动时,此钩子会触发。你可以实现一个普通函数,例如用于验证所需的插件配置

@hookimpl
def startup(datasette):
    config = datasette.plugin_config("my-plugin") or {}
    assert (
        "required-setting" in config
    ), "my-plugin requires setting required-setting"

或者你可以返回一个异步函数,该函数将在启动时被等待。如果你需要进行任何数据库查询,请使用此选项

@hookimpl
def startup(datasette):
    async def inner():
        db = datasette.get_database()
        if "my_table" not in await db.table_names():
            await db.execute_write(
                """
                create table my_table (mycol text)
            """
            )

    return inner

潜在用例

  • 运行插件的一些初始化代码

  • 创建插件在启动时所需的数据库表

  • 在启动时验证插件的元数据配置,如果无效则引发错误

注意

如果你正在为使用此钩子且不通过发送任何模拟请求来运行 Datasette 的插件编写单元测试,你需要在测试中显式调用 await ds.invoke_startup()。示例

@pytest.mark.asyncio
async def test_my_plugin():
    ds = Datasette()
    await ds.invoke_startup()
    # Rest of test goes here

示例:datasette-saved-queries, datasette-init

canned_queries(datasette, database, actor)

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项,或执行 SQL 查询。

database - 字符串

数据库的名称。

actor - 字典或 None

当前已认证的actor

使用此钩子为指定的数据库返回一个额外的预设查询定义字典。返回值应与预设查询文档中描述的 JSON 具有相同的形状。

from datasette import hookimpl


@hookimpl
def canned_queries(datasette, database):
    if database == "mydb":
        return {
            "my_query": {
                "sql": "select * from my_table where id > :min_id"
            }
        }

该钩子也可以返回一个返回列表的可等待函数。这里有一个示例,它返回存储在 saved_queries 数据库表中的查询(如果存在)

from datasette import hookimpl


@hookimpl
def canned_queries(datasette, database):
    async def inner():
        db = datasette.get_database(database)
        if await db.table_exists("saved_queries"):
            results = await db.execute(
                "select name, sql from saved_queries"
            )
            return {
                result["name"]: {"sql": result["sql"]}
                for result in results
            }

    return inner

actor 参数可用于在你的决策中包含当前已认证的 actor。这里有一个示例,它返回由该 actor 保存的查询

from datasette import hookimpl


@hookimpl
def canned_queries(datasette, database, actor):
    async def inner():
        db = datasette.get_database(database)
        if actor is not None and await db.table_exists(
            "saved_queries"
        ):
            results = await db.execute(
                "select name, sql from saved_queries where actor_id = :id",
                {"id": actor["id"]},
            )
            return {
                result["name"]: {"sql": result["sql"]}
                for result in results
            }

    return inner

示例:datasette-saved-queries

actor_from_request(datasette, request)

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项,或执行 SQL 查询。

request - 请求对象

当前的 HTTP 请求。

这是 Datasette 的认证和权限系统的一部分。函数应尝试根据请求中的信息对 actor(无论是用户还是某种 API actor)进行认证。

如果无法认证 actor,它应该返回 None。否则,它应该返回一个表示该 actor 的字典。

这里有一个根据传入的 API 密钥认证 actor 的示例

from datasette import hookimpl
import secrets

SECRET_KEY = "this-is-a-secret"


@hookimpl
def actor_from_request(datasette, request):
    authorization = (
        request.headers.get("authorization") or ""
    )
    expected = "Bearer {}".format(SECRET_KEY)

    if secrets.compare_digest(authorization, expected):
        return {"id": "bot"}

如果你将此安装在你的插件目录中,可以像这样测试它

$ curl -H 'Authorization: Bearer this-is-a-secret' http://localhost:8003/-/actor.json

除了返回字典外,此函数还可以返回一个可等待函数,该函数本身返回 None 或字典。这对于需要进行数据库查询的认证函数非常有用 - 例如

from datasette import hookimpl


@hookimpl
def actor_from_request(datasette, request):
    async def inner():
        token = request.args.get("_token")
        if not token:
            return None
        # Look up ?_token=xxx in sessions table
        result = await datasette.get_database().execute(
            "select count(*) from sessions where token = ?",
            [token],
        )
        if result.first()[0]:
            return {"token": token}
        else:
            return None

    return inner

示例:datasette-auth-tokens

filters_from_request(request, database, table, datasette)

request - 请求对象

当前的 HTTP 请求。

database - 字符串

数据库的名称。

table - 字符串

表的名称。

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项,或执行 SQL 查询。

此钩子在页面运行,并可以根据传入请求的查询字符串参数影响用于填充该页面的 SQL 查询的 where 子句。

钩子应该返回 datasette.filters.FilterArguments 的实例,该实例有一个必需参数和三个可选参数

return FilterArguments(
    where_clauses=["id > :max_id"],
    params={"max_id": 5},
    human_descriptions=["max_id is greater than 5"],
    extra_context={},
)

FilterArguments 类构造函数的参数如下

where_clauses - 字符串列表,必需

一个 SQL 片段列表,将插入到 SQL 查询中,并由 and 运算符连接。这些片段可以包含 :named 参数,这些参数将使用 params 中的数据填充。

params - 字典,可选

执行查询时使用的额外关键字参数。这些参数应与 where 子句中的任何 :arguments 匹配。

human_descriptions - 字符串列表,可选

这些字符串将包含在页面顶部的易读描述和页面 <title> 中。

extra_context - 字典,可选

应在渲染 table.html 模板时提供给它的额外上下文变量。

如果 URL 中添加了 ?_nothing=1,此示例插件将导致返回 0 个结果

from datasette import hookimpl
from datasette.filters import FilterArguments


@hookimpl
def filters_from_request(self, request):
    if request.args.get("_nothing"):
        return FilterArguments(
            ["1 = 0"], human_descriptions=["NOTHING"]
        )

示例:datasette-leaflet-freedraw

permission_allowed(datasette, actor, action, resource)

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项,或执行 SQL 查询。

actor - 字典

当前 actor,由 actor_from_request(datasette, request) 决定。

action - 字符串

要执行的操作,例如 "edit-table"

resource - 字符串或 None

单个资源的标识符,例如表的名称。

调用此钩子以检查 actor 是否有权限对资源执行操作。如果允许该操作,则可以返回 True;如果操作不允许,则返回 False;如果插件对此没有意见,则返回 None

这里有一个示例插件,它随机选择是否应允许或拒绝权限,但 view-instance 除外,它始终使用默认权限方案。

from datasette import hookimpl
import random


@hookimpl
def permission_allowed(action):
    if action != "view-instance":
        # Return True or False at random
        return random.random() > 0.5
    # Returning None falls back to default permissions

此函数也可以返回一个可等待函数,该函数本身返回 TrueFalseNone。如果你需要使用 await datasette.execute(...) 执行额外的数据库查询,可以使用此选项。

这里有一个示例,它仅在用户的 actor id 存在于 admin_users 表中时才允许用户查看 admin_log 表。它也禁止所有用户对 staff.db 数据库执行任意 SQL 查询。

@hookimpl
def permission_allowed(datasette, actor, action, resource):
    async def inner():
        if action == "execute-sql" and resource == "staff":
            return False
        if action == "view-table" and resource == (
            "staff",
            "admin_log",
        ):
            if not actor:
                return False
            user_id = actor["id"]
            return await datasette.get_database(
                "staff"
            ).execute(
                "select count(*) from admin_users where user_id = :user_id",
                {"user_id": user_id},
            )

    return inner

有关 Datasette 核心中包含的完整权限列表,请参阅内置权限

示例:datasette-permissions-sql

register_magic_parameters(datasette)

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项。

魔法参数可用于向预设查询添加自动参数。此插件钩子允许插件定义额外的魔法参数。

魔法参数都采用以下格式:_prefix_rest_of_parameter。前缀指示应调用哪个魔法参数函数 - 参数的其余部分作为参数传递给该函数。

要注册一个新函数,从此钩子返回一个 (string prefix, function) 元组。你注册的函数应接受两个参数:keyrequest,其中 key 是参数的 rest_of_parameter 部分,而 request 是当前的请求对象

此示例注册了两个新的魔法参数::_request_http_version 返回当前请求的 HTTP 版本,:_uuid_new 返回一个新的 UUID

from uuid import uuid4


def uuid(key, request):
    if key == "new":
        return str(uuid4())
    else:
        raise KeyError


def request(key, request):
    if key == "http_version":
        return request.scope["http_version"]
    else:
        raise KeyError


@hookimpl
def register_magic_parameters(datasette):
    return [
        ("request", request),
        ("uuid", uuid),
    ]

forbidden(datasette, request, message)

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项,或渲染模板或执行 SQL 查询。

request - 请求对象

当前的 HTTP 请求。

message - 字符串

一条提示请求被禁止原因的消息。

插件可以使用此钩子来自定义 Datasette 在发生 403 Forbidden 错误时的响应方式 - 通常是因为页面未能通过权限检查,请参阅权限

如果插件钩子希望对错误做出反应,它应该返回一个响应对象

此示例返回重定向到 /-/login 页面

from datasette import hookimpl
from urllib.parse import urlencode


@hookimpl
def forbidden(request, message):
    return Response.redirect(
        "/-/login?=" + urlencode({"message": message})
    )

如果函数需要进行任何异步方法调用,它也可以返回一个可等待函数。此示例渲染一个模板

from datasette import hookimpl, Response


@hookimpl
def forbidden(datasette):
    async def inner():
        return Response.html(
            await datasette.render_template(
                "render_message.html", request=request
            )
        )

    return inner

handle_exception(datasette, request, exception)

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项,或渲染模板或执行 SQL 查询。

request - 请求对象

当前的 HTTP 请求。

exception - Exception

引发的异常。

每当引发意外异常时,都会调用此钩子。你可以使用它来记录异常。

如果你的处理程序返回一个 Response 对象,它将返回给客户端,替换默认的 Datasette 错误页面。

处理程序可以直接返回响应,或者返回一个返回响应的可等待函数。

此示例将错误记录到Sentry,然后渲染自定义错误页面

from datasette import hookimpl, Response
import sentry_sdk


@hookimpl
def handle_exception(datasette, exception):
    sentry_sdk.capture_exception(exception)

    async def inner():
        return Response.html(
            await datasette.render_template(
                "custom_error.html", request=request
            )
        )

    return inner

示例:datasette-sentry

table_actions(datasette, actor, database, table, request)

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项,或执行 SQL 查询。

actor - 字典或 None

当前已认证的actor

database - 字符串

数据库的名称。

table - 字符串

表的名称。

request - 请求对象 或 None

当前的 HTTP 请求。如果请求对象不可用,则可以为 None

此钩子允许将表操作显示在通过表页面顶部的操作图标访问的菜单中。它应该返回一个 {"href": "...", "label": "..."} 菜单项列表。

它也可以返回一个 async def 可等待函数,该函数返回一个菜单项列表。

此示例在登录用户是 "root" 时添加一个新的表操作

from datasette import hookimpl


@hookimpl
def table_actions(datasette, actor, database, table):
    if actor and actor.get("id") == "root":
        return [
            {
                "href": datasette.urls.path(
                    "/-/edit-schema/{}/{}".format(
                        database, table
                    )
                ),
                "label": "Edit schema for this table",
            }
        ]

示例:datasette-graphql

database_actions(datasette, actor, database, request)

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项,或执行 SQL 查询。

actor - 字典或 None

当前已认证的actor

database - 字符串

数据库的名称。

request - 请求对象

当前的 HTTP 请求。

此钩子类似于 table_actions(datasette, actor, database, table, request),但填充数据库页面上的操作菜单。

示例:datasette-graphql

skip_csrf(datasette, scope)

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项,或执行 SQL 查询。

scope - 字典

传入 HTTP 请求的ASGI scope

此钩子可用于跳过特定传入请求的CSRF 保护。例如,你可能有一个位于 /submit-comment 的自定义路径,该路径旨在接受来自任何地方的评论,无论传入请求是否源自站点并带有 CSRF token。

此示例将为此特定 URL 路径禁用 CSRF 保护

from datasette import hookimpl


@hookimpl
def skip_csrf(scope):
    return scope["path"] == "/submit-comment"

如果任何当前活跃的 skip_csrf() 插件钩子返回 True,则将跳过该请求的 CSRF 保护。

get_metadata(datasette, key, database, table)

datasette - Datasette 类

你可以使用它通过 datasette.plugin_config(your_plugin_name) 访问插件配置选项。

actor - 字典或 None

当前已认证的actor

database - 字符串或 None

正在请求元数据的数据库名称。

table - 字符串或 None

表的名称。

key - 字符串或 None

正在请求数据的键的名称。

此钩子负责返回对应于 Datasette 元数据的字典。此函数将接收传递给上游内部元数据请求的 databasetablekey。无论如何,返回一个全局元数据对象非常重要,其中 "databases": [] 将是一个顶级键。此处返回的字典将与物理 metadata.yaml 的内容合并,如果存在,则会被其覆盖。

警告

此插件钩子的设计目前未提供与异步代码交互的机制,未来可能会改变。请参阅问题 1384

@hookimpl
def get_metadata(datasette, key, database, table):
    metadata = {
        "title": "This will be the Datasette landing page title!",
        "description": get_instance_description(datasette),
        "databases": [],
    }
    for db_name, db_data_dict in get_my_database_meta(
        datasette, database, table, key
    ):
        metadata["databases"][db_name] = db_data_dict
    # whatever we return here will be merged with any other plugins using this hook and
    # will be overwritten by a local metadata.yaml if one exists!
    return metadata

示例:datasette-remote-metadata 插件