From 33c7c53ff87c25445c68088ede49d062d9c31fe8 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 15 Apr 2018 17:56:15 -0700 Subject: [PATCH] Start of the plugin system, based on pluggy (#210) Uses https://pluggy.readthedocs.io/ originally created for the py.test project We're starting with two plugin hooks: prepare_connection(conn) This is called when a new SQLite connection is created. It can be used to register custom SQL functions. prepare_jinja2_environment(env) This is called with the Jinja2 environment. It can be used to register custom template tags and filters. An example plugin which uses these two hooks can be found at https://github.com/simonw/datasette-plugin-demos or installed using `pip install datasette-plugin-demos` Refs #14 --- datasette/__init__.py | 2 ++ datasette/app.py | 9 +++++++++ datasette/hookspecs.py | 15 +++++++++++++++ setup.py | 5 +++-- 4 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 datasette/hookspecs.py diff --git a/datasette/__init__.py b/datasette/__init__.py index 668a8c82..1ec88d90 100644 --- a/datasette/__init__.py +++ b/datasette/__init__.py @@ -1 +1,3 @@ from datasette.version import __version_info__, __version__ # noqa +from .hookspecs import hookimpl # noqa +from .hookspecs import hookspec # noqa diff --git a/datasette/app.py b/datasette/app.py index 3cd39b2f..c20b8275 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -17,6 +17,7 @@ import jinja2 import hashlib import time import pint +import pluggy import traceback from .utils import ( Filters, @@ -38,6 +39,7 @@ from .utils import ( urlsafe_components, validate_sql_select, ) +from . import hookspecs from .version import __version__ app_root = Path(__file__).parent.parent @@ -49,6 +51,11 @@ connections = threading.local() ureg = pint.UnitRegistry() +pm = pluggy.PluginManager('datasette') +pm.add_hookspecs(hookspecs) +pm.load_setuptools_entrypoints('datasette') + + class DatasetteError(Exception): def __init__(self, message, title=None, error_dict=None, status=500, template=None): self.message = message @@ -1100,6 +1107,7 @@ class Datasette: conn.enable_load_extension(True) for extension in self.sqlite_extensions: conn.execute("SELECT load_extension('{}')".format(extension)) + pm.hook.prepare_connection(conn=conn) def inspect(self): if not self._inspect: @@ -1226,6 +1234,7 @@ class Datasette: self.jinja_env.filters['quote_plus'] = lambda u: urllib.parse.quote_plus(u) self.jinja_env.filters['escape_sqlite'] = escape_sqlite self.jinja_env.filters['to_css_class'] = to_css_class + pm.hook.prepare_jinja2_environment(env=self.jinja_env) app.add_route(IndexView.as_view(self), '/') # TODO: /favicon.ico and /-/static/ deserve far-future cache expires app.add_route(favicon, '/favicon.ico') diff --git a/datasette/hookspecs.py b/datasette/hookspecs.py new file mode 100644 index 00000000..0e65a198 --- /dev/null +++ b/datasette/hookspecs.py @@ -0,0 +1,15 @@ +from pluggy import HookimplMarker +from pluggy import HookspecMarker + +hookspec = HookspecMarker('datasette') +hookimpl = HookimplMarker('datasette') + + +@hookspec +def prepare_connection(conn): + "Modify SQLite connection in some way e.g. register custom SQL functions" + + +@hookspec +def prepare_jinja2_environment(env): + "Modify Jinja2 template environment e.g. register custom template tags" diff --git a/setup.py b/setup.py index 362484c4..2071ac27 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ from setuptools import setup, find_packages -from datasette import __version__ +from datasette.version import __version__ import os @@ -28,7 +28,8 @@ setup( 'Sanic==0.7.0', 'Jinja2==2.10', 'hupper==1.0', - 'pint==0.8.1' + 'pint==0.8.1', + 'pluggy>=0.1.0,<1.0', ], entry_points=''' [console_scripts]