{"id": "writing_plugins:writing-plugins-static-assets", "page": "writing_plugins", "ref": "writing-plugins-static-assets", "title": "Static assets", "content": "If your plugin has a static/ directory, Datasette will automatically configure itself to serve those static assets from the following path: \n /-/static-plugins/NAME_OF_PLUGIN_PACKAGE/yourfile.js \n Use the datasette.urls.static_plugins(plugin_name, path) method to generate URLs to that asset that take the base_url setting into account, see datasette.urls . \n To bundle the static assets for a plugin in the package that you publish to PyPI, add the following to the plugin's setup.py : \n package_data = (\n {\n \"datasette_plugin_name\": [\n \"static/plugin.js\",\n ],\n },\n) \n Where datasette_plugin_name is the name of the plugin package (note that it uses underscores, not hyphens) and static/plugin.js is the path within that package to the static file. \n datasette-cluster-map is a useful example of a plugin that includes packaged static assets in this way.", "breadcrumbs": "[\"Writing plugins\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette-cluster-map\", \"label\": \"datasette-cluster-map\"}]"} {"id": "writing_plugins:writing-plugins-packaging", "page": "writing_plugins", "ref": "writing-plugins-packaging", "title": "Packaging a plugin", "content": "Plugins can be packaged using Python setuptools. You can see an example of a packaged plugin at https://github.com/simonw/datasette-plugin-demos \n The example consists of two files: a setup.py file that defines the plugin: \n from setuptools import setup\n\nVERSION = \"0.1\"\n\nsetup(\n name=\"datasette-plugin-demos\",\n description=\"Examples of plugins for Datasette\",\n author=\"Simon Willison\",\n url=\"https://github.com/simonw/datasette-plugin-demos\",\n license=\"Apache License, Version 2.0\",\n version=VERSION,\n py_modules=[\"datasette_plugin_demos\"],\n entry_points={\n \"datasette\": [\n \"plugin_demos = datasette_plugin_demos\"\n ]\n },\n install_requires=[\"datasette\"],\n) \n And a Python module file, datasette_plugin_demos.py , that implements the plugin: \n from datasette import hookimpl\nimport random\n\n\n@hookimpl\ndef prepare_jinja2_environment(env):\n env.filters[\"uppercase\"] = lambda u: u.upper()\n\n\n@hookimpl\ndef prepare_connection(conn):\n conn.create_function(\n \"random_integer\", 2, random.randint\n ) \n Having built a plugin in this way you can turn it into an installable package using the following command: \n python3 setup.py sdist \n This will create a .tar.gz file in the dist/ directory. \n You can then install your new plugin into a Datasette virtual environment or Docker container using pip : \n pip install datasette-plugin-demos-0.1.tar.gz \n To learn how to upload your plugin to PyPI for use by other people, read the PyPA guide to Packaging and distributing projects .", "breadcrumbs": "[\"Writing plugins\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette-plugin-demos\", \"label\": \"https://github.com/simonw/datasette-plugin-demos\"}, {\"href\": \"https://pypi.org/\", \"label\": \"PyPI\"}, {\"href\": \"https://packaging.python.org/tutorials/distributing-packages/\", \"label\": \"Packaging and distributing projects\"}]"} {"id": "writing_plugins:writing-plugins-one-off", "page": "writing_plugins", "ref": "writing-plugins-one-off", "title": "Writing one-off plugins", "content": "The quickest way to start writing a plugin is to create a my_plugin.py file and drop it into your plugins/ directory. Here is an example plugin, which adds a new custom SQL function called hello_world() which takes no arguments and returns the string Hello world! . \n from datasette import hookimpl\n\n\n@hookimpl\ndef prepare_connection(conn):\n conn.create_function(\n \"hello_world\", 0, lambda: \"Hello world!\"\n ) \n If you save this in plugins/my_plugin.py you can then start Datasette like this: \n datasette serve mydb.db --plugins-dir=plugins/ \n Now you can navigate to http://localhost:8001/mydb and run this SQL: \n select hello_world(); \n To see the output of your plugin.", "breadcrumbs": "[\"Writing plugins\"]", "references": "[{\"href\": \"http://localhost:8001/mydb\", \"label\": \"http://localhost:8001/mydb\"}]"} {"id": "writing_plugins:writing-plugins-designing-urls", "page": "writing_plugins", "ref": "writing-plugins-designing-urls", "title": "Designing URLs for your plugin", "content": "You can register new URL routes within Datasette using the register_routes(datasette) plugin hook. \n Datasette's default URLs include these: \n \n \n /dbname - database page \n \n \n /dbname/tablename - table page \n \n \n /dbname/tablename/pk - row page \n \n \n See Pages and API endpoints and Introspection for more default URL routes. \n To avoid accidentally conflicting with a database file that may be loaded into Datasette, plugins should register URLs using a /-/ prefix. For example, if your plugin adds a new interface for uploading Excel files you might register a URL route like this one: \n \n \n /-/upload-excel \n \n \n Try to avoid registering URLs that clash with other plugins that your users might have installed. There is no central repository of reserved URL paths (yet) but you can review existing plugins by browsing the plugins directory . \n If your plugin includes functionality that relates to a specific database you could also register a URL route like this: \n \n \n /dbname/-/upload-excel \n \n \n Or for a specific table like this: \n \n \n /dbname/tablename/-/modify-table-schema \n \n \n Note that a row could have a primary key of - and this URL scheme will still work, because Datasette row pages do not ever have a trailing slash followed by additional path components.", "breadcrumbs": "[\"Writing plugins\"]", "references": "[]"} {"id": "writing_plugins:writing-plugins-custom-templates", "page": "writing_plugins", "ref": "writing-plugins-custom-templates", "title": "Custom templates", "content": "If your plugin has a templates/ directory, Datasette will attempt to load templates from that directory before it uses its own default templates. \n The priority order for template loading is: \n \n \n templates from the --template-dir argument, if specified \n \n \n templates from the templates/ directory in any installed plugins \n \n \n default templates that ship with Datasette \n \n \n See Custom pages and templates for more details on how to write custom templates, including which filenames to use to customize which parts of the Datasette UI. \n Templates should be bundled for distribution using the same package_data mechanism in setup.py described for static assets above, for example: \n package_data = (\n {\n \"datasette_plugin_name\": [\n \"templates/my_template.html\",\n ],\n },\n) \n You can also use wildcards here such as templates/*.html . See datasette-edit-schema for an example of this pattern.", "breadcrumbs": "[\"Writing plugins\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette-edit-schema\", \"label\": \"datasette-edit-schema\"}]"} {"id": "writing_plugins:writing-plugins-cookiecutter", "page": "writing_plugins", "ref": "writing-plugins-cookiecutter", "title": "Starting an installable plugin using cookiecutter", "content": "Plugins that can be installed should be written as Python packages using a setup.py file. \n The quickest way to start writing one an installable plugin is to use the datasette-plugin cookiecutter template. This creates a new plugin structure for you complete with an example test and GitHub Actions workflows for testing and publishing your plugin. \n Install cookiecutter and then run this command to start building a plugin using the template: \n cookiecutter gh:simonw/datasette-plugin \n Read a cookiecutter template for writing Datasette plugins for more information about this template.", "breadcrumbs": "[\"Writing plugins\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette-plugin\", \"label\": \"datasette-plugin\"}, {\"href\": \"https://cookiecutter.readthedocs.io/en/stable/installation.html\", \"label\": \"Install cookiecutter\"}, {\"href\": \"https://simonwillison.net/2020/Jun/20/cookiecutter-plugins/\", \"label\": \"a cookiecutter template for writing Datasette plugins\"}]"} {"id": "writing_plugins:writing-plugins-configuration", "page": "writing_plugins", "ref": "writing-plugins-configuration", "title": "Writing plugins that accept configuration", "content": "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: \n plugin_config = datasette.plugin_config(\n \"datasette-cluster-map\", database=\"sf-trees\", table=\"Street_Tree_List\"\n) \n This will return the {\"latitude_column\": \"lat\", \"longitude_column\": \"lng\"} in the above example. \n If there is no configuration for that plugin, the method will return None . \n 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: \n {\n \"databases: {\n \"sf-trees\": {\n \"plugins\": {\n \"datasette-cluster-map\": {\n \"latitude_column\": \"xlat\",\n \"longitude_column\": \"xlng\"\n }\n }\n }\n }\n} \n In this case, the above code would return that configuration for ANY table within the sf-trees database. \n The plugin configuration could also be set at the top level of metadata.json : \n {\n \"title\": \"This is the top-level title in metadata.json\",\n \"plugins\": {\n \"datasette-cluster-map\": {\n \"latitude_column\": \"xlat\",\n \"longitude_column\": \"xlng\"\n }\n }\n} \n Now that datasette-cluster-map plugin configuration will apply to every table in every database.", "breadcrumbs": "[\"Writing plugins\"]", "references": "[]"} {"id": "writing_plugins:writing-plugins-building-urls", "page": "writing_plugins", "ref": "writing-plugins-building-urls", "title": "Building URLs within plugins", "content": "Plugins that define their own custom user interface elements may need to link to other pages within Datasette. \n This can be a bit tricky if the Datasette instance is using the base_url configuration setting to run behind a proxy, since that can cause Datasette's URLs to include an additional prefix. \n The datasette.urls object provides internal methods for correctly generating URLs to different pages within Datasette, taking any base_url configuration into account. \n This object is exposed in templates as the urls variable, which can be used like this: \n Back to the Homepage \n See datasette.urls for full details on this object.", "breadcrumbs": "[\"Writing plugins\"]", "references": "[]"} {"id": "changelog:writable-canned-queries", "page": "changelog", "ref": "writable-canned-queries", "title": "Writable canned queries", "content": "Datasette's Canned queries feature lets you define SQL queries in metadata.json which can then be executed by users visiting a specific URL. https://latest.datasette.io/fixtures/neighborhood_search for example. \n Canned queries were previously restricted to SELECT , but Datasette 0.44 introduces the ability for canned queries to execute INSERT or UPDATE queries as well, using the new \"write\": true property ( #800 ): \n {\n \"databases\": {\n \"dogs\": {\n \"queries\": {\n \"add_name\": {\n \"sql\": \"INSERT INTO names (name) VALUES (:name)\",\n \"write\": true\n }\n }\n }\n }\n} \n See Writable canned queries for more details.", "breadcrumbs": "[\"Changelog\", \"0.44 (2020-06-11)\"]", "references": "[{\"href\": \"https://latest.datasette.io/fixtures/neighborhood_search\", \"label\": \"https://latest.datasette.io/fixtures/neighborhood_search\"}, {\"href\": \"https://github.com/simonw/datasette/issues/800\", \"label\": \"#800\"}]"} {"id": "changelog:v0-29-medium-changes", "page": "changelog", "ref": "v0-29-medium-changes", "title": "Easier custom templates for table rows", "content": "If you want to customize the display of individual table rows, you can do so using a _table.html template include that looks something like this: \n {% for row in display_rows %}\n
\n

{{ row[\"title\"] }}

\n

{{ row[\"description\"] }}\n

Category: {{ row.display(\"category_id\") }}

\n
\n{% endfor %} \n This is a backwards incompatible change . If you previously had a custom template called _rows_and_columns.html you need to rename it to _table.html . \n See Custom templates for full details.", "breadcrumbs": "[\"Changelog\", \"0.29 (2019-07-07)\"]", "references": "[]"} {"id": "changelog:v0-28-register-output-renderer", "page": "changelog", "ref": "v0-28-register-output-renderer", "title": "register_output_renderer plugins", "content": "Russ Garrett implemented a new Datasette plugin hook called register_output_renderer ( #441 ) which allows plugins to create additional output renderers in addition to Datasette's default .json and .csv . \n Russ's in-development datasette-geo plugin includes an example of this hook being used to output .geojson automatically converted from SpatiaLite.", "breadcrumbs": "[\"Changelog\", \"0.28 (2019-05-19)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/pull/441\", \"label\": \"#441\"}, {\"href\": \"https://github.com/russss/datasette-geo\", \"label\": \"datasette-geo\"}, {\"href\": \"https://github.com/russss/datasette-geo/blob/d4cecc020848bbde91e9e17bf352f7c70bc3dccf/datasette_plugin_geo/geojson.py\", \"label\": \"an example\"}]"} {"id": "changelog:v0-28-publish-cloudrun", "page": "changelog", "ref": "v0-28-publish-cloudrun", "title": "datasette publish cloudrun", "content": "Google Cloud Run is a brand new serverless hosting platform from Google, which allows you to build a Docker container which will run only when HTTP traffic is received and will shut down (and hence cost you nothing) the rest of the time. It's similar to Zeit's Now v1 Docker hosting platform which sadly is no longer accepting signups from new users. \n The new datasette publish cloudrun command was contributed by Romain Primet ( #434 ) and publishes selected databases to a new Datasette instance running on Google Cloud Run. \n See Publishing to Google Cloud Run for full documentation.", "breadcrumbs": "[\"Changelog\", \"0.28 (2019-05-19)\"]", "references": "[{\"href\": \"https://cloud.google.com/run/\", \"label\": \"Google Cloud Run\"}, {\"href\": \"https://hyperion.alpha.spectrum.chat/zeit/now/cannot-create-now-v1-deployments~d206a0d4-5835-4af5-bb5c-a17f0171fb25?m=MTU0Njk2NzgwODM3OA==\", \"label\": \"no longer accepting signups\"}, {\"href\": \"https://github.com/simonw/datasette/pull/434\", \"label\": \"#434\"}]"} {"id": "changelog:v0-28-medium-changes", "page": "changelog", "ref": "v0-28-medium-changes", "title": "Medium changes", "content": "Datasette now conforms to the Black coding style ( #449 ) - and has a unit test to enforce this in the future \n \n \n \n \n New Special table arguments : \n \n \n \n ?columnname__in=value1,value2,value3 filter for executing SQL IN queries against a table, see Table arguments ( #433 ) \n \n \n ?columnname__date=yyyy-mm-dd filter which returns rows where the spoecified datetime column falls on the specified date ( 583b22a ) \n \n \n ?tags__arraycontains=tag filter which acts against a JSON array contained in a column ( 78e45ea ) \n \n \n ?_where=sql-fragment filter for the table view ( #429 ) \n \n \n ?_fts_table=mytable and ?_fts_pk=mycolumn query string options can be used to specify which FTS table to use for a search query - see Configuring full-text search for a table or view ( #428 ) \n \n \n \n \n \n \n \n You can now pass the same table filter multiple times - for example, ?content__not=world&content__not=hello will return all rows where the content column is neither hello or world ( #288 ) \n \n \n You can now specify about and about_url metadata (in addition to source and license ) linking to further information about a project - see Source, license and about \n \n \n New ?_trace=1 parameter now adds debug information showing every SQL query that was executed while constructing the page ( #435 ) \n \n \n datasette inspect now just calculates table counts, and does not introspect other database metadata ( #462 ) \n \n \n Removed /-/inspect page entirely - this will be replaced by something similar in the future, see #465 \n \n \n Datasette can now run against an in-memory SQLite database. You can do this by starting it without passing any files or by using the new --memory option to datasette serve . This can be useful for experimenting with SQLite queries that do not access any data, such as SELECT 1+1 or SELECT sqlite_version() .", "breadcrumbs": "[\"Changelog\", \"0.28 (2019-05-19)\"]", "references": "[{\"href\": \"https://github.com/python/black\", \"label\": \"Black coding style\"}, {\"href\": \"https://github.com/simonw/datasette/pull/449\", \"label\": \"#449\"}, {\"href\": \"https://github.com/simonw/datasette/issues/433\", \"label\": \"#433\"}, {\"href\": \"https://github.com/simonw/datasette/commit/583b22aa28e26c318de0189312350ab2688c90b1\", \"label\": \"583b22a\"}, {\"href\": \"https://github.com/simonw/datasette/commit/78e45ead4d771007c57b307edf8fc920101f8733\", \"label\": \"78e45ea\"}, {\"href\": \"https://github.com/simonw/datasette/issues/429\", \"label\": \"#429\"}, {\"href\": \"https://github.com/simonw/datasette/issues/428\", \"label\": \"#428\"}, {\"href\": \"https://github.com/simonw/datasette/issues/288\", \"label\": \"#288\"}, {\"href\": \"https://github.com/simonw/datasette/issues/435\", \"label\": \"#435\"}, {\"href\": \"https://github.com/simonw/datasette/issues/462\", \"label\": \"#462\"}, {\"href\": \"https://github.com/simonw/datasette/issues/465\", \"label\": \"#465\"}]"} {"id": "changelog:v0-28-faceting", "page": "changelog", "ref": "v0-28-faceting", "title": "Faceting improvements, and faceting plugins", "content": "Datasette Facets provide an intuitive way to quickly summarize and interact with data. Previously the only supported faceting technique was column faceting, but 0.28 introduces two powerful new capabilities: facet-by-JSON-array and the ability to define further facet types using plugins. \n Facet by array ( #359 ) is only available if your SQLite installation provides the json1 extension. Datasette will automatically detect columns that contain JSON arrays of values and offer a faceting interface against those columns - useful for modelling things like tags without needing to break them out into a new table. See Facet by JSON array for more. \n The new register_facet_classes() plugin hook ( #445 ) can be used to register additional custom facet classes. Each facet class should provide two methods: suggest() which suggests facet selections that might be appropriate for a provided SQL query, and facet_results() which executes a facet operation and returns results. Datasette's own faceting implementations have been refactored to use the same API as these plugins.", "breadcrumbs": "[\"Changelog\", \"0.28 (2019-05-19)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/359\", \"label\": \"#359\"}, {\"href\": \"https://github.com/simonw/datasette/pull/445\", \"label\": \"#445\"}]"} {"id": "changelog:v0-28-databases-that-change", "page": "changelog", "ref": "v0-28-databases-that-change", "title": "Supporting databases that change", "content": "From the beginning of the project, Datasette has been designed with read-only databases in mind. If a database is guaranteed not to change it opens up all kinds of interesting opportunities - from taking advantage of SQLite immutable mode and HTTP caching to bundling static copies of the database directly in a Docker container. The interesting ideas in Datasette explores this idea in detail. \n As my goals for the project have developed, I realized that read-only databases are no longer the right default. SQLite actually supports concurrent access very well provided only one thread attempts to write to a database at a time, and I keep encountering sensible use-cases for running Datasette on top of a database that is processing inserts and updates. \n So, as-of version 0.28 Datasette no longer assumes that a database file will not change. It is now safe to point Datasette at a SQLite database which is being updated by another process. \n Making this change was a lot of work - see tracking tickets #418 , #419 and #420 . It required new thinking around how Datasette should calculate table counts (an expensive operation against a large, changing database) and also meant reconsidering the \"content hash\" URLs Datasette has used in the past to optimize the performance of HTTP caches. \n Datasette can still run against immutable files and gains numerous performance benefits from doing so, but this is no longer the default behaviour. Take a look at the new Performance and caching documentation section for details on how to make the most of Datasette against data that you know will be staying read-only and immutable.", "breadcrumbs": "[\"Changelog\", \"0.28 (2019-05-19)\"]", "references": "[{\"href\": \"https://simonwillison.net/2018/Oct/4/datasette-ideas/\", \"label\": \"The interesting ideas in Datasette\"}, {\"href\": \"https://github.com/simonw/datasette/issues/418\", \"label\": \"#418\"}, {\"href\": \"https://github.com/simonw/datasette/issues/419\", \"label\": \"#419\"}, {\"href\": \"https://github.com/simonw/datasette/issues/420\", \"label\": \"#420\"}]"} {"id": "settings:using-setting", "page": "settings", "ref": "using-setting", "title": "Using --setting", "content": "Datasette supports a number of settings. These can be set using the --setting name value option to datasette serve . \n You can set multiple settings at once like this: \n datasette mydatabase.db \\\n --setting default_page_size 50 \\\n --setting sql_time_limit_ms 3500 \\\n --setting max_returned_rows 2000", "breadcrumbs": "[\"Settings\"]", "references": "[]"} {"id": "changelog:url-building", "page": "changelog", "ref": "url-building", "title": "URL building", "content": "The new datasette.urls family of methods can be used to generate URLs to key pages within the Datasette interface, both within custom templates and Datasette plugins. See Building URLs within plugins for more details. ( #904 )", "breadcrumbs": "[\"Changelog\", \"0.51 (2020-10-31)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/904\", \"label\": \"#904\"}]"} {"id": "installation:upgrading-packages-using-pipx", "page": "installation", "ref": "upgrading-packages-using-pipx", "title": "Upgrading packages using pipx", "content": "You can upgrade your pipx installation to the latest release of Datasette using pipx upgrade datasette : \n $ pipx upgrade datasette\nupgraded package datasette from 0.39 to 0.40 (location: /Users/simon/.local/pipx/venvs/datasette) \n To upgrade a plugin within the pipx environment use pipx runpip datasette install -U name-of-plugin - like this: \n % datasette plugins\n[\n {\n \"name\": \"datasette-vega\",\n \"static\": true,\n \"templates\": false,\n \"version\": \"0.6\"\n }\n]\n\n$ pipx runpip datasette install -U datasette-vega\nCollecting datasette-vega\nDownloading datasette_vega-0.6.2-py3-none-any.whl (1.8 MB)\n |\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1.8 MB 2.0 MB/s\n...\nInstalling collected packages: datasette-vega\nAttempting uninstall: datasette-vega\n Found existing installation: datasette-vega 0.6\n Uninstalling datasette-vega-0.6:\n Successfully uninstalled datasette-vega-0.6\nSuccessfully installed datasette-vega-0.6.2\n\n$ datasette plugins\n[\n {\n \"name\": \"datasette-vega\",\n \"static\": true,\n \"templates\": false,\n \"version\": \"0.6.2\"\n }\n]", "breadcrumbs": "[\"Installation\", \"Advanced installation options\", \"Using pipx\"]", "references": "[]"} {"id": "changelog:through-for-joins-through-many-to-many-tables", "page": "changelog", "ref": "through-for-joins-through-many-to-many-tables", "title": "?_through= for joins through many-to-many tables", "content": "The new ?_through={json} argument to the Table view allows records to be filtered based on a many-to-many relationship. See Special table arguments for full documentation - here's an example . ( #355 ) \n This feature was added to help support facet by many-to-many , which isn't quite ready yet but will be coming in the next Datasette release.", "breadcrumbs": "[\"Changelog\", \"0.29 (2019-07-07)\"]", "references": "[{\"href\": \"https://latest.datasette.io/fixtures/roadside_attractions?_through={%22table%22:%22roadside_attraction_characteristics%22,%22column%22:%22characteristic_id%22,%22value%22:%221%22}\", \"label\": \"an example\"}, {\"href\": \"https://github.com/simonw/datasette/issues/355\", \"label\": \"#355\"}, {\"href\": \"https://github.com/simonw/datasette/issues/551\", \"label\": \"facet by many-to-many\"}]"} {"id": "changelog:the-road-to-datasette-1-0", "page": "changelog", "ref": "the-road-to-datasette-1-0", "title": "The road to Datasette 1.0", "content": "I've assembled a milestone for Datasette 1.0 . The focus of the 1.0 release will be the following: \n \n \n Signify confidence in the quality/stability of Datasette \n \n \n Give plugin authors confidence that their plugins will work for the whole 1.x release cycle \n \n \n Provide the same confidence to developers building against Datasette JSON APIs \n \n \n If you have thoughts about what you would like to see for Datasette 1.0 you can join the conversation on issue #519 .", "breadcrumbs": "[\"Changelog\", \"0.44 (2020-06-11)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/milestone/7\", \"label\": \"milestone for Datasette 1.0\"}, {\"href\": \"https://github.com/simonw/datasette/issues/519\", \"label\": \"the conversation on issue #519\"}]"} {"id": "changelog:the-internal-database", "page": "changelog", "ref": "the-internal-database", "title": "The _internal database", "content": "As part of ongoing work to help Datasette handle much larger numbers of connected databases and tables (see Datasette Library ) Datasette now maintains an in-memory SQLite database with details of all of the attached databases, tables, columns, indexes and foreign keys. ( #1150 ) \n This will support future improvements such as a searchable, paginated homepage of all available tables. \n You can explore an example of this database by signing in as root to the latest.datasette.io demo instance and then navigating to latest.datasette.io/_internal . \n Plugins can use these tables to introspect attached data in an efficient way. Plugin authors should note that this is not yet considered a stable interface, so any plugins that use this may need to make changes prior to Datasette 1.0 if the _internal table schemas change.", "breadcrumbs": "[\"Changelog\", \"0.54 (2021-01-25)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/417\", \"label\": \"Datasette Library\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1150\", \"label\": \"#1150\"}, {\"href\": \"https://latest.datasette.io/login-as-root\", \"label\": \"signing in as root\"}, {\"href\": \"https://latest.datasette.io/_internal\", \"label\": \"latest.datasette.io/_internal\"}]"} {"id": "testing_plugins:testing-plugins-register-in-test", "page": "testing_plugins", "ref": "testing-plugins-register-in-test", "title": "Registering a plugin for the duration of a test", "content": "When writing tests for plugins you may find it useful to register a test plugin just for the duration of a single test. You can do this using pm.register() and pm.unregister() like this: \n from datasette import hookimpl\nfrom datasette.app import Datasette\nfrom datasette.plugins import pm\nimport pytest\n\n\n@pytest.mark.asyncio\nasync def test_using_test_plugin():\n class TestPlugin:\n __name__ = \"TestPlugin\"\n\n # Use hookimpl and method names to register hooks\n @hookimpl\n def register_routes(self):\n return [\n (r\"^/error$\", lambda: 1 / 0),\n ]\n\n pm.register(TestPlugin(), name=\"undo\")\n try:\n # The test implementation goes here\n datasette = Datasette()\n response = await datasette.client.get(\"/error\")\n assert response.status_code == 500\n finally:\n pm.unregister(name=\"undo\")", "breadcrumbs": "[\"Testing plugins\"]", "references": "[]"} {"id": "testing_plugins:testing-plugins-pytest-httpx", "page": "testing_plugins", "ref": "testing-plugins-pytest-httpx", "title": "Testing outbound HTTP calls with pytest-httpx", "content": "If your plugin makes outbound HTTP calls - for example datasette-auth-github or datasette-import-table - you may need to mock those HTTP requests in your tests. \n The pytest-httpx package is a useful library for mocking calls. It can be tricky to use with Datasette though since it mocks all HTTPX requests, and Datasette's own testing mechanism uses HTTPX internally. \n To avoid breaking your tests, you can return [\"localhost\"] from the non_mocked_hosts() fixture. \n As an example, here's a very simple plugin which executes an HTTP response and returns the resulting content: \n from datasette import hookimpl\nfrom datasette.utils.asgi import Response\nimport httpx\n\n\n@hookimpl\ndef register_routes():\n return [\n (r\"^/-/fetch-url$\", fetch_url),\n ]\n\n\nasync def fetch_url(datasette, request):\n if request.method == \"GET\":\n return Response.html(\n \"\"\"\n
\n \n \n
\"\"\".format(\n request.scope[\"csrftoken\"]()\n )\n )\n vars = await request.post_vars()\n url = vars[\"url\"]\n return Response.text(httpx.get(url).text) \n Here's a test for that plugin that mocks the HTTPX outbound request: \n from datasette.app import Datasette\nimport pytest\n\n\n@pytest.fixture\ndef non_mocked_hosts():\n # This ensures httpx-mock will not affect Datasette's own\n # httpx calls made in the tests by datasette.client:\n return [\"localhost\"]\n\n\nasync def test_outbound_http_call(httpx_mock):\n httpx_mock.add_response(\n url=\"https://www.example.com/\",\n text=\"Hello world\",\n )\n datasette = Datasette([], memory=True)\n response = await datasette.client.post(\n \"/-/fetch-url\",\n data={\"url\": \"https://www.example.com/\"},\n )\n assert response.text == \"Hello world\"\n\n outbound_request = httpx_mock.get_request()\n assert (\n outbound_request.url == \"https://www.example.com/\"\n )", "breadcrumbs": "[\"Testing plugins\"]", "references": "[{\"href\": \"https://pypi.org/project/pytest-httpx/\", \"label\": \"pytest-httpx\"}]"} {"id": "testing_plugins:testing-plugins-pdb", "page": "testing_plugins", "ref": "testing-plugins-pdb", "title": "Using pdb for errors thrown inside Datasette", "content": "If an exception occurs within Datasette itself during a test, the response returned to your plugin will have a response.status_code value of 500. \n You can add pdb=True to the Datasette constructor to drop into a Python debugger session inside your test run instead of getting back a 500 response code. This is equivalent to running the datasette command-line tool with the --pdb option. \n Here's what that looks like in a test function: \n def test_that_opens_the_debugger_or_errors():\n ds = Datasette([db_path], pdb=True)\n response = await ds.client.get(\"/\") \n If you use this pattern you will need to run pytest with the -s option to avoid capturing stdin/stdout in order to interact with the debugger prompt.", "breadcrumbs": "[\"Testing plugins\"]", "references": "[]"} {"id": "testing_plugins:testing-plugins-fixtures", "page": "testing_plugins", "ref": "testing-plugins-fixtures", "title": "Using pytest fixtures", "content": "Pytest fixtures can be used to create initial testable objects which can then be used by multiple tests. \n A common pattern for Datasette plugins is to create a fixture which sets up a temporary test database and wraps it in a Datasette instance. \n Here's an example that uses the sqlite-utils library to populate a temporary test database. It also sets the title of that table using a simulated metadata.json configuration: \n from datasette.app import Datasette\nimport pytest\nimport sqlite_utils\n\n\n@pytest.fixture(scope=\"session\")\ndef datasette(tmp_path_factory):\n db_directory = tmp_path_factory.mktemp(\"dbs\")\n db_path = db_directory / \"test.db\"\n db = sqlite_utils.Database(db_path)\n db[\"dogs\"].insert_all(\n [\n {\"id\": 1, \"name\": \"Cleo\", \"age\": 5},\n {\"id\": 2, \"name\": \"Pancakes\", \"age\": 4},\n ],\n pk=\"id\",\n )\n datasette = Datasette(\n [db_path],\n metadata={\n \"databases\": {\n \"test\": {\n \"tables\": {\n \"dogs\": {\"title\": \"Some dogs\"}\n }\n }\n }\n },\n )\n return datasette\n\n\n@pytest.mark.asyncio\nasync def test_example_table_json(datasette):\n response = await datasette.client.get(\n \"/test/dogs.json?_shape=array\"\n )\n assert response.status_code == 200\n assert response.json() == [\n {\"id\": 1, \"name\": \"Cleo\", \"age\": 5},\n {\"id\": 2, \"name\": \"Pancakes\", \"age\": 4},\n ]\n\n\n@pytest.mark.asyncio\nasync def test_example_table_html(datasette):\n response = await datasette.client.get(\"/test/dogs\")\n assert \">Some dogs\" in response.text \n Here the datasette() function defines the fixture, which is than automatically passed to the two test functions based on pytest automatically matching their datasette function parameters. \n The @pytest.fixture(scope=\"session\") line here ensures the fixture is reused for the full pytest execution session. This means that the temporary database file will be created once and reused for each test. \n If you want to create that test database repeatedly for every individual test function, write the fixture function like this instead. You may want to do this if your plugin modifies the database contents in some way: \n @pytest.fixture\ndef datasette(tmp_path_factory):\n # This fixture will be executed repeatedly for every test\n ...", "breadcrumbs": "[\"Testing plugins\"]", "references": "[{\"href\": \"https://docs.pytest.org/en/stable/fixture.html\", \"label\": \"Pytest fixtures\"}, {\"href\": \"https://sqlite-utils.datasette.io/en/stable/python-api.html\", \"label\": \"sqlite-utils library\"}]"} {"id": "testing_plugins:testing-plugins-datasette-test-instance", "page": "testing_plugins", "ref": "testing-plugins-datasette-test-instance", "title": "Setting up a Datasette test instance", "content": "The above example shows the easiest way to start writing tests against a Datasette instance: \n from datasette.app import Datasette\nimport pytest\n\n\n@pytest.mark.asyncio\nasync def test_plugin_is_installed():\n datasette = Datasette(memory=True)\n response = await datasette.client.get(\"/-/plugins.json\")\n assert response.status_code == 200 \n Creating a Datasette() instance like this as useful shortcut in tests, but there is one detail you need to be aware of. It's important to ensure that the async method .invoke_startup() is called on that instance. You can do that like this: \n datasette = Datasette(memory=True)\nawait datasette.invoke_startup() \n This method registers any startup(datasette) or prepare_jinja2_environment(env, datasette) plugins that might themselves need to make async calls. \n If you are using await datasette.client.get() and similar methods then you don't need to worry about this - Datasette automatically calls invoke_startup() the first time it handles a request.", "breadcrumbs": "[\"Testing plugins\"]", "references": "[]"} {"id": "pages:tableview", "page": "pages", "ref": "tableview", "title": "Table", "content": "The table page is the heart of Datasette: it allows users to interactively explore the contents of a database table, including sorting, filtering, Full-text search and applying Facets . \n The HTML interface is worth spending some time exploring. As with other pages, you can return the JSON data by appending .json to the URL path, before any ? query string arguments. \n The query string arguments are described in more detail here: Table arguments \n You can also use the table page to interactively construct a SQL query - by applying different filters and a sort order for example - and then click the \"View and edit SQL\" link to see the SQL query that was used for the page and edit and re-submit it. \n Some examples: \n \n \n ../items lists all of the line-items registered by UK MPs as potential conflicts of interest. It demonstrates Datasette's support for Full-text search . \n \n \n ../antiquities-act%2Factions_under_antiquities_act is an interface for exploring the \"actions under the antiquities act\" data table published by FiveThirtyEight. \n \n \n ../global-power-plants?country_long=United+Kingdom&primary_fuel=Gas is a filtered table page showing every Gas power plant in the United Kingdom. It includes some default facets (configured using its metadata.json ) and uses the datasette-cluster-map plugin to show a map of the results.", "breadcrumbs": "[\"Pages and API endpoints\"]", "references": "[{\"href\": \"https://register-of-members-interests.datasettes.com/regmem/items\", \"label\": \"../items\"}, {\"href\": \"https://fivethirtyeight.datasettes.com/fivethirtyeight/antiquities-act%2Factions_under_antiquities_act\", \"label\": \"../antiquities-act%2Factions_under_antiquities_act\"}, {\"href\": \"https://global-power-plants.datasettes.com/global-power-plants/global-power-plants?_facet=primary_fuel&_facet=owner&_facet=country_long&country_long__exact=United+Kingdom&primary_fuel=Gas\", \"label\": \"../global-power-plants?country_long=United+Kingdom&primary_fuel=Gas\"}, {\"href\": \"https://global-power-plants.datasettes.com/-/metadata\", \"label\": \"its metadata.json\"}, {\"href\": \"https://github.com/simonw/datasette-cluster-map\", \"label\": \"datasette-cluster-map\"}]"} {"id": "facets:suggested-facets", "page": "facets", "ref": "suggested-facets", "title": "Suggested facets", "content": "Datasette's table UI will suggest facets for the user to apply, based on the following criteria: \n For the currently filtered data are there any columns which, if applied as a facet... \n \n \n Will return 30 or less unique options \n \n \n Will return more than one unique option \n \n \n Will return less unique options than the total number of filtered rows \n \n \n And the query used to evaluate this criteria can be completed in under 50ms \n \n \n That last point is particularly important: Datasette runs a query for every column that is displayed on a page, which could get expensive - so to avoid slow load times it sets a time limit of just 50ms for each of those queries.\n This means suggested facets are unlikely to appear for tables with millions of records in them.", "breadcrumbs": "[\"Facets\"]", "references": "[]"} {"id": "csv_export:streaming-all-records", "page": "csv_export", "ref": "streaming-all-records", "title": "Streaming all records", "content": "The stream all rows option is designed to be as efficient as possible -\n under the hood it takes advantage of Python 3 asyncio capabilities and\n Datasette's efficient pagination to stream back the full\n CSV file. \n Since databases can get pretty large, by default this option is capped at 100MB -\n if a table returns more than 100MB of data the last line of the CSV will be a\n truncation error message. \n You can increase or remove this limit using the max_csv_mb config\n setting. You can also disable the CSV export feature entirely using\n allow_csv_stream .", "breadcrumbs": "[\"CSV export\"]", "references": "[]"} {"id": "ecosystem:sqlite-utils", "page": "ecosystem", "ref": "sqlite-utils", "title": "sqlite-utils", "content": "sqlite-utils is a key building block for the wider Datasette ecosystem. It provides a collection of utilities for manipulating SQLite databases, both as a Python library and a command-line utility. Features include: \n \n \n Insert data into a SQLite database from JSON, CSV or TSV, automatically creating tables with the correct schema or altering existing tables to add missing columns. \n \n \n Configure tables for use with SQLite full-text search, including creating triggers needed to keep the search index up-to-date. \n \n \n Modify tables in ways that are not supported by SQLite's default ALTER TABLE syntax - for example changing the types of columns or selecting a new primary key for a table. \n \n \n Adding foreign keys to existing database tables. \n \n \n Extracting columns of data into a separate lookup table.", "breadcrumbs": "[\"The Datasette Ecosystem\"]", "references": "[{\"href\": \"https://sqlite-utils.datasette.io/\", \"label\": \"sqlite-utils\"}]"} {"id": "sql_queries:sql-views", "page": "sql_queries", "ref": "sql-views", "title": "Views", "content": "If you want to bundle some pre-written SQL queries with your Datasette-hosted database you can do so in two ways. The first is to include SQL views in your database - Datasette will then list those views on your database index page. \n The quickest way to create views is with the SQLite command-line interface: \n $ sqlite3 sf-trees.db\nSQLite version 3.19.3 2017-06-27 16:48:08\nEnter \".help\" for usage hints.\nsqlite> CREATE VIEW demo_view AS select qSpecies from Street_Tree_List;\n", "breadcrumbs": "[\"Running SQL queries\"]", "references": "[]"} {"id": "sql_queries:sql-parameters", "page": "sql_queries", "ref": "sql-parameters", "title": "Named parameters", "content": "Datasette has special support for SQLite named parameters. Consider a SQL query like this: \n select * from Street_Tree_List\nwhere \"PermitNotes\" like :notes\nand \"qSpecies\" = :species \n If you execute this query using the custom query editor, Datasette will extract the two named parameters and use them to construct form fields for you to provide values. \n You can also provide values for these fields by constructing a URL: \n /mydatabase?sql=select...&species=44 \n SQLite string escaping rules will be applied to values passed using named parameters - they will be wrapped in quotes and their content will be correctly escaped. \n Values from named parameters are treated as SQLite strings. If you need to perform numeric comparisons on them you should cast them to an integer or float first using cast(:name as integer) or cast(:name as real) , for example: \n select * from Street_Tree_List\nwhere latitude > cast(:min_latitude as real)\nand latitude < cast(:max_latitude as real) \n Datasette disallows custom SQL queries containing the string PRAGMA (with a small number of exceptions ) as SQLite pragma statements can be used to change database settings at runtime. If you need to include the string \"pragma\" in a query you can do so safely using a named parameter.", "breadcrumbs": "[\"Running SQL queries\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/761\", \"label\": \"of exceptions\"}]"} {"id": "sql_queries:sql", "page": "sql_queries", "ref": "sql", "title": "Running SQL queries", "content": "Datasette treats SQLite database files as read-only and immutable. This means it is not possible to execute INSERT or UPDATE statements using Datasette, which allows us to expose SELECT statements to the outside world without needing to worry about SQL injection attacks. \n The easiest way to execute custom SQL against Datasette is through the web UI. The database index page includes a SQL editor that lets you run any SELECT query you like. You can also construct queries using the filter interface on the tables page, then click \"View and edit SQL\" to open that query in the custom SQL editor. \n Note that this interface is only available if the execute-sql permission is allowed. \n Any Datasette SQL query is reflected in the URL of the page, allowing you to bookmark them, share them with others and navigate through previous queries using your browser back button. \n You can also retrieve the results of any query as JSON by adding .json to the base URL.", "breadcrumbs": "[]", "references": "[]"} {"id": "facets:speeding-up-facets-with-indexes", "page": "facets", "ref": "speeding-up-facets-with-indexes", "title": "Speeding up facets with indexes", "content": "The performance of facets can be greatly improved by adding indexes on the columns you wish to facet by.\n Adding indexes can be performed using the sqlite3 command-line utility. Here's how to add an index on the state column in a table called Food_Trucks : \n $ sqlite3 mydatabase.db\nSQLite version 3.19.3 2017-06-27 16:48:08\nEnter \".help\" for usage hints.\nsqlite> CREATE INDEX Food_Trucks_state ON Food_Trucks(\"state\"); \n Or using the sqlite-utils command-line utility: \n $ sqlite-utils create-index mydatabase.db Food_Trucks state", "breadcrumbs": "[\"Facets\"]", "references": "[{\"href\": \"https://sqlite-utils.datasette.io/en/stable/cli.html#creating-indexes\", \"label\": \"sqlite-utils\"}]"} {"id": "metadata:specifying-units-for-a-column", "page": "metadata", "ref": "specifying-units-for-a-column", "title": "Specifying units for a column", "content": "Datasette supports attaching units to a column, which will be used when displaying\n values from that column. SI prefixes will be used where appropriate. \n Column units are configured in the metadata like so: \n {\n \"databases\": {\n \"database1\": {\n \"tables\": {\n \"example_table\": {\n \"units\": {\n \"column1\": \"metres\",\n \"column2\": \"Hz\"\n }\n }\n }\n }\n }\n} \n Units are interpreted using Pint , and you can see the full list of available units in\n Pint's unit registry . You can also add custom units to the metadata, which will be\n registered with Pint: \n {\n \"custom_units\": [\n \"decibel = [] = dB\"\n ]\n}", "breadcrumbs": "[\"Metadata\"]", "references": "[{\"href\": \"https://pint.readthedocs.io/\", \"label\": \"Pint\"}, {\"href\": \"https://github.com/hgrecco/pint/blob/master/pint/default_en.txt\", \"label\": \"unit registry\"}, {\"href\": \"http://pint.readthedocs.io/en/latest/defining.html\", \"label\": \"custom units\"}]"} {"id": "spatialite:spatialite-warning", "page": "spatialite", "ref": "spatialite-warning", "title": "Warning", "content": "The SpatiaLite extension adds a large number of additional SQL functions , some of which are not be safe for untrusted users to execute: they may cause the Datasette server to crash. \n You should not expose a SpatiaLite-enabled Datasette instance to the public internet without taking extra measures to secure it against potentially harmful SQL queries. \n The following steps are recommended: \n \n \n Disable arbitrary SQL queries by untrusted users. See Controlling the ability to execute arbitrary SQL for ways to do this. The easiest is to start Datasette with the datasette --setting default_allow_sql off option. \n \n \n Define Canned queries with the SQL queries that use SpatiaLite functions that you want people to be able to execute. \n \n \n The Datasette SpatiaLite tutorial includes detailed instructions for running SpatiaLite safely using these techniques", "breadcrumbs": "[\"SpatiaLite\"]", "references": "[{\"href\": \"https://www.gaia-gis.it/gaia-sins/spatialite-sql-5.0.1.html\", \"label\": \"a large number of additional SQL functions\"}, {\"href\": \"https://datasette.io/tutorials/spatialite\", \"label\": \"Datasette SpatiaLite tutorial\"}]"} {"id": "spatialite:spatialite-installation", "page": "spatialite", "ref": "spatialite-installation", "title": "Installation", "content": "", "breadcrumbs": "[\"SpatiaLite\"]", "references": "[]"} {"id": "spatialite:spatial-indexing-latitude-longitude-columns", "page": "spatialite", "ref": "spatial-indexing-latitude-longitude-columns", "title": "Spatial indexing latitude/longitude columns", "content": "Here's a recipe for taking a table with existing latitude and longitude columns, adding a SpatiaLite POINT geometry column to that table, populating the new column and then populating a spatial index: \n import sqlite3\n\nconn = sqlite3.connect(\"museums.db\")\n# Lead the spatialite extension:\nconn.enable_load_extension(True)\nconn.load_extension(\"/usr/local/lib/mod_spatialite.dylib\")\n# Initialize spatial metadata for this database:\nconn.execute(\"select InitSpatialMetadata(1)\")\n# Add a geometry column called point_geom to our museums table:\nconn.execute(\n \"SELECT AddGeometryColumn('museums', 'point_geom', 4326, 'POINT', 2);\"\n)\n# Now update that geometry column with the lat/lon points\nconn.execute(\n \"\"\"\n UPDATE museums SET\n point_geom = GeomFromText('POINT('||\"longitude\"||' '||\"latitude\"||')',4326);\n\"\"\"\n)\n# Now add a spatial index to that column\nconn.execute(\n 'select CreateSpatialIndex(\"museums\", \"point_geom\");'\n)\n# If you don't commit your changes will not be persisted:\nconn.commit()\nconn.close()", "breadcrumbs": "[\"SpatiaLite\"]", "references": "[]"} {"id": "changelog:smaller-changes", "page": "changelog", "ref": "smaller-changes", "title": "Smaller changes", "content": "Wide tables shown within Datasette now scroll horizontally ( #998 ). This is achieved using a new
element which may impact the implementation of some plugins (for example this change to datasette-cluster-map ). \n \n \n New debug-menu permission. ( #1068 ) \n \n \n Removed --debug option, which didn't do anything. ( #814 ) \n \n \n Link: HTTP header pagination. ( #1014 ) \n \n \n x button for clearing filters. ( #1016 ) \n \n \n Edit SQL button on canned queries, ( #1019 ) \n \n \n --load-extension=spatialite shortcut. ( #1028 ) \n \n \n scale-in animation for column action menu. ( #1039 ) \n \n \n Option to pass a list of templates to .render_template() is now documented. ( #1045 ) \n \n \n New datasette.urls.static_plugins() method. ( #1033 ) \n \n \n datasette -o option now opens the most relevant page. ( #976 ) \n \n \n datasette --cors option now enables access to /database.db downloads. ( #1057 ) \n \n \n Database file downloads now implement cascading permissions, so you can download a database if you have view-database-download permission even if you do not have permission to access the Datasette instance. ( #1058 ) \n \n \n New documentation on Designing URLs for your plugin . ( #1053 )", "breadcrumbs": "[\"Changelog\", \"0.51 (2020-10-31)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/998\", \"label\": \"#998\"}, {\"href\": \"https://github.com/simonw/datasette-cluster-map/commit/fcb4abbe7df9071c5ab57defd39147de7145b34e\", \"label\": \"this change to datasette-cluster-map\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1068\", \"label\": \"#1068\"}, {\"href\": \"https://github.com/simonw/datasette/issues/814\", \"label\": \"#814\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1014\", \"label\": \"#1014\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1016\", \"label\": \"#1016\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1019\", \"label\": \"#1019\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1028\", \"label\": \"#1028\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1039\", \"label\": \"#1039\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1045\", \"label\": \"#1045\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1033\", \"label\": \"#1033\"}, {\"href\": \"https://github.com/simonw/datasette/issues/976\", \"label\": \"#976\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1057\", \"label\": \"#1057\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1058\", \"label\": \"#1058\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1053\", \"label\": \"#1053\"}]"} {"id": "changelog:small-changes", "page": "changelog", "ref": "small-changes", "title": "Small changes", "content": "Databases published using datasette publish now open in Immutable mode . ( #469 ) \n \n \n ?col__date= now works for columns containing spaces \n \n \n Automatic label detection (for deciding which column to show when linking to a foreign key) has been improved. ( #485 ) \n \n \n Fixed bug where pagination broke when combined with an expanded foreign key. ( #489 ) \n \n \n Contributors can now run pip install -e .[docs] to get all of the dependencies needed to build the documentation, including cd docs && make livehtml support. \n \n \n Datasette's dependencies are now all specified using the ~= match operator. ( #532 ) \n \n \n white-space: pre-wrap now used for table creation SQL. ( #505 ) \n \n \n Full list of commits between 0.28 and 0.29.", "breadcrumbs": "[\"Changelog\", \"0.29 (2019-07-07)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/469\", \"label\": \"#469\"}, {\"href\": \"https://github.com/simonw/datasette/issues/485\", \"label\": \"#485\"}, {\"href\": \"https://github.com/simonw/datasette/issues/489\", \"label\": \"#489\"}, {\"href\": \"https://github.com/simonw/datasette/issues/532\", \"label\": \"#532\"}, {\"href\": \"https://github.com/simonw/datasette/issues/505\", \"label\": \"#505\"}, {\"href\": \"https://github.com/simonw/datasette/compare/0.28...0.29\", \"label\": \"Full list of commits\"}]"} {"id": "changelog:signed-values-and-secrets", "page": "changelog", "ref": "signed-values-and-secrets", "title": "Signed values and secrets", "content": "Both flash messages and user authentication needed a way to sign values and set signed cookies. Two new methods are now available for plugins to take advantage of this mechanism: .sign(value, namespace=\"default\") and .unsign(value, namespace=\"default\") . \n Datasette will generate a secret automatically when it starts up, but to avoid resetting the secret (and hence invalidating any cookies) every time the server restarts you should set your own secret. You can pass a secret to Datasette using the new --secret option or with a DATASETTE_SECRET environment variable. See Configuring the secret for more details. \n You can also set a secret when you deploy Datasette using datasette publish or datasette package - see Using secrets with datasette publish . \n Plugins can now sign values and verify their signatures using the datasette.sign() and datasette.unsign() methods.", "breadcrumbs": "[\"Changelog\", \"0.44 (2020-06-11)\"]", "references": "[]"} {"id": "settings:setting-truncate-cells-html", "page": "settings", "ref": "setting-truncate-cells-html", "title": "truncate_cells_html", "content": "In the HTML table view, truncate any strings that are longer than this value.\n The full value will still be available in CSV, JSON and on the individual row\n HTML page. Set this to 0 to disable truncation. \n datasette mydatabase.db --setting truncate_cells_html 0", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[]"} {"id": "settings:setting-trace-debug", "page": "settings", "ref": "setting-trace-debug", "title": "trace_debug", "content": "This setting enables appending ?_trace=1 to any page in order to see the SQL queries and other trace information that was used to generate that page. \n Enable it like this: \n datasette mydatabase.db --setting trace_debug 1 \n Some examples: \n \n \n https://latest.datasette.io/?_trace=1 \n \n \n https://latest.datasette.io/fixtures/roadside_attractions?_trace=1 \n \n \n See datasette.tracer for details on how to hook into this mechanism as a plugin author.", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[{\"href\": \"https://latest.datasette.io/?_trace=1\", \"label\": \"https://latest.datasette.io/?_trace=1\"}, {\"href\": \"https://latest.datasette.io/fixtures/roadside_attractions?_trace=1\", \"label\": \"https://latest.datasette.io/fixtures/roadside_attractions?_trace=1\"}]"} {"id": "settings:setting-template-debug", "page": "settings", "ref": "setting-template-debug", "title": "template_debug", "content": "This setting enables template context debug mode, which is useful to help understand what variables are available to custom templates when you are writing them. \n Enable it like this: \n datasette mydatabase.db --setting template_debug 1 \n Now you can add ?_context=1 or &_context=1 to any Datasette page to see the context that was passed to that template. \n Some examples: \n \n \n https://latest.datasette.io/?_context=1 \n \n \n https://latest.datasette.io/fixtures?_context=1 \n \n \n https://latest.datasette.io/fixtures/roadside_attractions?_context=1", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[{\"href\": \"https://latest.datasette.io/?_context=1\", \"label\": \"https://latest.datasette.io/?_context=1\"}, {\"href\": \"https://latest.datasette.io/fixtures?_context=1\", \"label\": \"https://latest.datasette.io/fixtures?_context=1\"}, {\"href\": \"https://latest.datasette.io/fixtures/roadside_attractions?_context=1\", \"label\": \"https://latest.datasette.io/fixtures/roadside_attractions?_context=1\"}]"} {"id": "settings:setting-suggest-facets", "page": "settings", "ref": "setting-suggest-facets", "title": "suggest_facets", "content": "Should Datasette calculate suggested facets? On by default, turn this off like so: \n datasette mydatabase.db --setting suggest_facets off", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[]"} {"id": "settings:setting-sql-time-limit-ms", "page": "settings", "ref": "setting-sql-time-limit-ms", "title": "sql_time_limit_ms", "content": "By default, queries have a time limit of one second. If a query takes longer than this to run Datasette will terminate the query and return an error. \n If this time limit is too short for you, you can customize it using the sql_time_limit_ms limit - for example, to increase it to 3.5 seconds: \n datasette mydatabase.db --setting sql_time_limit_ms 3500 \n You can optionally set a lower time limit for an individual query using the ?_timelimit=100 query string argument: \n /my-database/my-table?qSpecies=44&_timelimit=100 \n This would set the time limit to 100ms for that specific query. This feature is useful if you are working with databases of unknown size and complexity - a query that might make perfect sense for a smaller table could take too long to execute on a table with millions of rows. By setting custom time limits you can execute queries \"optimistically\" - e.g. give me an exact count of rows matching this query but only if it takes less than 100ms to calculate.", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[]"} {"id": "settings:setting-secret", "page": "settings", "ref": "setting-secret", "title": "Configuring the secret", "content": "Datasette uses a secret string to sign secure values such as cookies. \n If you do not provide a secret, Datasette will create one when it starts up. This secret will reset every time the Datasette server restarts though, so things like authentication cookies will not stay valid between restarts. \n You can pass a secret to Datasette in two ways: with the --secret command-line option or by setting a DATASETTE_SECRET environment variable. \n $ datasette mydb.db --secret=SECRET_VALUE_HERE \n Or: \n $ export DATASETTE_SECRET=SECRET_VALUE_HERE\n$ datasette mydb.db \n One way to generate a secure random secret is to use Python like this: \n $ python3 -c 'import secrets; print(secrets.token_hex(32))'\ncdb19e94283a20f9d42cca50c5a4871c0aa07392db308755d60a1a5b9bb0fa52 \n Plugin authors make use of this signing mechanism in their plugins using .sign(value, namespace=\"default\") and .unsign(value, namespace=\"default\") .", "breadcrumbs": "[\"Settings\"]", "references": "[]"} {"id": "settings:setting-publish-secrets", "page": "settings", "ref": "setting-publish-secrets", "title": "Using secrets with datasette publish", "content": "The datasette publish and datasette package commands both generate a secret for you automatically when Datasette is deployed. \n This means that every time you deploy a new version of a Datasette project, a new secret will be generated. This will cause signed cookies to become invalid on every fresh deploy. \n You can fix this by creating a secret that will be used for multiple deploys and passing it using the --secret option: \n datasette publish cloudrun mydb.db --service=my-service --secret=cdb19e94283a20f9d42cca5", "breadcrumbs": "[\"Settings\"]", "references": "[]"} {"id": "settings:setting-num-sql-threads", "page": "settings", "ref": "setting-num-sql-threads", "title": "num_sql_threads", "content": "Maximum number of threads in the thread pool Datasette uses to execute SQLite queries. Defaults to 3. \n datasette mydatabase.db --setting num_sql_threads 10 \n Setting this to 0 turns off threaded SQL queries entirely - useful for environments that do not support threading such as Pyodide .", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[{\"href\": \"https://pyodide.org/\", \"label\": \"Pyodide\"}]"} {"id": "settings:setting-max-returned-rows", "page": "settings", "ref": "setting-max-returned-rows", "title": "max_returned_rows", "content": "Datasette returns a maximum of 1,000 rows of data at a time. If you execute a query that returns more than 1,000 rows, Datasette will return the first 1,000 and include a warning that the result set has been truncated. You can use OFFSET/LIMIT or other methods in your SQL to implement pagination if you need to return more than 1,000 rows. \n You can increase or decrease this limit like so: \n datasette mydatabase.db --setting max_returned_rows 2000", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[]"} {"id": "settings:setting-max-csv-mb", "page": "settings", "ref": "setting-max-csv-mb", "title": "max_csv_mb", "content": "The maximum size of CSV that can be exported, in megabytes. Defaults to 100MB.\n You can disable the limit entirely by settings this to 0: \n datasette mydatabase.db --setting max_csv_mb 0", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[]"} {"id": "settings:setting-force-https-urls", "page": "settings", "ref": "setting-force-https-urls", "title": "force_https_urls", "content": "Forces self-referential URLs in the JSON output to always use the https:// \n protocol. This is useful for cases where the application itself is hosted using\n HTTP but is served to the outside world via a proxy that enables HTTPS. \n datasette mydatabase.db --setting force_https_urls 1", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[]"} {"id": "settings:setting-facet-time-limit-ms", "page": "settings", "ref": "setting-facet-time-limit-ms", "title": "facet_time_limit_ms", "content": "This is the time limit Datasette allows for calculating a facet, which defaults to 200ms: \n datasette mydatabase.db --setting facet_time_limit_ms 1000", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[]"} {"id": "settings:setting-facet-suggest-time-limit-ms", "page": "settings", "ref": "setting-facet-suggest-time-limit-ms", "title": "facet_suggest_time_limit_ms", "content": "When Datasette calculates suggested facets it needs to run a SQL query for every column in your table. The default for this time limit is 50ms to account for the fact that it needs to run once for every column. If the time limit is exceeded the column will not be suggested as a facet. \n You can increase this time limit like so: \n datasette mydatabase.db --setting facet_suggest_time_limit_ms 500", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[]"} {"id": "settings:setting-default-page-size", "page": "settings", "ref": "setting-default-page-size", "title": "default_page_size", "content": "The default number of rows returned by the table page. You can over-ride this on a per-page basis using the ?_size=80 query string parameter, provided you do not specify a value higher than the max_returned_rows setting. You can set this default using --setting like so: \n datasette mydatabase.db --setting default_page_size 50", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[]"} {"id": "settings:setting-default-facet-size", "page": "settings", "ref": "setting-default-facet-size", "title": "default_facet_size", "content": "The default number of unique rows returned by Facets is 30. You can customize it like this: \n datasette mydatabase.db --setting default_facet_size 50", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[]"} {"id": "settings:setting-default-cache-ttl", "page": "settings", "ref": "setting-default-cache-ttl", "title": "default_cache_ttl", "content": "Default HTTP caching max-age header in seconds, used for Cache-Control: max-age=X . Can be over-ridden on a per-request basis using the ?_ttl= query string parameter. Set this to 0 to disable HTTP caching entirely. Defaults to 5 seconds. \n datasette mydatabase.db --setting default_cache_ttl 60", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[]"} {"id": "settings:setting-default-allow-sql", "page": "settings", "ref": "setting-default-allow-sql", "title": "default_allow_sql", "content": "Should users be able to execute arbitrary SQL queries by default? \n Setting this to off causes permission checks for execute-sql to fail by default. \n datasette mydatabase.db --setting default_allow_sql off \n There are two ways to achieve this: the other is to add \"allow_sql\": false to your metadata.json file, as described in Controlling the ability to execute arbitrary SQL . This setting offers a more convenient way to do this.", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[]"} {"id": "settings:setting-cache-size-kb", "page": "settings", "ref": "setting-cache-size-kb", "title": "cache_size_kb", "content": "Sets the amount of memory SQLite uses for its per-connection cache , in KB. \n datasette mydatabase.db --setting cache_size_kb 5000", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[{\"href\": \"https://www.sqlite.org/pragma.html#pragma_cache_size\", \"label\": \"per-connection cache\"}]"} {"id": "settings:setting-base-url", "page": "settings", "ref": "setting-base-url", "title": "base_url", "content": "If you are running Datasette behind a proxy, it may be useful to change the root path used for the Datasette instance. \n For example, if you are sending traffic from https://www.example.com/tools/datasette/ through to a proxied Datasette instance you may wish Datasette to use /tools/datasette/ as its root URL. \n You can do that like so: \n datasette mydatabase.db --setting base_url /tools/datasette/", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[]"} {"id": "settings:setting-allow-facet", "page": "settings", "ref": "setting-allow-facet", "title": "allow_facet", "content": "Allow users to specify columns they would like to facet on using the ?_facet=COLNAME URL parameter to the table view. \n This is enabled by default. If disabled, facets will still be displayed if they have been specifically enabled in metadata.json configuration for the table. \n Here's how to disable this feature: \n datasette mydatabase.db --setting allow_facet off", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[]"} {"id": "settings:setting-allow-download", "page": "settings", "ref": "setting-allow-download", "title": "allow_download", "content": "Should users be able to download the original SQLite database using a link on the database index page? This is turned on by default. However, databases can only be downloaded if they are served in immutable mode and not in-memory. If downloading is unavailable for either of these reasons, the download link is hidden even if allow_download is on. To disable database downloads, use the following: \n datasette mydatabase.db --setting allow_download off", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[]"} {"id": "settings:setting-allow-csv-stream", "page": "settings", "ref": "setting-allow-csv-stream", "title": "allow_csv_stream", "content": "Enables the CSV export feature where an entire table\n (potentially hundreds of thousands of rows) can be exported as a single CSV\n file. This is turned on by default - you can turn it off like this: \n datasette mydatabase.db --setting allow_csv_stream off", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[]"} {"id": "changelog:secret-plugin-configuration-options", "page": "changelog", "ref": "secret-plugin-configuration-options", "title": "Secret plugin configuration options", "content": "Plugins like datasette-auth-github need a safe way to set secret configuration options. Since the default mechanism for configuring plugins exposes those settings in /-/metadata a new mechanism was needed. Secret configuration values describes how plugins can now specify that their settings should be read from a file or an environment variable: \n {\n \"plugins\": {\n \"datasette-auth-github\": {\n \"client_secret\": {\n \"$env\": \"GITHUB_CLIENT_SECRET\"\n }\n }\n }\n} \n These plugin secrets can be set directly using datasette publish . See Custom metadata and plugins for details. ( #538 and #543 )", "breadcrumbs": "[\"Changelog\", \"0.29 (2019-07-07)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette-auth-github\", \"label\": \"datasette-auth-github\"}, {\"href\": \"https://github.com/simonw/datasette/issues/538\", \"label\": \"#538\"}, {\"href\": \"https://github.com/simonw/datasette/issues/543\", \"label\": \"#543\"}]"} {"id": "changelog:running-datasette-behind-a-proxy", "page": "changelog", "ref": "running-datasette-behind-a-proxy", "title": "Running Datasette behind a proxy", "content": "The base_url configuration option is designed to help run Datasette on a specific path behind a proxy - for example if you want to run an instance of Datasette at /my-datasette/ within your existing site's URL hierarchy, proxied behind nginx or Apache. \n Support for this configuration option has been greatly improved ( #1023 ), and guidelines for using it are now available in a new documentation section on Running Datasette behind a proxy . ( #1027 )", "breadcrumbs": "[\"Changelog\", \"0.51 (2020-10-31)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/1023\", \"label\": \"#1023\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1027\", \"label\": \"#1027\"}]"} {"id": "pages:rowview", "page": "pages", "ref": "rowview", "title": "Row", "content": "Every row in every Datasette table has its own URL. This means individual records can be linked to directly. \n Table cells with extremely long text contents are truncated on the table view according to the truncate_cells_html setting. If a cell has been truncated the full length version of that cell will be available on the row page. \n Rows which are the targets of foreign key references from other tables will show a link to a filtered search for all records that reference that row. Here's an example from the Registers of Members Interests database: \n ../people/uk.org.publicwhip%2Fperson%2F10001 \n Note that this URL includes the encoded primary key of the record. \n Here's that same page as JSON: \n ../people/uk.org.publicwhip%2Fperson%2F10001.json", "breadcrumbs": "[\"Pages and API endpoints\"]", "references": "[{\"href\": \"https://register-of-members-interests.datasettes.com/regmem/people/uk.org.publicwhip%2Fperson%2F10001\", \"label\": \"../people/uk.org.publicwhip%2Fperson%2F10001\"}, {\"href\": \"https://register-of-members-interests.datasettes.com/regmem/people/uk.org.publicwhip%2Fperson%2F10001.json\", \"label\": \"../people/uk.org.publicwhip%2Fperson%2F10001.json\"}]"} {"id": "changelog:register-routes-plugin-hooks", "page": "changelog", "ref": "register-routes-plugin-hooks", "title": "register_routes() plugin hooks", "content": "Plugins can now register new views and routes via the register_routes(datasette) plugin hook ( #819 ). View functions can be defined that accept any of the current datasette object, the current request , or the ASGI scope , send and receive objects.", "breadcrumbs": "[\"Changelog\", \"0.44 (2020-06-11)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/819\", \"label\": \"#819\"}]"} {"id": "spatialite:querying-polygons-using-within", "page": "spatialite", "ref": "querying-polygons-using-within", "title": "Querying polygons using within()", "content": "The within() SQL function can be used to check if a point is within a geometry: \n select\n name\nfrom\n places\nwhere\n within(GeomFromText('POINT(-3.1724366 51.4704448)'), places.geom); \n The GeomFromText() function takes a string of well-known text. Note that the order used here is longitude then latitude . \n To run that same within() query in a way that benefits from the spatial index, use the following: \n select\n name\nfrom\n places\nwhere\n within(GeomFromText('POINT(-3.1724366 51.4704448)'), places.geom)\n and rowid in (\n SELECT pkid FROM idx_places_geom\n where xmin < -3.1724366\n and xmax > -3.1724366\n and ymin < 51.4704448\n and ymax > 51.4704448\n );", "breadcrumbs": "[\"SpatiaLite\"]", "references": "[]"} {"id": "custom_templates:publishing-static-assets", "page": "custom_templates", "ref": "publishing-static-assets", "title": "Publishing static assets", "content": "The datasette publish command can be used to publish your static assets,\n using the same syntax as above: \n $ datasette publish cloudrun mydb.db --static assets:static-files/ \n This will upload the contents of the static-files/ directory as part of the\n deployment, and configure Datasette to correctly serve the assets from /assets/ .", "breadcrumbs": "[\"Custom pages and templates\", \"Custom CSS and JavaScript\"]", "references": "[]"} {"id": "publish:publishing", "page": "publish", "ref": "publishing", "title": "Publishing data", "content": "Datasette includes tools for publishing and deploying your data to the internet. The datasette publish command will deploy a new Datasette instance containing your databases directly to a Heroku or Google Cloud hosting account. You can also use datasette package to create a Docker image that bundles your databases together with the datasette application that is used to serve them.", "breadcrumbs": "[]", "references": "[]"} {"id": "publish:publish-vercel", "page": "publish", "ref": "publish-vercel", "title": "Publishing to Vercel", "content": "Vercel - previously known as Zeit Now - provides a layer over AWS Lambda to allow for quick, scale-to-zero deployment. You can deploy Datasette instances to Vercel using the datasette-publish-vercel plugin. \n pip install datasette-publish-vercel\ndatasette publish vercel mydatabase.db --project my-database-project \n Not every feature is supported: consult the datasette-publish-vercel README for more details.", "breadcrumbs": "[\"Publishing data\", \"datasette publish\"]", "references": "[{\"href\": \"https://vercel.com/\", \"label\": \"Vercel\"}, {\"href\": \"https://github.com/simonw/datasette-publish-vercel\", \"label\": \"datasette-publish-vercel\"}, {\"href\": \"https://github.com/simonw/datasette-publish-vercel/blob/main/README.md\", \"label\": \"datasette-publish-vercel README\"}]"} {"id": "publish:publish-heroku", "page": "publish", "ref": "publish-heroku", "title": "Publishing to Heroku", "content": "To publish your data using Heroku , first create an account there and install and configure the Heroku CLI tool . \n You can publish one or more databases to Heroku using the following command: \n datasette publish heroku mydatabase.db \n This will output some details about the new deployment, including a URL like this one: \n https://limitless-reef-88278.herokuapp.com/ deployed to Heroku \n You can specify a custom app name by passing -n my-app-name to the publish command. This will also allow you to overwrite an existing app. \n Rather than deploying directly you can use the --generate-dir option to output the files that would be deployed to a directory: \n datasette publish heroku mydatabase.db --generate-dir=/tmp/deploy-this-to-heroku \n See datasette publish heroku for the full list of options for this command.", "breadcrumbs": "[\"Publishing data\", \"datasette publish\"]", "references": "[{\"href\": \"https://www.heroku.com/\", \"label\": \"Heroku\"}, {\"href\": \"https://devcenter.heroku.com/articles/heroku-cli\", \"label\": \"Heroku CLI tool\"}]"} {"id": "publish:publish-fly", "page": "publish", "ref": "publish-fly", "title": "Publishing to Fly", "content": "Fly is a competitively priced Docker-compatible hosting platform that supports running applications in globally distributed data centers close to your end users. You can deploy Datasette instances to Fly using the datasette-publish-fly plugin. \n pip install datasette-publish-fly\ndatasette publish fly mydatabase.db --app=\"my-app\" \n Consult the datasette-publish-fly README for more details.", "breadcrumbs": "[\"Publishing data\", \"datasette publish\"]", "references": "[{\"href\": \"https://fly.io/\", \"label\": \"Fly\"}, {\"href\": \"https://fly.io/docs/pricing/\", \"label\": \"competitively priced\"}, {\"href\": \"https://github.com/simonw/datasette-publish-fly\", \"label\": \"datasette-publish-fly\"}, {\"href\": \"https://github.com/simonw/datasette-publish-fly/blob/main/README.md\", \"label\": \"datasette-publish-fly README\"}]"} {"id": "publish:publish-custom-metadata-and-plugins", "page": "publish", "ref": "publish-custom-metadata-and-plugins", "title": "Custom metadata and plugins", "content": "datasette publish accepts a number of additional options which can be used to further customize your Datasette instance. \n You can define your own Metadata and deploy that with your instance like so: \n datasette publish cloudrun --service=my-service mydatabase.db -m metadata.json \n If you just want to set the title, license or source information you can do that directly using extra options to datasette publish : \n datasette publish cloudrun mydatabase.db --service=my-service \\\n --title=\"Title of my database\" \\\n --source=\"Where the data originated\" \\\n --source_url=\"http://www.example.com/\" \n You can also specify plugins you would like to install. For example, if you want to include the datasette-vega visualization plugin you can use the following: \n datasette publish cloudrun mydatabase.db --service=my-service --install=datasette-vega \n If a plugin has any Secret configuration values you can use the --plugin-secret option to set those secrets at publish time. For example, using Heroku with datasette-auth-github you might run the following command: \n $ datasette publish heroku my_database.db \\\n --name my-heroku-app-demo \\\n --install=datasette-auth-github \\\n --plugin-secret datasette-auth-github client_id your_client_id \\\n --plugin-secret datasette-auth-github client_secret your_client_secret", "breadcrumbs": "[\"Publishing data\", \"datasette publish\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette-vega\", \"label\": \"datasette-vega\"}, {\"href\": \"https://github.com/simonw/datasette-auth-github\", \"label\": \"datasette-auth-github\"}]"} {"id": "publish:publish-cloud-run", "page": "publish", "ref": "publish-cloud-run", "title": "Publishing to Google Cloud Run", "content": "Google Cloud Run allows you to publish data in a scale-to-zero environment, so your application will start running when the first request is received and will shut down again when traffic ceases. This means you only pay for time spent serving traffic. \n \n Cloud Run is a great option for inexpensively hosting small, low traffic projects - but costs can add up for projects that serve a lot of requests. \n Be particularly careful if your project has tables with large numbers of rows. Search engine crawlers that index a page for every row could result in a high bill. \n The datasette-block-robots plugin can be used to request search engine crawlers omit crawling your site, which can help avoid this issue. \n \n You will first need to install and configure the Google Cloud CLI tools by following these instructions . \n You can then publish one or more SQLite database files to Google Cloud Run using the following command: \n datasette publish cloudrun mydatabase.db --service=my-database \n A Cloud Run service is a single hosted application. The service name you specify will be used as part of the Cloud Run URL. If you deploy to a service name that you have used in the past your new deployment will replace the previous one. \n If you omit the --service option you will be asked to pick a service name interactively during the deploy. \n You may need to interact with prompts from the tool. Many of the prompts ask for values that can be set as properties for the Google Cloud SDK if you want to avoid the prompts. \n For example, the default region for the deployed instance can be set using the command: \n gcloud config set run/region us-central1 \n You should replace us-central1 with your desired region . Alternately, you can specify the region by setting the CLOUDSDK_RUN_REGION environment variable. \n Once it has finished it will output a URL like this one: \n Service [my-service] revision [my-service-00001] has been deployed\nand is serving traffic at https://my-service-j7hipcg4aq-uc.a.run.app \n Cloud Run provides a URL on the .run.app domain, but you can also point your own domain or subdomain at your Cloud Run service - see mapping custom domains in the Cloud Run documentation for details. \n See datasette publish cloudrun for the full list of options for this command.", "breadcrumbs": "[\"Publishing data\", \"datasette publish\"]", "references": "[{\"href\": \"https://cloud.google.com/run/\", \"label\": \"Google Cloud Run\"}, {\"href\": \"https://datasette.io/plugins/datasette-block-robots\", \"label\": \"datasette-block-robots\"}, {\"href\": \"https://cloud.google.com/sdk/\", \"label\": \"these instructions\"}, {\"href\": \"https://cloud.google.com/sdk/docs/properties\", \"label\": \"set as properties for the Google Cloud SDK\"}, {\"href\": \"https://cloud.google.com/about/locations\", \"label\": \"region\"}, {\"href\": \"https://cloud.google.com/run/docs/mapping-custom-domains\", \"label\": \"mapping custom domains\"}]"} {"id": "plugins:plugins-installing", "page": "plugins", "ref": "plugins-installing", "title": "Installing plugins", "content": "If a plugin has been packaged for distribution using setuptools you can use the plugin by installing it alongside Datasette in the same virtual environment or Docker container. \n You can install plugins using the datasette install command: \n datasette install datasette-vega \n You can uninstall plugins with datasette uninstall : \n datasette uninstall datasette-vega \n You can upgrade plugins with datasette install --upgrade or datasette install -U : \n datasette install -U datasette-vega \n This command can also be used to upgrade Datasette itself to the latest released version: \n datasette install -U datasette \n These commands are thin wrappers around pip install and pip uninstall , which ensure they run pip in the same virtual environment as Datasette itself.", "breadcrumbs": "[\"Plugins\"]", "references": "[]"} {"id": "plugins:plugins-installed", "page": "plugins", "ref": "plugins-installed", "title": "Seeing what plugins are installed", "content": "You can see a list of installed plugins by navigating to the /-/plugins page of your Datasette instance - for example: https://fivethirtyeight.datasettes.com/-/plugins \n You can also use the datasette plugins command: \n $ datasette plugins\n[\n {\n \"name\": \"datasette_json_html\",\n \"static\": false,\n \"templates\": false,\n \"version\": \"0.4.0\"\n }\n] \n [[[cog\nfrom datasette import cli\nfrom click.testing import CliRunner\nimport textwrap, json\ncog.out(\"\\n\")\nresult = CliRunner().invoke(cli.cli, [\"plugins\", \"--all\"])\n# cog.out() with text containing newlines was unindenting for some reason\ncog.outl(\"If you run ``datasette plugins --all`` it will include default plugins that ship as part of Datasette::\\n\")\nplugins = [p for p in json.loads(result.output) if p[\"name\"].startswith(\"datasette.\")]\nindented = textwrap.indent(json.dumps(plugins, indent=4), \" \")\nfor line in indented.split(\"\\n\"):\n cog.outl(line)\ncog.out(\"\\n\\n\") \n ]]] \n If you run datasette plugins --all it will include default plugins that ship as part of Datasette: \n [\n {\n \"name\": \"datasette.actor_auth_cookie\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"actor_from_request\"\n ]\n },\n {\n \"name\": \"datasette.blob_renderer\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"register_output_renderer\"\n ]\n },\n {\n \"name\": \"datasette.default_magic_parameters\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"register_magic_parameters\"\n ]\n },\n {\n \"name\": \"datasette.default_menu_links\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"menu_links\"\n ]\n },\n {\n \"name\": \"datasette.default_permissions\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"permission_allowed\"\n ]\n },\n {\n \"name\": \"datasette.facets\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"register_facet_classes\"\n ]\n },\n {\n \"name\": \"datasette.filters\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"filters_from_request\"\n ]\n },\n {\n \"name\": \"datasette.forbidden\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"forbidden\"\n ]\n },\n {\n \"name\": \"datasette.handle_exception\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"handle_exception\"\n ]\n },\n {\n \"name\": \"datasette.publish.cloudrun\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"publish_subcommand\"\n ]\n },\n {\n \"name\": \"datasette.publish.heroku\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"publish_subcommand\"\n ]\n },\n {\n \"name\": \"datasette.sql_functions\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"prepare_connection\"\n ]\n }\n] \n [[[end]]] \n You can add the --plugins-dir= option to include any plugins found in that directory.", "breadcrumbs": "[\"Plugins\"]", "references": "[{\"href\": \"https://fivethirtyeight.datasettes.com/-/plugins\", \"label\": \"https://fivethirtyeight.datasettes.com/-/plugins\"}]"} {"id": "plugins:plugins-configuration-secret", "page": "plugins", "ref": "plugins-configuration-secret", "title": "Secret configuration values", "content": "Any values embedded in metadata.json will be visible to anyone who views the /-/metadata page of your Datasette instance. Some plugins may need configuration that should stay secret - API keys for example. There are two ways in which you can store secret configuration values. \n As environment variables . If your secret lives in an environment variable that is available to the Datasette process, you can indicate that the configuration value should be read from that environment variable like so: \n {\n \"plugins\": {\n \"datasette-auth-github\": {\n \"client_secret\": {\n \"$env\": \"GITHUB_CLIENT_SECRET\"\n }\n }\n }\n} \n As values in separate files . Your secrets can also live in files on disk. To specify a secret should be read from a file, provide the full file path like this: \n {\n \"plugins\": {\n \"datasette-auth-github\": {\n \"client_secret\": {\n \"$file\": \"/secrets/client-secret\"\n }\n }\n }\n} \n If you are publishing your data using the datasette publish family of commands, you can use the --plugin-secret option to set these secrets at publish time. For example, using Heroku you might run the following command: \n $ datasette publish heroku my_database.db \\\n --name my-heroku-app-demo \\\n --install=datasette-auth-github \\\n --plugin-secret datasette-auth-github client_id your_client_id \\\n --plugin-secret datasette-auth-github client_secret your_client_secret \n This will set the necessary environment variables and add the following to the deployed metadata.json : \n {\n \"plugins\": {\n \"datasette-auth-github\": {\n \"client_id\": {\n \"$env\": \"DATASETTE_AUTH_GITHUB_CLIENT_ID\"\n },\n \"client_secret\": {\n \"$env\": \"DATASETTE_AUTH_GITHUB_CLIENT_SECRET\"\n }\n }\n }\n}", "breadcrumbs": "[\"Plugins\", \"Plugin configuration\"]", "references": "[]"} {"id": "plugins:plugins-configuration", "page": "plugins", "ref": "plugins-configuration", "title": "Plugin configuration", "content": "Plugins can have their own configuration, embedded in a 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. \n Here is an example of some plugin configuration for a specific table: \n {\n \"databases\": {\n \"sf-trees\": {\n \"tables\": {\n \"Street_Tree_List\": {\n \"plugins\": {\n \"datasette-cluster-map\": {\n \"latitude_column\": \"lat\",\n \"longitude_column\": \"lng\"\n }\n }\n }\n }\n }\n }\n} \n 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 .", "breadcrumbs": "[\"Plugins\"]", "references": "[]"} {"id": "changelog:plugins-can-now-add-links-within-datasette", "page": "changelog", "ref": "plugins-can-now-add-links-within-datasette", "title": "Plugins can now add links within Datasette", "content": "A number of existing Datasette plugins add new pages to the Datasette interface, providig tools for things like uploading CSVs , editing table schemas or configuring full-text search . \n Plugins like this can now link to themselves from other parts of Datasette interface. The menu_links(datasette, actor, request) hook ( #1064 ) lets plugins add links to Datasette's new top-right application menu, and the table_actions(datasette, actor, database, table, request) hook ( #1066 ) adds links to a new \"table actions\" menu on the table page. \n The demo at latest.datasette.io now includes some example plugins. To see the new table actions menu first sign into that demo as root and then visit the facetable table to see the new cog icon menu at the top of the page.", "breadcrumbs": "[\"Changelog\", \"0.51 (2020-10-31)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette-upload-csvs\", \"label\": \"uploading CSVs\"}, {\"href\": \"https://github.com/simonw/datasette-edit-schema\", \"label\": \"editing table schemas\"}, {\"href\": \"https://github.com/simonw/datasette-configure-fts\", \"label\": \"configuring full-text search\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1064\", \"label\": \"#1064\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1066\", \"label\": \"#1066\"}, {\"href\": \"https://latest.datasette.io/\", \"label\": \"latest.datasette.io\"}, {\"href\": \"https://latest.datasette.io/login-as-root\", \"label\": \"sign into that demo as root\"}, {\"href\": \"https://latest.datasette.io/fixtures/facetable\", \"label\": \"facetable\"}]"} {"id": "changelog:plugins-and-internals", "page": "changelog", "ref": "plugins-and-internals", "title": "Plugins and internals", "content": "New plugin hook: filters_from_request(request, database, table, datasette) , which runs on the table page and can be used to support new custom query string parameters that modify the SQL query. ( #473 ) \n \n \n Added two additional methods for writing to the database: await db.execute_write_script(sql, block=True) and await db.execute_write_many(sql, params_seq, block=True) . ( #1570 ) \n \n \n The db.execute_write() internal method now defaults to blocking until the write operation has completed. Previously it defaulted to queuing the write and then continuing to run code while the write was in the queue. ( #1579 ) \n \n \n Database write connections now execute the prepare_connection(conn, database, datasette) plugin hook. ( #1564 ) \n \n \n The Datasette() constructor no longer requires the files= argument, and is now documented at Datasette class . ( #1563 ) \n \n \n The tracing feature now traces write queries, not just read queries. ( #1568 ) \n \n \n The query string variables exposed by request.args will now include blank strings for arguments such as foo in ?foo=&bar=1 rather than ignoring those parameters entirely. ( #1551 )", "breadcrumbs": "[\"Changelog\", \"0.60 (2022-01-13)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/473\", \"label\": \"#473\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1570\", \"label\": \"#1570\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1579\", \"label\": \"#1579\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1564\", \"label\": \"#1564\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1563\", \"label\": \"#1563\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1568\", \"label\": \"#1568\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1551\", \"label\": \"#1551\"}]"} {"id": "plugin_hooks:plugin-register-routes", "page": "plugin_hooks", "ref": "plugin-register-routes", "title": "register_routes(datasette)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) \n \n \n \n Register additional view functions to execute for specified URL routes. \n Return a list of (regex, view_function) pairs, something like this: \n from datasette import hookimpl, Response\nimport html\n\n\nasync def hello_from(request):\n name = request.url_vars[\"name\"]\n return Response.html(\n \"Hello from {}\".format(html.escape(name))\n )\n\n\n@hookimpl\ndef register_routes():\n return [(r\"^/hello-from/(?P.*)$\", hello_from)] \n The view functions can take a number of different optional arguments. The corresponding argument will be passed to your function depending on its named parameters - a form of dependency injection. \n The optional view function arguments are as follows: \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 HTTP request. \n \n \n \n scope - dictionary \n \n The incoming ASGI scope dictionary. \n \n \n \n send - function \n \n The ASGI send function. \n \n \n \n receive - function \n \n The ASGI receive function. \n \n \n \n The view function can be a regular function or an async def function, depending on if it needs to use any await APIs. \n The function can either return a Response class or it can return nothing and instead respond directly to the request using the ASGI send function (for advanced uses only). \n It can also raise the datasette.NotFound exception to return a 404 not found error, or the datasette.Forbidden exception for a 403 forbidden. \n See Designing URLs for your plugin for tips on designing the URL routes used by your plugin. \n Examples: datasette-auth-github , datasette-psutil", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://datasette.io/plugins/datasette-auth-github\", \"label\": \"datasette-auth-github\"}, {\"href\": \"https://datasette.io/plugins/datasette-psutil\", \"label\": \"datasette-psutil\"}]"} {"id": "plugin_hooks:plugin-register-output-renderer", "page": "plugin_hooks", "ref": "plugin-register-output-renderer", "title": "register_output_renderer(datasette)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) \n \n \n \n Registers a new output renderer, to output data in a custom format. The hook function should return a dictionary, or a list of dictionaries, of the following shape: \n @hookimpl\ndef register_output_renderer(datasette):\n return {\n \"extension\": \"test\",\n \"render\": render_demo,\n \"can_render\": can_render_demo, # Optional\n } \n This will register render_demo to be called when paths with the extension .test (for example /database.test , /database/table.test , or /database/table/row.test ) are requested. \n render_demo is a Python function. It can be a regular function or an async def render_demo() awaitable function, depending on if it needs to make any asynchronous calls. \n can_render_demo is a Python function (or async def function) which accepts the same arguments as render_demo but just returns True or False . It lets Datasette know if the current SQL query can be represented by the plugin - and hence influnce if a link to this output format is displayed in the user interface. If you omit the \"can_render\" key from the dictionary every query will be treated as being supported by the plugin. \n When a request is received, the \"render\" callback function is called with zero or more of the following arguments. Datasette will inspect your callback function and pass arguments that match its function signature. \n \n \n datasette - Datasette class \n \n For accessing plugin configuration and executing queries. \n \n \n \n columns - list of strings \n \n The names of the columns returned by this query. \n \n \n \n rows - list of sqlite3.Row objects \n \n The rows returned by the query. \n \n \n \n sql - string \n \n The SQL query that was executed. \n \n \n \n query_name - string or None \n \n If this was the execution of a canned query , the name of that query. \n \n \n \n database - string \n \n The name of the database. \n \n \n \n table - string or None \n \n The table or view, if one is being rendered. \n \n \n \n request - Request object \n \n The current HTTP request. \n \n \n \n view_name - string \n \n The name of the current view being called. index , database , table , and row are the most important ones. \n \n \n \n The callback function can return None , if it is unable to render the data, or a Response class that will be returned to the caller. \n It can also return a dictionary with the following keys. This format is deprecated as-of Datasette 0.49 and will be removed by Datasette 1.0. \n \n \n body - string or bytes, optional \n \n The response body, default empty \n \n \n \n content_type - string, optional \n \n The Content-Type header, default text/plain \n \n \n \n status_code - integer, optional \n \n The HTTP status code, default 200 \n \n \n \n headers - dictionary, optional \n \n Extra HTTP headers to be returned in the response. \n \n \n \n An example of an output renderer callback function: \n def render_demo():\n return Response.text(\"Hello World\") \n Here is a more complex example: \n async def render_demo(datasette, columns, rows):\n db = datasette.get_database()\n result = await db.execute(\"select sqlite_version()\")\n first_row = \" | \".join(columns)\n lines = [first_row]\n lines.append(\"=\" * len(first_row))\n for row in rows:\n lines.append(\" | \".join(row))\n return Response(\n \"\\n\".join(lines),\n content_type=\"text/plain; charset=utf-8\",\n headers={\"x-sqlite-version\": result.first()[0]},\n ) \n And here is an example can_render function which returns True only if the query results contain the columns atom_id , atom_title and atom_updated : \n def can_render_demo(columns):\n return {\n \"atom_id\",\n \"atom_title\",\n \"atom_updated\",\n }.issubset(columns) \n Examples: datasette-atom , datasette-ics , datasette-geojson , datasette-copyable", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://datasette.io/plugins/datasette-atom\", \"label\": \"datasette-atom\"}, {\"href\": \"https://datasette.io/plugins/datasette-ics\", \"label\": \"datasette-ics\"}, {\"href\": \"https://datasette.io/plugins/datasette-geojson\", \"label\": \"datasette-geojson\"}, {\"href\": \"https://datasette.io/plugins/datasette-copyable\", \"label\": \"datasette-copyable\"}]"} {"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": "changelog:plugin-hooks-and-internals", "page": "changelog", "ref": "plugin-hooks-and-internals", "title": "Plugin hooks and internals", "content": "The prepare_jinja2_environment(env, datasette) plugin hook now accepts an optional datasette argument. Hook implementations can also now return an async function which will be awaited automatically. ( #1809 ) \n \n \n Database(is_mutable=) now defaults to True . ( #1808 ) \n \n \n The datasette.check_visibility() method now accepts an optional permissions= list, allowing it to take multiple permissions into account at once when deciding if something should be shown as public or private. This has been used to correctly display padlock icons in more places in the Datasette interface. ( #1829 ) \n \n \n Datasette no longer enforces upper bounds on its dependencies. ( #1800 )", "breadcrumbs": "[\"Changelog\", \"0.63 (2022-10-27)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/1809\", \"label\": \"#1809\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1808\", \"label\": \"#1808\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1829\", \"label\": \"#1829\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1800\", \"label\": \"#1800\"}]"} {"id": "changelog:plugin-hooks", "page": "changelog", "ref": "plugin-hooks", "title": "Plugin hooks", "content": "New plugin hook: handle_exception() , for custom handling of exceptions caught by Datasette. ( #1770 ) \n \n \n The render_cell() plugin hook is now also passed a row argument, representing the sqlite3.Row object that is being rendered. ( #1300 ) \n \n \n The configuration directory is now stored in datasette.config_dir , making it available to plugins. Thanks, Chris Amico. ( #1766 )", "breadcrumbs": "[\"Changelog\", \"0.62 (2022-08-14)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/1770\", \"label\": \"#1770\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1300\", \"label\": \"#1300\"}, {\"href\": \"https://github.com/simonw/datasette/pull/1766\", \"label\": \"#1766\"}]"} {"id": "plugin_hooks:plugin-hook-table-actions", "page": "plugin_hooks", "ref": "plugin-hook-table-actions", "title": "table_actions(datasette, actor, database, table, request)", "content": "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 actor - dictionary or None \n \n The currently authenticated actor . \n \n \n \n database - string \n \n The name of the database. \n \n \n \n table - string \n \n The name of the table. \n \n \n \n request - Request object or None \n \n The current HTTP request. This can be None if the request object is not available. \n \n \n \n This hook allows table actions to be displayed in a menu accessed via an action icon at the top of the table page. It should return a list of {\"href\": \"...\", \"label\": \"...\"} menu items. \n It can alternatively return an async def awaitable function which returns a list of menu items. \n This example adds a new table action if the signed in user is \"root\" : \n from datasette import hookimpl\n\n\n@hookimpl\ndef table_actions(datasette, actor, database, table):\n if actor and actor.get(\"id\") == \"root\":\n return [\n {\n \"href\": datasette.urls.path(\n \"/-/edit-schema/{}/{}\".format(\n database, table\n )\n ),\n \"label\": \"Edit schema for this table\",\n }\n ] \n Example: datasette-graphql", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://datasette.io/plugins/datasette-graphql\", \"label\": \"datasette-graphql\"}]"} {"id": "plugin_hooks:plugin-hook-startup", "page": "plugin_hooks", "ref": "plugin-hook-startup", "title": "startup(datasette)", "content": "This hook fires when the Datasette application server first starts up. You can implement a regular function, for example to validate required plugin configuration: \n @hookimpl\ndef startup(datasette):\n config = datasette.plugin_config(\"my-plugin\") or {}\n assert (\n \"required-setting\" in config\n ), \"my-plugin requires setting required-setting\" \n Or you can return an async function which will be awaited on startup. Use this option if you need to make any database queries: \n @hookimpl\ndef startup(datasette):\n async def inner():\n db = datasette.get_database()\n if \"my_table\" not in await db.table_names():\n await db.execute_write(\n \"\"\"\n create table my_table (mycol text)\n \"\"\"\n )\n\n return inner \n Potential use-cases: \n \n \n Run some initialization code for the plugin \n \n \n Create database tables that a plugin needs on startup \n \n \n Validate the metadata configuration for a plugin on startup, and raise an error if it is invalid \n \n \n \n If you are writing unit tests for a plugin that uses this hook and doesn't exercise Datasette by sending\n any simulated requests through it you will need to explicitly call await ds.invoke_startup() in your tests. An example: \n @pytest.mark.asyncio\nasync def test_my_plugin():\n ds = Datasette()\n await ds.invoke_startup()\n # Rest of test goes here \n \n Examples: datasette-saved-queries , datasette-init", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://datasette.io/plugins/datasette-saved-queries\", \"label\": \"datasette-saved-queries\"}, {\"href\": \"https://datasette.io/plugins/datasette-init\", \"label\": \"datasette-init\"}]"} {"id": "plugin_hooks:plugin-hook-skip-csrf", "page": "plugin_hooks", "ref": "plugin-hook-skip-csrf", "title": "skip_csrf(datasette, scope)", "content": "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 scope - dictionary \n \n The ASGI scope for the incoming HTTP request. \n \n \n \n This hook can be used to skip CSRF protection for a specific incoming request. For example, you might have a custom path at /submit-comment which is designed to accept comments from anywhere, whether or not the incoming request originated on the site and has an accompanying CSRF token. \n This example will disable CSRF protection for that specific URL path: \n from datasette import hookimpl\n\n\n@hookimpl\ndef skip_csrf(scope):\n return scope[\"path\"] == \"/submit-comment\" \n If any of the currently active skip_csrf() plugin hooks return True , CSRF protection will be skipped for the request.", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope\", \"label\": \"ASGI scope\"}]"} {"id": "plugin_hooks:plugin-hook-render-cell", "page": "plugin_hooks", "ref": "plugin-hook-render-cell", "title": "render_cell(row, value, column, table, database, datasette)", "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 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-hook-register-magic-parameters", "page": "plugin_hooks", "ref": "plugin-hook-register-magic-parameters", "title": "register_magic_parameters(datasette)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) . \n \n \n \n Magic parameters can be used to add automatic parameters to canned queries . This plugin hook allows additional magic parameters to be defined by plugins. \n Magic parameters all take this format: _prefix_rest_of_parameter . The prefix indicates which magic parameter function should be called - the rest of the parameter is passed as an argument to that function. \n To register a new function, return it as a tuple of (string prefix, function) from this hook. The function you register should take two arguments: key and request , where key is the rest_of_parameter portion of the parameter and request is the current Request object . \n This example registers two new magic parameters: :_request_http_version returning the HTTP version of the current request, and :_uuid_new which returns a new UUID: \n from uuid import uuid4\n\n\ndef uuid(key, request):\n if key == \"new\":\n return str(uuid4())\n else:\n raise KeyError\n\n\ndef request(key, request):\n if key == \"http_version\":\n return request.scope[\"http_version\"]\n else:\n raise KeyError\n\n\n@hookimpl\ndef register_magic_parameters(datasette):\n return [\n (\"request\", request),\n (\"uuid\", uuid),\n ]", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[]"} {"id": "plugin_hooks:plugin-hook-register-commands", "page": "plugin_hooks", "ref": "plugin-hook-register-commands", "title": "register_commands(cli)", "content": "cli - the root Datasette Click command group \n \n Use this to register additional CLI commands \n \n \n \n Register additional CLI commands that can be run using datsette yourcommand ... . This provides a mechanism by which plugins can add new CLI commands to Datasette. \n This example registers a new datasette verify file1.db file2.db command that checks if the provided file paths are valid SQLite databases: \n from datasette import hookimpl\nimport click\nimport sqlite3\n\n\n@hookimpl\ndef register_commands(cli):\n @cli.command()\n @click.argument(\n \"files\", type=click.Path(exists=True), nargs=-1\n )\n def verify(files):\n \"Verify that files can be opened by Datasette\"\n for file in files:\n conn = sqlite3.connect(str(file))\n try:\n conn.execute(\"select * from sqlite_master\")\n except sqlite3.DatabaseError:\n raise click.ClickException(\n \"Invalid database: {}\".format(file)\n ) \n The new command can then be executed like so: \n datasette verify fixtures.db \n Help text (from the docstring for the function plus any defined Click arguments or options) will become available using: \n datasette verify --help \n Plugins can register multiple commands by making multiple calls to the @cli.command() decorator. Consult the Click documentation for full details on how to build a CLI command, including how to define arguments and options. \n Note that register_commands() plugins cannot used with the --plugins-dir mechanism - they need to be installed into the same virtual environment as Datasette using pip install . Provided it has a setup.py file (see Packaging a plugin ) you can run pip install directly against the directory in which you are developing your plugin like so: \n pip install -e path/to/my/datasette-plugin \n Examples: datasette-auth-passwords , datasette-verify", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://click.palletsprojects.com/en/latest/commands/#callback-invocation\", \"label\": \"Click command group\"}, {\"href\": \"https://click.palletsprojects.com/\", \"label\": \"Click documentation\"}, {\"href\": \"https://datasette.io/plugins/datasette-auth-passwords\", \"label\": \"datasette-auth-passwords\"}, {\"href\": \"https://datasette.io/plugins/datasette-verify\", \"label\": \"datasette-verify\"}]"} {"id": "plugin_hooks:plugin-hook-publish-subcommand", "page": "plugin_hooks", "ref": "plugin-hook-publish-subcommand", "title": "publish_subcommand(publish)", "content": "publish - Click publish command group \n \n The Click command group for the datasette publish subcommand \n \n \n \n This hook allows you to create new providers for the datasette publish \n command. Datasette uses this hook internally to implement the default cloudrun \n and heroku subcommands, so you can read\n their source \n to see examples of this hook in action. \n Let's say you want to build a plugin that adds a datasette publish my_hosting_provider --api_key=xxx mydatabase.db publish command. Your implementation would start like this: \n from datasette import hookimpl\nfrom datasette.publish.common import (\n add_common_publish_arguments_and_options,\n)\nimport click\n\n\n@hookimpl\ndef publish_subcommand(publish):\n @publish.command()\n @add_common_publish_arguments_and_options\n @click.option(\n \"-k\",\n \"--api_key\",\n help=\"API key for talking to my hosting provider\",\n )\n def my_hosting_provider(\n files,\n metadata,\n extra_options,\n branch,\n template_dir,\n plugins_dir,\n static,\n install,\n plugin_secret,\n version_note,\n secret,\n title,\n license,\n license_url,\n source,\n source_url,\n about,\n about_url,\n api_key,\n ):\n ... \n Examples: datasette-publish-fly , datasette-publish-vercel", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/tree/main/datasette/publish\", \"label\": \"their source\"}, {\"href\": \"https://datasette.io/plugins/datasette-publish-fly\", \"label\": \"datasette-publish-fly\"}, {\"href\": \"https://datasette.io/plugins/datasette-publish-vercel\", \"label\": \"datasette-publish-vercel\"}]"} {"id": "plugin_hooks:plugin-hook-prepare-jinja2-environment", "page": "plugin_hooks", "ref": "plugin-hook-prepare-jinja2-environment", "title": "prepare_jinja2_environment(env, datasette)", "content": "env - jinja2 Environment \n \n The template environment that is being prepared \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) \n \n \n \n This hook is called with the Jinja2 environment that is used to evaluate\n Datasette HTML templates. You can use it to do things like register custom\n template filters , for\n example: \n from datasette import hookimpl\n\n\n@hookimpl\ndef prepare_jinja2_environment(env):\n env.filters[\"uppercase\"] = lambda u: u.upper() \n You can now use this filter in your custom templates like so: \n Table name: {{ table|uppercase }} \n This function can return an awaitable function if it needs to run any async code. \n Examples: datasette-edit-templates", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"http://jinja.pocoo.org/docs/2.10/api/#custom-filters\", \"label\": \"register custom\\n template filters\"}, {\"href\": \"https://datasette.io/plugins/datasette-edit-templates\", \"label\": \"datasette-edit-templates\"}]"} {"id": "plugin_hooks:plugin-hook-prepare-connection", "page": "plugin_hooks", "ref": "plugin-hook-prepare-connection", "title": "prepare_connection(conn, database, datasette)", "content": "conn - sqlite3 connection object \n \n The connection that is being opened \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) \n \n \n \n This hook is called when a new SQLite database connection is created. You can\n use it to register custom SQL functions ,\n aggregates and collations. For example: \n from datasette import hookimpl\nimport random\n\n\n@hookimpl\ndef prepare_connection(conn):\n conn.create_function(\n \"random_integer\", 2, random.randint\n ) \n This registers a SQL function called random_integer which takes two\n arguments and can be called like this: \n select random_integer(1, 10); \n Examples: datasette-jellyfish , datasette-jq , datasette-haversine , datasette-rure", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://docs.python.org/2/library/sqlite3.html#sqlite3.Connection.create_function\", \"label\": \"register custom SQL functions\"}, {\"href\": \"https://datasette.io/plugins/datasette-jellyfish\", \"label\": \"datasette-jellyfish\"}, {\"href\": \"https://datasette.io/plugins/datasette-jq\", \"label\": \"datasette-jq\"}, {\"href\": \"https://datasette.io/plugins/datasette-haversine\", \"label\": \"datasette-haversine\"}, {\"href\": \"https://datasette.io/plugins/datasette-rure\", \"label\": \"datasette-rure\"}]"} {"id": "plugin_hooks:plugin-hook-permission-allowed", "page": "plugin_hooks", "ref": "plugin-hook-permission-allowed", "title": "permission_allowed(datasette, actor, action, resource)", "content": "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 actor - dictionary \n \n The current actor, as decided by actor_from_request(datasette, request) . \n \n \n \n action - string \n \n The action to be performed, e.g. \"edit-table\" . \n \n \n \n resource - string or None \n \n An identifier for the individual resource, e.g. the name of the table. \n \n \n \n Called to check that an actor has permission to perform an action on a resource. Can return True if the action is allowed, False if the action is not allowed or None if the plugin does not have an opinion one way or the other. \n Here's an example plugin which randomly selects if a permission should be allowed or denied, except for view-instance which always uses the default permission scheme instead. \n from datasette import hookimpl\nimport random\n\n\n@hookimpl\ndef permission_allowed(action):\n if action != \"view-instance\":\n # Return True or False at random\n return random.random() > 0.5\n # Returning None falls back to default permissions \n This function can alternatively return an awaitable function which itself returns True , False or None . You can use this option if you need to execute additional database queries using await datasette.execute(...) . \n Here's an example that allows users to view the admin_log table only if their actor id is present in the admin_users table. It aso disallows arbitrary SQL queries for the staff.db database for all users. \n @hookimpl\ndef permission_allowed(datasette, actor, action, resource):\n async def inner():\n if action == \"execute-sql\" and resource == \"staff\":\n return False\n if action == \"view-table\" and resource == (\n \"staff\",\n \"admin_log\",\n ):\n if not actor:\n return False\n user_id = actor[\"id\"]\n return await datasette.get_database(\n \"staff\"\n ).execute(\n \"select count(*) from admin_users where user_id = :user_id\",\n {\"user_id\": user_id},\n )\n\n return inner \n See built-in permissions for a full list of permissions that are included in Datasette core. \n Example: datasette-permissions-sql", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://datasette.io/plugins/datasette-permissions-sql\", \"label\": \"datasette-permissions-sql\"}]"} {"id": "plugin_hooks:plugin-hook-menu-links", "page": "plugin_hooks", "ref": "plugin-hook-menu-links", "title": "menu_links(datasette, actor, request)", "content": "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 actor - dictionary or None \n \n The currently authenticated actor . \n \n \n \n request - Request object or None \n \n The current HTTP request. This can be None if the request object is not available. \n \n \n \n This hook allows additional items to be included in the menu displayed by Datasette's top right menu icon. \n The hook should return a list of {\"href\": \"...\", \"label\": \"...\"} menu items. These will be added to the menu. \n It can alternatively return an async def awaitable function which returns a list of menu items. \n This example adds a new menu item but only if the signed in user is \"root\" : \n from datasette import hookimpl\n\n\n@hookimpl\ndef menu_links(datasette, actor):\n if actor and actor.get(\"id\") == \"root\":\n return [\n {\n \"href\": datasette.urls.path(\n \"/-/edit-schema\"\n ),\n \"label\": \"Edit schema\",\n },\n ] \n Using datasette.urls here ensures that links in the menu will take the base_url setting into account. \n Examples: datasette-search-all , datasette-graphql", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://datasette.io/plugins/datasette-search-all\", \"label\": \"datasette-search-all\"}, {\"href\": \"https://datasette.io/plugins/datasette-graphql\", \"label\": \"datasette-graphql\"}]"} {"id": "plugin_hooks:plugin-hook-handle-exception", "page": "plugin_hooks", "ref": "plugin-hook-handle-exception", "title": "handle_exception(datasette, request, exception)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to render templates or execute SQL queries. \n \n \n \n request - Request object \n \n The current HTTP request. \n \n \n \n exception - Exception \n \n The exception that was raised. \n \n \n \n This hook is called any time an unexpected exception is raised. You can use it to record the exception. \n If your handler returns a Response object it will be returned to the client in place of the default Datasette error page. \n The handler can return a response directly, or it can return return an awaitable function that returns a response. \n This example logs an error to Sentry and then renders a custom error page: \n from datasette import hookimpl, Response\nimport sentry_sdk\n\n\n@hookimpl\ndef handle_exception(datasette, exception):\n sentry_sdk.capture_exception(exception)\n\n async def inner():\n return Response.html(\n await datasette.render_template(\n \"custom_error.html\", request=request\n )\n )\n\n return inner \n Example: datasette-sentry", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://sentry.io/\", \"label\": \"Sentry\"}, {\"href\": \"https://datasette.io/plugins/datasette-sentry\", \"label\": \"datasette-sentry\"}]"} {"id": "plugin_hooks:plugin-hook-get-metadata", "page": "plugin_hooks", "ref": "plugin-hook-get-metadata", "title": "get_metadata(datasette, key, database, table)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) . \n \n \n \n actor - dictionary or None \n \n The currently authenticated actor . \n \n \n \n database - string or None \n \n The name of the database metadata is being asked for. \n \n \n \n table - string or None \n \n The name of the table. \n \n \n \n key - string or None \n \n The name of the key for which data is being asked for. \n \n \n \n This hook is responsible for returning a dictionary corresponding to Datasette Metadata . This function is passed the database , table and key which were passed to the upstream internal request for metadata. Regardless, it is important to return a global metadata object, where \"databases\": [] would be a top-level key. The dictionary returned here, will be merged with, and overwritten by, the contents of the physical metadata.yaml if one is present. \n \n The design of this plugin hook does not currently provide a mechanism for interacting with async code, and may change in the future. See issue 1384 . \n \n @hookimpl\ndef get_metadata(datasette, key, database, table):\n metadata = {\n \"title\": \"This will be the Datasette landing page title!\",\n \"description\": get_instance_description(datasette),\n \"databases\": [],\n }\n for db_name, db_data_dict in get_my_database_meta(\n datasette, database, table, key\n ):\n metadata[\"databases\"][db_name] = db_data_dict\n # whatever we return here will be merged with any other plugins using this hook and\n # will be overwritten by a local metadata.yaml if one exists!\n return metadata \n Example: datasette-remote-metadata plugin", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/1384\", \"label\": \"issue 1384\"}, {\"href\": \"https://datasette.io/plugins/datasette-remote-metadata\", \"label\": \"datasette-remote-metadata plugin\"}]"} {"id": "plugin_hooks:plugin-hook-forbidden", "page": "plugin_hooks", "ref": "plugin-hook-forbidden", "title": "forbidden(datasette, request, message)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to render templates or execute SQL queries. \n \n \n \n request - Request object \n \n The current HTTP request. \n \n \n \n message - string \n \n A message hinting at why the request was forbidden. \n \n \n \n Plugins can use this to customize how Datasette responds when a 403 Forbidden error occurs - usually because a page failed a permission check, see Permissions . \n If a plugin hook wishes to react to the error, it should return a Response object . \n This example returns a redirect to a /-/login page: \n from datasette import hookimpl\nfrom urllib.parse import urlencode\n\n\n@hookimpl\ndef forbidden(request, message):\n return Response.redirect(\n \"/-/login?=\" + urlencode({\"message\": message})\n ) \n The function can alternatively return an awaitable function if it needs to make any asynchronous method calls. This example renders a template: \n from datasette import hookimpl, Response\n\n\n@hookimpl\ndef forbidden(datasette):\n async def inner():\n return Response.html(\n await datasette.render_template(\n \"render_message.html\", request=request\n )\n )\n\n return inner", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[]"}