mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
New "startup" plugin hook, closes #834
This commit is contained in:
parent
b906030235
commit
09a3479a54
8 changed files with 61 additions and 0 deletions
|
|
@ -302,6 +302,13 @@ class Datasette:
|
||||||
self._permission_checks = collections.deque(maxlen=200)
|
self._permission_checks = collections.deque(maxlen=200)
|
||||||
self._root_token = secrets.token_hex(32)
|
self._root_token = secrets.token_hex(32)
|
||||||
|
|
||||||
|
async def invoke_startup(self):
|
||||||
|
for hook in pm.hook.startup(datasette=self):
|
||||||
|
if callable(hook):
|
||||||
|
hook = hook()
|
||||||
|
if asyncio.iscoroutine(hook):
|
||||||
|
hook = await hook
|
||||||
|
|
||||||
def sign(self, value, namespace="default"):
|
def sign(self, value, namespace="default"):
|
||||||
return URLSafeSerializer(self._secret, namespace).dumps(value)
|
return URLSafeSerializer(self._secret, namespace).dumps(value)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -397,6 +397,9 @@ def serve(
|
||||||
# Private utility mechanism for writing unit tests
|
# Private utility mechanism for writing unit tests
|
||||||
return ds
|
return ds
|
||||||
|
|
||||||
|
# Run the "startup" plugin hooks
|
||||||
|
asyncio.get_event_loop().run_until_complete(ds.invoke_startup())
|
||||||
|
|
||||||
# Run async sanity checks - but only if we're not under pytest
|
# Run async sanity checks - but only if we're not under pytest
|
||||||
asyncio.get_event_loop().run_until_complete(check_databases(ds))
|
asyncio.get_event_loop().run_until_complete(check_databases(ds))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@ hookspec = HookspecMarker("datasette")
|
||||||
hookimpl = HookimplMarker("datasette")
|
hookimpl = HookimplMarker("datasette")
|
||||||
|
|
||||||
|
|
||||||
|
@hookspec
|
||||||
|
def startup(datasette):
|
||||||
|
"Fires directly after Datasette first starts running"
|
||||||
|
|
||||||
|
|
||||||
@hookspec
|
@hookspec
|
||||||
def asgi_wrapper(datasette):
|
def asgi_wrapper(datasette):
|
||||||
"Returns an ASGI middleware callable to wrap our ASGI application with"
|
"Returns an ASGI middleware callable to wrap our ASGI application with"
|
||||||
|
|
|
||||||
|
|
@ -995,6 +995,39 @@ This example plugin adds a ``x-databases`` HTTP header listing the currently att
|
||||||
|
|
||||||
Examples: `datasette-auth-github <https://github.com/simonw/datasette-auth-github>`_, `datasette-search-all <https://github.com/simonw/datasette-search-all>`_, `datasette-media <https://github.com/simonw/datasette-media>`_
|
Examples: `datasette-auth-github <https://github.com/simonw/datasette-auth-github>`_, `datasette-search-all <https://github.com/simonw/datasette-search-all>`_, `datasette-media <https://github.com/simonw/datasette-media>`_
|
||||||
|
|
||||||
|
.. _plugin_hook_startup:
|
||||||
|
|
||||||
|
startup(datasette)
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This hook fires when the Datasette application server first starts up. You can implement a regular function, for example to validate required plugin configuration:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def startup(datasette):
|
||||||
|
config = datasette.plugin_config("my-plugin") or {}
|
||||||
|
assert "required-setting" in config, "my-plugin requires setting required-setting"
|
||||||
|
|
||||||
|
Or you can return an async function which will be awaited on startup. Use this option if you need to make any database queries:
|
||||||
|
|
||||||
|
@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)
|
||||||
|
""", block=True)
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
|
Potential use-cases:
|
||||||
|
|
||||||
|
* Run some initialization code for the plugin
|
||||||
|
* Create database tables that a plugin needs
|
||||||
|
* Validate the metadata configuration for a plugin on startup, and raise an error if it is invalid
|
||||||
|
|
||||||
.. _plugin_hook_actor_from_request:
|
.. _plugin_hook_actor_from_request:
|
||||||
|
|
||||||
actor_from_request(datasette, request)
|
actor_from_request(datasette, request)
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ EXPECTED_PLUGINS = [
|
||||||
"register_facet_classes",
|
"register_facet_classes",
|
||||||
"register_routes",
|
"register_routes",
|
||||||
"render_cell",
|
"render_cell",
|
||||||
|
"startup",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -167,3 +167,8 @@ def register_routes():
|
||||||
(r"/two/(?P<name>.*)$", two),
|
(r"/two/(?P<name>.*)$", two),
|
||||||
(r"/three/$", three),
|
(r"/three/$", three),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def startup(datasette):
|
||||||
|
datasette._startup_hook_fired = True
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from click.testing import CliRunner
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import pytest
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -572,3 +572,9 @@ def test_register_routes_asgi(app_client):
|
||||||
response = app_client.get("/three/")
|
response = app_client.get("/three/")
|
||||||
assert {"hello": "world"} == response.json
|
assert {"hello": "world"} == response.json
|
||||||
assert "1" == response.headers["x-three"]
|
assert "1" == response.headers["x-three"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_startup(app_client):
|
||||||
|
await app_client.ds.invoke_startup()
|
||||||
|
assert app_client.ds._startup_hook_fired
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue