diff --git a/datasette/app.py b/datasette/app.py index 51953ac0..d9b61c51 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -170,7 +170,7 @@ class Datasette: def metadata(self, key=None, database=None, table=None, fallback=True): """ Looks up metadata, cascading backwards from specified level. - Returns None if metadata value is not foundself. + Returns None if metadata value is not found. """ assert not (database is None and table is not None), \ "Cannot call metadata() with table= specified but not database=" @@ -199,6 +199,17 @@ class Datasette: m.update(item) return m + def plugin_config( + self, plugin_name, database=None, table=None, fallback=True + ): + "Return config for plugin, falling back from specified database/table" + plugins = self.metadata( + "plugins", database=database, table=table, fallback=fallback + ) + if plugins is None: + return None + return plugins.get(plugin_name) + def app_css_hash(self): if not hasattr(self, "_app_css_hash"): self._app_css_hash = hashlib.sha1( diff --git a/datasette/hookspecs.py b/datasette/hookspecs.py index 233a9aa0..8523a135 100644 --- a/datasette/hookspecs.py +++ b/datasette/hookspecs.py @@ -1,8 +1,8 @@ from pluggy import HookimplMarker from pluggy import HookspecMarker -hookspec = HookspecMarker('datasette') -hookimpl = HookimplMarker('datasette') +hookspec = HookspecMarker("datasette") +hookimpl = HookimplMarker("datasette") @hookspec diff --git a/docs/plugins.rst b/docs/plugins.rst index f69fed95..c95329d6 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -149,6 +149,71 @@ The priority order for template loading is: 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. +Plugin configuration +-------------------- + +Plugins can have their own configuration, embedded in a :ref:`metadata` file. Configuration options for plugins live within a ``"plugins"`` key in that file, which can be included at the root, database or table level. + +Here is an example of some plugin configuration for a specific table:: + + { + "databases: { + "sf-trees": { + "tables": { + "Street_Tree_List": { + "plugins": { + "datasette-cluster-map": { + "latitude_column": "lat", + "longitude_column": "lng" + } + } + } + } + } + } + } + +This tells the ``datasette-cluster-map`` column which latitude and longitude columns should be used for a table called ``Street_Tree_List`` inside a database file called ``sf-trees.db``. + +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. + Plugin hooks ------------ diff --git a/tests/fixtures.py b/tests/fixtures.py index f1b8bca4..1683f5c9 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -129,9 +129,19 @@ METADATA = { 'license_url': 'https://github.com/simonw/datasette/blob/master/LICENSE', 'source': 'tests/fixtures.py', 'source_url': 'https://github.com/simonw/datasette/blob/master/tests/fixtures.py', + "plugins": { + "name-of-plugin": { + "depth": "root" + } + }, 'databases': { 'fixtures': { 'description': 'Test tables description', + "plugins": { + "name-of-plugin": { + "depth": "database" + } + }, 'tables': { 'simple_primary_key': { 'description_html': 'Simple primary key', @@ -143,7 +153,12 @@ METADATA = { 'sortable_with_nulls', 'sortable_with_nulls_2', 'text', - ] + ], + "plugins": { + "name-of-plugin": { + "depth": "table" + } + } }, 'no_primary_key': { 'sortable_columns': [], diff --git a/tests/test_docs.py b/tests/test_docs.py index ae96659a..6f84832d 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -14,8 +14,8 @@ label_re = re.compile(r'\.\. _([^\s:]+):') def get_headings(filename, underline="-"): content = (docs_path / filename).open().read() - heading_re = re.compile(r'(\S+)\n\{}+\n'.format(underline)) - return set(heading_re.findall(content)) + heading_re = re.compile(r'(\w+)(\([^)]*\))?\n\{}+\n'.format(underline)) + return set(h[0] for h in heading_re.findall(content)) def get_labels(filename): diff --git a/tests/test_plugins.py b/tests/test_plugins.py index fee27c24..3c91161b 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -85,3 +85,20 @@ def test_plugins_render_cell(app_client): assert a is not None, str(a) assert a.attrs["href"] == "http://example.com/" assert a.text == "Example" + + +def test_plugin_config(app_client): + assert {"depth": "table"} == app_client.ds.plugin_config( + "name-of-plugin", database="fixtures", table="sortable" + ) + assert {"depth": "database"} == app_client.ds.plugin_config( + "name-of-plugin", database="fixtures", table="unknown_table" + ) + assert {"depth": "database"} == app_client.ds.plugin_config( + "name-of-plugin", database="fixtures" + ) + assert {"depth": "root"} == app_client.ds.plugin_config( + "name-of-plugin", database="unknown_database" + ) + assert {"depth": "root"} == app_client.ds.plugin_config("name-of-plugin") + assert None is app_client.ds.plugin_config("unknown-plugin")