{"id": "plugin_hooks:plugin-actions", "page": "plugin_hooks", "ref": "plugin-actions", "title": "Action hooks", "content": "Action hooks can be used to add items to the action menus that appear at the top of different pages within Datasette. Unlike menu_links() , actions which are displayed on every page, actions should only be relevant to the page the user is currently viewing. \n Each of these hooks should return return a list of {\"href\": \"...\", \"label\": \"...\"} menu items, with optional \"description\": \"...\" keys describing each action in more detail. \n They can alternatively return an async def awaitable function which, when called, returns a list of those menu items.", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[]"} {"id": "plugin_hooks:plugin-event-tracking", "page": "plugin_hooks", "ref": "plugin-event-tracking", "title": "Event tracking", "content": "Datasette includes an internal mechanism for tracking notable events. This can be used for analytics, but can also be used by plugins that want to listen out for when key events occur (such as a table being created) and take action in response. \n Plugins can register to receive events using the track_event plugin hook. \n They can also define their own events for other plugins to receive using the register_events() plugin hook , combined with calls to the datasette.track_event() internal method .", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[]"} {"id": "plugin_hooks:plugin-register-permissions", "page": "plugin_hooks", "ref": "plugin-register-permissions", "title": "register_permissions(datasette)", "content": "If your plugin needs to register additional permissions unique to that plugin - upload-csvs for example - you can return a list of those permissions from this hook. \n from datasette import hookimpl, Permission\n\n\n@hookimpl\ndef register_permissions(datasette):\n return [\n Permission(\n name=\"upload-csvs\",\n abbr=None,\n description=\"Upload CSV files\",\n takes_database=True,\n takes_resource=False,\n default=False,\n )\n ] \n The fields of the Permission class are as follows: \n \n \n name - string \n \n The name of the permission, e.g. upload-csvs . This should be unique across all plugins that the user might have installed, so choose carefully. \n \n \n \n abbr - string or None \n \n An abbreviation of the permission, e.g. uc . This is optional - you can set it to None if you do not want to pick an abbreviation. Since this needs to be unique across all installed plugins it's best not to specify an abbreviation at all. If an abbreviation is provided it will be used when creating restricted signed API tokens. \n \n \n \n description - string or None \n \n A human-readable description of what the permission lets you do. Should make sense as the second part of a sentence that starts \"A user with this permission can ...\". \n \n \n \n takes_database - boolean \n \n True if this permission can be granted on a per-database basis, False if it is only valid at the overall Datasette instance level. \n \n \n \n takes_resource - boolean \n \n True if this permission can be granted on a per-resource basis. A resource is a database table, SQL view or canned query . \n \n \n \n default - boolean \n \n The default value for this permission if it is not explicitly granted to a user. True means the permission is granted by default, False means it is not. \n This should only be True if you want anonymous users to be able to take this action.", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[]"} {"id": "plugin_hooks:plugin-hook-render-cell", "page": "plugin_hooks", "ref": "plugin-hook-render-cell", "title": "render_cell(row, value, column, table, database, datasette, request)", "content": "Lets you customize the display of values within table cells in the HTML table view. \n \n \n row - sqlite.Row \n \n The SQLite row object that the value being rendered is part of \n \n \n \n value - string, integer, float, bytes or None \n \n The value that was loaded from the database \n \n \n \n column - string \n \n The name of the column being rendered \n \n \n \n table - string or None \n \n The name of the table - or None if this is a custom SQL query \n \n \n \n database - string \n \n The name of the database \n \n \n \n datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to execute SQL queries. \n \n \n \n request - Request object \n \n The current request object \n \n \n \n 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. \n If the hook returns a string, that string will be rendered in the table cell. \n If you want to return HTML markup you can do so by returning a jinja2.Markup object. \n You can also return an awaitable function which returns a value. \n Datasette will loop through all available render_cell hooks and display the value returned by the first one that does not return None . \n Here is an example of a custom render_cell() plugin which looks for values that are a JSON string matching the following format: \n {\"href\": \"https://www.example.com/\", \"label\": \"Name\"} \n If the value matches that pattern, the plugin returns an HTML link element: \n from datasette import hookimpl\nimport markupsafe\nimport json\n\n\n@hookimpl\ndef render_cell(value):\n # Render {\"href\": \"...\", \"label\": \"...\"} as link\n if not isinstance(value, str):\n return None\n stripped = value.strip()\n if not (\n stripped.startswith(\"{\") and stripped.endswith(\"}\")\n ):\n return None\n try:\n data = json.loads(value)\n except ValueError:\n return None\n if not isinstance(data, dict):\n return None\n if set(data.keys()) != {\"href\", \"label\"}:\n return None\n href = data[\"href\"]\n if not (\n href.startswith(\"/\")\n or href.startswith(\"http://\")\n or href.startswith(\"https://\")\n ):\n return None\n return markupsafe.Markup(\n '{label}'.format(\n href=markupsafe.escape(data[\"href\"]),\n label=markupsafe.escape(data[\"label\"] or \"\")\n or \" \",\n )\n ) \n Examples: datasette-render-binary , datasette-render-markdown , datasette-json-html", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://datasette.io/plugins/datasette-render-binary\", \"label\": \"datasette-render-binary\"}, {\"href\": \"https://datasette.io/plugins/datasette-render-markdown\", \"label\": \"datasette-render-markdown\"}, {\"href\": \"https://datasette.io/plugins/datasette-json-html\", \"label\": \"datasette-json-html\"}]"} {"id": "plugin_hooks:plugin-register-facet-classes", "page": "plugin_hooks", "ref": "plugin-register-facet-classes", "title": "register_facet_classes()", "content": "Return a list of additional Facet subclasses to be registered. \n \n The design of this plugin hook is unstable and may change. See issue 830 . \n \n Each Facet subclass implements a new type of facet operation. The class should look like this: \n class SpecialFacet(Facet):\n # This key must be unique across all facet classes:\n type = \"special\"\n\n async def suggest(self):\n # Use self.sql and self.params to suggest some facets\n suggested_facets = []\n suggested_facets.append(\n {\n \"name\": column, # Or other unique name\n # Construct the URL that will enable this facet:\n \"toggle_url\": self.ds.absolute_url(\n self.request,\n path_with_added_args(\n self.request, {\"_facet\": column}\n ),\n ),\n }\n )\n return suggested_facets\n\n async def facet_results(self):\n # This should execute the facet operation and return results, again\n # using self.sql and self.params as the starting point\n facet_results = []\n facets_timed_out = []\n facet_size = self.get_facet_size()\n # Do some calculations here...\n for column in columns_selected_for_facet:\n try:\n facet_results_values = []\n # More calculations...\n facet_results_values.append(\n {\n \"value\": value,\n \"label\": label,\n \"count\": count,\n \"toggle_url\": self.ds.absolute_url(\n self.request, toggle_path\n ),\n \"selected\": selected,\n }\n )\n facet_results.append(\n {\n \"name\": column,\n \"results\": facet_results_values,\n \"truncated\": len(facet_rows_results)\n > facet_size,\n }\n )\n except QueryInterrupted:\n facets_timed_out.append(column)\n\n return facet_results, facets_timed_out \n See datasette/facets.py for examples of how these classes can work. \n The plugin hook can then be used to register the new facet class like this: \n @hookimpl\ndef register_facet_classes():\n return [SpecialFacet]", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/830\", \"label\": \"issue 830\"}, {\"href\": \"https://github.com/simonw/datasette/blob/main/datasette/facets.py\", \"label\": \"datasette/facets.py\"}]"} {"id": "plugin_hooks:plugin-asgi-wrapper", "page": "plugin_hooks", "ref": "plugin-asgi-wrapper", "title": "asgi_wrapper(datasette)", "content": "Return an ASGI middleware wrapper function that will be applied to the Datasette ASGI application. \n This is a very powerful hook. You can use it to manipulate the entire Datasette response, or even to configure new URL routes that will be handled by your own custom code. \n You can write your ASGI code directly against the low-level specification, or you can use the middleware utilities provided by an ASGI framework such as Starlette . \n This example plugin adds a x-databases HTTP header listing the currently attached databases: \n from datasette import hookimpl\nfrom functools import wraps\n\n\n@hookimpl\ndef asgi_wrapper(datasette):\n def wrap_with_databases_header(app):\n @wraps(app)\n async def add_x_databases_header(\n scope, receive, send\n ):\n async def wrapped_send(event):\n if event[\"type\"] == \"http.response.start\":\n original_headers = (\n event.get(\"headers\") or []\n )\n event = {\n \"type\": event[\"type\"],\n \"status\": event[\"status\"],\n \"headers\": original_headers\n + [\n [\n b\"x-databases\",\n \", \".join(\n datasette.databases.keys()\n ).encode(\"utf-8\"),\n ]\n ],\n }\n await send(event)\n\n await app(scope, receive, wrapped_send)\n\n return add_x_databases_header\n\n return wrap_with_databases_header \n Examples: datasette-cors , datasette-pyinstrument , datasette-total-page-time", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://asgi.readthedocs.io/\", \"label\": \"ASGI\"}, {\"href\": \"https://www.starlette.io/middleware/\", \"label\": \"Starlette\"}, {\"href\": \"https://datasette.io/plugins/datasette-cors\", \"label\": \"datasette-cors\"}, {\"href\": \"https://datasette.io/plugins/datasette-pyinstrument\", \"label\": \"datasette-pyinstrument\"}, {\"href\": \"https://datasette.io/plugins/datasette-total-page-time\", \"label\": \"datasette-total-page-time\"}]"} {"id": "plugin_hooks:plugin-hook-slots", "page": "plugin_hooks", "ref": "plugin-hook-slots", "title": "Template slots", "content": "The following set of plugin hooks can be used to return extra HTML content that will be inserted into the corresponding page, directly below the