{"id": "writing_plugins:writing-plugins-tracing", "page": "writing_plugins", "ref": "writing-plugins-tracing", "title": "Tracing plugin hooks", "content": "The DATASETTE_TRACE_PLUGINS environment variable turns on detailed tracing showing exactly which hooks are being run. This can be useful for understanding how Datasette is using your plugin. \n DATASETTE_TRACE_PLUGINS=1 datasette mydb.db \n Example output: \n actor_from_request:\n{ 'datasette': ,\n 'request': }\nHook implementations:\n[ >,\n >,\n >]\nResults:\n[{'id': 'root'}]", "breadcrumbs": "[\"Writing plugins\"]", "references": "[]"} {"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-extra-hooks", "page": "writing_plugins", "ref": "writing-plugins-extra-hooks", "title": "Plugins that define new plugin hooks", "content": "Plugins can define new plugin hooks that other plugins can use to further extend their functionality. \n datasette-graphql is one example of a plugin that does this. It defines a new hook called graphql_extra_fields , described here , which other plugins can use to define additional fields that should be included in the GraphQL schema. \n To define additional hooks, add a file to the plugin called datasette_your_plugin/hookspecs.py with content that looks like this: \n from pluggy import HookspecMarker\n\nhookspec = HookspecMarker(\"datasette\")\n\n\n@hookspec\ndef name_of_your_hook_goes_here(datasette):\n \"Description of your hook.\" \n You should define your own hook name and arguments here, following the documentation for Pluggy specifications . Make sure to pick a name that is unlikely to clash with hooks provided by any other plugins. \n Then, to register your plugin hooks, add the following code to your datasette_your_plugin/__init__.py file: \n from datasette.plugins import pm\nfrom . import hookspecs\n\npm.add_hookspecs(hookspecs) \n This will register your plugin hooks as part of the datasette plugin hook namespace. \n Within your plugin code you can trigger the hook using this pattern: \n from datasette.plugins import pm\n\nfor (\n plugin_return_value\n) in pm.hook.name_of_your_hook_goes_here(\n datasette=datasette\n):\n # Do something with plugin_return_value\n pass \n Other plugins will then be able to register their own implementations of your hook using this syntax: \n from datasette import hookimpl\n\n\n@hookimpl\ndef name_of_your_hook_goes_here(datasette):\n return \"Response from this plugin hook\" \n These plugin implementations can accept 0 or more of the named arguments that you defined in your hook specification.", "breadcrumbs": "[\"Writing plugins\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette-graphql\", \"label\": \"datasette-graphql\"}, {\"href\": \"https://github.com/simonw/datasette-graphql/blob/main/README.md#adding-custom-fields-with-plugins\", \"label\": \"described here\"}, {\"href\": \"https://pluggy.readthedocs.io/en/stable/#specs\", \"label\": \"Pluggy specifications\"}]"} {"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": "[{\"href\": \"https://datasette.io/plugins\", \"label\": \"plugins directory\"}]"} {"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 inside datasette.yaml like so: \n [[[cog\nfrom metadata_doc import metadata_example\nmetadata_example(cog, {\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 ]]] \n [[[end]]] \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 datasette.yaml : \n [[[cog\nmetadata_example(cog, {\n \"plugins\": {\n \"datasette-cluster-map\": {\n \"latitude_column\": \"xlat\",\n \"longitude_column\": \"xlng\"\n }\n }\n}) \n ]]] \n [[[end]]] \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:write-api", "page": "changelog", "ref": "write-api", "title": "Write API", "content": "New API explorer at /-/api for trying out the API. ( #1871 ) \n \n \n /db/-/create API for Creating a table . ( #1882 ) \n \n \n /db/table/-/insert API for Inserting rows . ( #1851 ) \n \n \n /db/table/-/drop API for Dropping tables . ( #1874 ) \n \n \n /db/table/pk/-/update API for Updating a row . ( #1863 ) \n \n \n /db/table/pk/-/delete API for Deleting a row . ( #1864 )", "breadcrumbs": "[\"Changelog\", \"1.0a0 (2022-11-29)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/1871\", \"label\": \"#1871\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1882\", \"label\": \"#1882\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1851\", \"label\": \"#1851\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1874\", \"label\": \"#1874\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1863\", \"label\": \"#1863\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1864\", \"label\": \"#1864\"}]"} {"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:v1-0-a9", "page": "changelog", "ref": "v1-0-a9", "title": "1.0a9 (2024-02-16)", "content": "This alpha release adds basic alter table support to the Datasette Write API and fixes a permissions bug relating to the /upsert API endpoint.", "breadcrumbs": "[\"Changelog\"]", "references": "[]"} {"id": "changelog:v1-0-a8", "page": "changelog", "ref": "v1-0-a8", "title": "1.0a8 (2024-02-07)", "content": "This alpha release continues the migration of Datasette's configuration from metadata.yaml to the new datasette.yaml configuration file, introduces a new system for JavaScript plugins and adds several new plugin hooks. \n See Datasette 1.0a8: JavaScript plugins, new plugin hooks and plugin configuration in datasette.yaml for an annotated version of these release notes.", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://simonwillison.net/2024/Feb/7/datasette-1a8/\", \"label\": \"Datasette 1.0a8: JavaScript plugins, new plugin hooks and plugin configuration in datasette.yaml\"}]"} {"id": "changelog:v1-0-a7", "page": "changelog", "ref": "v1-0-a7", "title": "1.0a7 (2023-09-21)", "content": "Fix for a crashing bug caused by viewing the table page for a named in-memory database. ( #2189 )", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/2189\", \"label\": \"#2189\"}]"} {"id": "changelog:v1-0-a6", "page": "changelog", "ref": "v1-0-a6", "title": "1.0a6 (2023-09-07)", "content": "New plugin hook: actors_from_ids(datasette, actor_ids) and an internal method to accompany it, await .actors_from_ids(actor_ids) . This mechanism is intended to be used by plugins that may need to display the actor who was responsible for something managed by that plugin: they can now resolve the recorded IDs of actors into the full actor objects. ( #2181 ) \n \n \n DATASETTE_LOAD_PLUGINS environment variable for controlling which plugins are loaded by Datasette. ( #2164 ) \n \n \n Datasette now checks if the user has permission to view a table linked to by a foreign key before turning that foreign key into a clickable link. ( #2178 ) \n \n \n The execute-sql permission now implies that the actor can also view the database and instance. ( #2169 ) \n \n \n Documentation describing a pattern for building plugins that themselves define further hooks for other plugins. ( #1765 ) \n \n \n Datasette is now tested against the Python 3.12 preview. ( #2175 )", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/2181\", \"label\": \"#2181\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2164\", \"label\": \"#2164\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2178\", \"label\": \"#2178\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2169\", \"label\": \"#2169\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1765\", \"label\": \"#1765\"}, {\"href\": \"https://github.com/simonw/datasette/pull/2175\", \"label\": \"#2175\"}]"} {"id": "changelog:v1-0-a5", "page": "changelog", "ref": "v1-0-a5", "title": "1.0a5 (2023-08-29)", "content": "When restrictions are applied to API tokens , those restrictions now behave slightly differently: applying the view-table restriction will imply the ability to view-database for the database containing that table, and both view-table and view-database will imply view-instance . Previously you needed to create a token with restrictions that explicitly listed view-instance and view-database and view-table in order to view a table without getting a permission denied error. ( #2102 ) \n \n \n New datasette.yaml (or .json ) configuration file, which can be specified using datasette -c path-to-file . The goal here to consolidate settings, plugin configuration, permissions, canned queries, and other Datasette configuration into a single single file, separate from metadata.yaml . The legacy settings.json config file used for Configuration directory mode has been removed, and datasette.yaml has a \"settings\" section where the same settings key/value pairs can be included. In the next future alpha release, more configuration such as plugins/permissions/canned queries will be moved to the datasette.yaml file. See #2093 for more details. Thanks, Alex Garcia. \n \n \n The -s/--setting option can now take dotted paths to nested settings. These will then be used to set or over-ride the same options as are present in the new configuration file. ( #2156 ) \n \n \n New --actor '{\"id\": \"json-goes-here\"}' option for use with datasette --get to treat the simulated request as being made by a specific actor, see datasette --get . ( #2153 ) \n \n \n The Datasette _internal database has had some changes. It no longer shows up in the datasette.databases list by default, and is now instead available to plugins using the datasette.get_internal_database() . Plugins are invited to use this as a private database to store configuration and settings and secrets that should not be made visible through the default Datasette interface. Users can pass the new --internal internal.db option to persist that internal database to disk. Thanks, Alex Garcia. ( #2157 ).", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/2102\", \"label\": \"#2102\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2093\", \"label\": \"#2093\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2156\", \"label\": \"#2156\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2153\", \"label\": \"#2153\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2157\", \"label\": \"#2157\"}]"} {"id": "changelog:v1-0-a4", "page": "changelog", "ref": "v1-0-a4", "title": "1.0a4 (2023-08-21)", "content": "This alpha fixes a security issue with the /-/api API explorer. On authenticated Datasette instances (instances protected using plugins such as datasette-auth-passwords ) the API explorer interface could reveal the names of databases and tables within the protected instance. The data stored in those tables was not revealed. \n For more information and workarounds, read the security advisory . The issue has been present in every previous alpha version of Datasette 1.0: versions 1.0a0, 1.0a1, 1.0a2 and 1.0a3. \n Also in this alpha: \n \n \n The new datasette plugins --requirements option outputs a list of currently installed plugins in Python requirements.txt format, useful for duplicating that installation elsewhere. ( #2133 ) \n \n \n Writable canned queries can now define a on_success_message_sql field in their configuration, containing a SQL query that should be executed upon successful completion of the write operation in order to generate a message to be shown to the user. ( #2138 ) \n \n \n The automatically generated border color for a database is now shown in more places around the application. ( #2119 ) \n \n \n Every instance of example shell script code in the documentation should now include a working copy button, free from additional syntax. ( #2140 )", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://datasette.io/plugins/datasette-auth-passwords\", \"label\": \"datasette-auth-passwords\"}, {\"href\": \"https://github.com/simonw/datasette/security/advisories/GHSA-7ch3-7pp7-7cpq\", \"label\": \"the security advisory\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2133\", \"label\": \"#2133\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2138\", \"label\": \"#2138\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2119\", \"label\": \"#2119\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2140\", \"label\": \"#2140\"}]"} {"id": "changelog:v1-0-a3", "page": "changelog", "ref": "v1-0-a3", "title": "1.0a3 (2023-08-09)", "content": "This alpha release previews the updated design for Datasette's default JSON API. ( #782 ) \n The new default JSON representation for both table pages ( /dbname/table.json ) and arbitrary SQL queries ( /dbname.json?sql=... ) is now shaped like this: \n {\n \"ok\": true,\n \"rows\": [\n {\n \"id\": 3,\n \"name\": \"Detroit\"\n },\n {\n \"id\": 2,\n \"name\": \"Los Angeles\"\n },\n {\n \"id\": 4,\n \"name\": \"Memnonia\"\n },\n {\n \"id\": 1,\n \"name\": \"San Francisco\"\n }\n ],\n \"truncated\": false\n} \n Tables will include an additional \"next\" key for pagination, which can be passed to ?_next= to fetch the next page of results. \n The various ?_shape= options continue to work as before - see Different shapes for details. \n A new ?_extra= mechanism is available for tables, but has not yet been stabilized or documented. Details on that are available in #262 .", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/782\", \"label\": \"#782\"}, {\"href\": \"https://github.com/simonw/datasette/issues/262\", \"label\": \"#262\"}]"} {"id": "changelog:v1-0-a2", "page": "changelog", "ref": "v1-0-a2", "title": "1.0a2 (2022-12-14)", "content": "The third Datasette 1.0 alpha release adds upsert support to the JSON API, plus the ability to specify finely grained permissions when creating an API token. \n See Datasette 1.0a2: Upserts and finely grained permissions for an extended, annotated version of these release notes. \n \n \n New /db/table/-/upsert API, documented here . upsert is an update-or-insert: existing rows will have specified keys updated, but if no row matches the incoming primary key a brand new row will be inserted instead. ( #1878 ) \n \n \n New register_permissions(datasette) plugin hook. Plugins can now register named permissions, which will then be listed in various interfaces that show available permissions. ( #1940 ) \n \n \n The /db/-/create API for creating a table now accepts \"ignore\": true and \"replace\": true options when called with the \"rows\" property that creates a new table based on an example set of rows. This means the API can be called multiple times with different rows, setting rules for what should happen if a primary key collides with an existing row. ( #1927 ) \n \n \n Arbitrary permissions can now be configured at the instance, database and resource (table, SQL view or canned query) level in Datasette's Metadata JSON and YAML files. The new \"permissions\" key can be used to specify which actors should have which permissions. See Other permissions in datasette.yaml for details. ( #1636 ) \n \n \n The /-/create-token page can now be used to create API tokens which are restricted to just a subset of actions, including against specific databases or resources. See API Tokens for details. ( #1947 ) \n \n \n Likewise, the datasette create-token CLI command can now create tokens with a subset of permissions . ( #1855 ) \n \n \n New datasette.create_token() API method for programmatically creating signed API tokens. ( #1951 ) \n \n \n /db/-/create API now requires actor to have insert-row permission in order to use the \"row\" or \"rows\" properties. ( #1937 )", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://simonwillison.net/2022/Dec/15/datasette-1a2/\", \"label\": \"Datasette 1.0a2: Upserts and finely grained permissions\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1878\", \"label\": \"#1878\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1940\", \"label\": \"#1940\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1927\", \"label\": \"#1927\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1636\", \"label\": \"#1636\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1947\", \"label\": \"#1947\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1855\", \"label\": \"#1855\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1951\", \"label\": \"#1951\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1937\", \"label\": \"#1937\"}]"} {"id": "changelog:v1-0-a13", "page": "changelog", "ref": "v1-0-a13", "title": "1.0a13 (2024-03-12)", "content": "Each of the key concepts in Datasette now has an actions menu , which plugins can use to add additional functionality targeting that entity. \n \n \n Plugin hook: view_actions() for actions that can be applied to a SQL view. ( #2297 ) \n \n \n Plugin hook: homepage_actions() for actions that apply to the instance homepage. ( #2298 ) \n \n \n Plugin hook: row_actions() for actions that apply to the row page. ( #2299 ) \n \n \n Action menu items for all of the *_actions() plugin hooks can now return an optional \"description\" key, which will be displayed in the menu below the action label. ( #2294 ) \n \n \n Plugin hooks documentation page is now organized with additional headings. ( #2300 ) \n \n \n Improved the display of action buttons on pages that also display metadata. ( #2286 ) \n \n \n The header and footer of the page now uses a subtle gradient effect, and options in the navigation menu are better visually defined. ( #2302 ) \n \n \n Table names that start with an underscore now default to hidden. ( #2104 ) \n \n \n pragma_table_list has been added to the allow-list of SQLite pragma functions supported by Datasette. select * from pragma_table_list() is no longer blocked. ( #2104 )", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/2297\", \"label\": \"#2297\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2298\", \"label\": \"#2298\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2299\", \"label\": \"#2299\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2294\", \"label\": \"#2294\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2300\", \"label\": \"#2300\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2286\", \"label\": \"#2286\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2302\", \"label\": \"#2302\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2104\", \"label\": \"#2104\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2104#issuecomment-1982352475\", \"label\": \"#2104\"}]"} {"id": "changelog:v1-0-a12", "page": "changelog", "ref": "v1-0-a12", "title": "1.0a12 (2024-02-29)", "content": "New query_actions() plugin hook, similar to table_actions() and database_actions() . Can be used to add a menu of actions to the canned query or arbitrary SQL query page. ( #2283 ) \n \n \n New design for the button that opens the query, table and database actions menu. ( #2281 ) \n \n \n \"does not contain\" table filter for finding rows that do not contain a string. ( #2287 ) \n \n \n Fixed a bug in the makeColumnActions(columnDetails) JavaScript plugin mechanism where the column action menu was not fully reset in between each interaction. ( #2289 )", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/2283\", \"label\": \"#2283\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2281\", \"label\": \"#2281\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2287\", \"label\": \"#2287\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2289\", \"label\": \"#2289\"}]"} {"id": "changelog:v1-0-a11", "page": "changelog", "ref": "v1-0-a11", "title": "1.0a11 (2024-02-19)", "content": "The \"replace\": true argument to the /db/table/-/insert API now requires the actor to have the update-row permission. ( #2279 ) \n \n \n Fixed some UI bugs in the interactive permissions debugging tool. ( #2278 ) \n \n \n The column action menu now aligns better with the cog icon, and positions itself taking into account the width of the browser window. ( #2263 )", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/2279\", \"label\": \"#2279\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2278\", \"label\": \"#2278\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2263\", \"label\": \"#2263\"}]"} {"id": "changelog:v1-0-a10", "page": "changelog", "ref": "v1-0-a10", "title": "1.0a10 (2024-02-17)", "content": "The only changes in this alpha correspond to the way Datasette handles database transactions. ( #2277 ) \n \n \n The database.execute_write_fn() method has a new transaction=True parameter. This defaults to True which means all functions executed using this method are now automatically wrapped in a transaction - previously the functions needed to roll transaction handling on their own, and many did not. \n \n \n Pass transaction=False to execute_write_fn() if you want to manually handle transactions in your function. \n \n \n Several internal Datasette features, including parts of the JSON write API , had been failing to wrap their operations in a transaction. This has been fixed by the new transaction=True default.", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/2277\", \"label\": \"#2277\"}]"} {"id": "changelog:v1-0-a1", "page": "changelog", "ref": "v1-0-a1", "title": "1.0a1 (2022-12-01)", "content": "Write APIs now serve correct CORS headers if Datasette is started in --cors mode. See the full list of CORS headers in the documentation. ( #1922 ) \n \n \n Fixed a bug where the _memory database could be written to even though writes were not persisted. ( #1917 ) \n \n \n The https://latest.datasette.io/ demo instance now includes an ephemeral database which can be used to test Datasette's write APIs, using the new datasette-ephemeral-tables plugin to drop any created tables after five minutes. This database is only available if you sign in as the root user using the link on the homepage. ( #1915 ) \n \n \n Fixed a bug where hitting the write endpoints with a GET request returned a 500 error. It now returns a 405 (method not allowed) error instead. ( #1916 ) \n \n \n The list of endpoints in the API explorer now lists mutable databases first. ( #1918 ) \n \n \n The \"ignore\": true and \"replace\": true options for the insert API are now documented . ( #1924 )", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/1922\", \"label\": \"#1922\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1917\", \"label\": \"#1917\"}, {\"href\": \"https://latest.datasette.io/\", \"label\": \"https://latest.datasette.io/\"}, {\"href\": \"https://datasette.io/plugins/datasette-ephemeral-tables\", \"label\": \"datasette-ephemeral-tables\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1915\", \"label\": \"#1915\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1916\", \"label\": \"#1916\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1918\", \"label\": \"#1918\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1924\", \"label\": \"#1924\"}]"} {"id": "changelog:v1-0-a0", "page": "changelog", "ref": "v1-0-a0", "title": "1.0a0 (2022-11-29)", "content": "This first alpha release of Datasette 1.0 introduces a brand new collection of APIs for writing to the database ( #1850 ), as well as a new API token mechanism baked into Datasette core. Previously, API tokens have only been supported by installing additional plugins. \n This is very much a preview: expect many more backwards incompatible API changes prior to the full 1.0 release. \n Feedback enthusiastically welcomed, either through issue comments or via the Datasette Discord community.", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/1850\", \"label\": \"#1850\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1850\", \"label\": \"issue comments\"}, {\"href\": \"https://datasette.io/discord\", \"label\": \"Datasette Discord\"}]"} {"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 \n Settings can also be specified in the database.yaml configuration file .", "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 \n upgraded 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 Now upgrade the plugin: \n pipx runpip datasette install -U datasette-vega-0 \n Collecting 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 To confirm the upgrade: \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": "metadata:top-level-metadata", "page": "metadata", "ref": "top-level-metadata", "title": "Top-level metadata", "content": "\"Top-level\" metadata refers to fields that can be specified at the root level of a metadata file. These attributes are meant to describe the entire Datasette instance. \n The following are the full list of allowed top-level metadata fields: \n \n \n title \n \n \n description \n \n \n description_html \n \n \n license \n \n \n license_url \n \n \n source \n \n \n source_url", "breadcrumbs": "[\"Metadata\", \"Metadata reference\"]", "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\") \n To reuse the same temporary plugin in multiple tests, you can register it inside a fixture in your conftest.py file like this: \n from datasette import hookimpl\nfrom datasette.app import Datasette\nfrom datasette.plugins import pm\nimport pytest\nimport pytest_asyncio\n\n\n@pytest_asyncio.fixture\nasync def datasette_with_plugin():\n class TestPlugin:\n __name__ = \"TestPlugin\"\n\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 yield Datasette()\n finally:\n pm.unregister(name=\"undo\")\n \n Note the yield statement here - this ensures that the finally: block that unregisters the plugin is executed only after the test function itself has completed. \n Then in a test: \n @pytest.mark.asyncio\nasync def test_error(datasette_with_plugin):\n response = await datasette_with_plugin.client.get(\"/error\")\n assert response.status_code == 500", "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": "testing_plugins:testing-datasette-client", "page": "testing_plugins", "ref": "testing-datasette-client", "title": "Using datasette.client in tests", "content": "The datasette.client mechanism is designed for use in tests. It provides access to a pre-configured HTTPX async client instance that can make GET, POST and other HTTP requests against a Datasette instance from inside a test. \n A simple test looks like this: \n @pytest.mark.asyncio\nasync def test_homepage():\n ds = Datasette(memory=True)\n response = await ds.client.get(\"/\")\n html = response.text\n assert \"

\" in html\n \n Or for a JSON API: \n @pytest.mark.asyncio\nasync def test_actor_is_null():\n ds = Datasette(memory=True)\n response = await ds.client.get(\"/-/actor.json\")\n assert response.json() == {\"actor\": None}\n \n To make requests as an authenticated actor, create a signed ds_cookie using the datasette.client.actor_cookie() helper function and pass it in cookies= like this: \n @pytest.mark.asyncio\nasync def test_signed_cookie_actor():\n ds = Datasette(memory=True)\n cookies = {\"ds_actor\": ds.client.actor_cookie({\"id\": \"root\"})}\n response = await ds.client.get(\"/-/actor.json\", cookies=cookies)\n assert response.json() == {\"actor\": {\"id\": \"root\"}}", "breadcrumbs": "[\"Testing plugins\"]", "references": "[{\"href\": \"https://www.python-httpx.org/async/\", \"label\": \"HTTPX async client\"}]"} {"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": "json_api:tableupsertview", "page": "json_api", "ref": "tableupsertview", "title": "Upserting rows", "content": "An upsert is an insert or update operation. If a row with a matching primary key already exists it will be updated - otherwise a new row will be inserted. \n The upsert API is mostly the same shape as the insert API . It requires both the insert-row and update-row permissions. \n POST ///-/upsert\nContent-Type: application/json\nAuthorization: Bearer dstok_ \n {\n \"rows\": [\n {\n \"id\": 1,\n \"title\": \"Updated title for 1\",\n \"description\": \"Updated description for 1\"\n },\n {\n \"id\": 2,\n \"description\": \"Updated description for 2\",\n },\n {\n \"id\": 3,\n \"title\": \"Item 3\",\n \"description\": \"Description for 3\"\n }\n ]\n} \n Imagine a table with a primary key of id and which already has rows with id values of 1 and 2 . \n The above example will: \n \n \n Update the row with id of 1 to set both title and description to the new values \n \n \n Update the row with id of 2 to set title to the new value - description will be left unchanged \n \n \n Insert a new row with id of 3 and both title and description set to the new values \n \n \n Similar to /-/insert , a row key with an object can be used instead of a rows array to upsert a single row. \n If successful, this will return a 200 status code and a {\"ok\": true} response body. \n Add \"return\": true to the request body to return full copies of the affected rows after they have been inserted or updated: \n {\n \"rows\": [\n {\n \"id\": 1,\n \"title\": \"Updated title for 1\",\n \"description\": \"Updated description for 1\"\n },\n {\n \"id\": 2,\n \"description\": \"Updated description for 2\",\n },\n {\n \"id\": 3,\n \"title\": \"Item 3\",\n \"description\": \"Description for 3\"\n }\n ],\n \"return\": true\n} \n This will return the following: \n {\n \"ok\": true,\n \"rows\": [\n {\n \"id\": 1,\n \"title\": \"Updated title for 1\",\n \"description\": \"Updated description for 1\"\n },\n {\n \"id\": 2,\n \"title\": \"Item 2\",\n \"description\": \"Updated description for 2\"\n },\n {\n \"id\": 3,\n \"title\": \"Item 3\",\n \"description\": \"Description for 3\"\n }\n ]\n} \n When using upsert you must provide the primary key column (or columns if the table has a compound primary key) for every row, or you will get a 400 error: \n {\n \"ok\": false,\n \"errors\": [\n \"Row 0 is missing primary key column(s): \\\"id\\\"\"\n ]\n} \n If your table does not have an explicit primary key you should pass the SQLite rowid key instead. \n Pass \"alter: true to automatically add any missing columns to the table. This requires the alter-table permission.", "breadcrumbs": "[\"JSON API\", \"The JSON write API\"]", "references": "[]"} {"id": "json_api:tableinsertview", "page": "json_api", "ref": "tableinsertview", "title": "Inserting rows", "content": "This requires the insert-row permission. \n A single row can be inserted using the \"row\" key: \n POST //
/-/insert\nContent-Type: application/json\nAuthorization: Bearer dstok_ \n {\n \"row\": {\n \"column1\": \"value1\",\n \"column2\": \"value2\"\n }\n} \n If successful, this will return a 201 status code and the newly inserted row, for example: \n {\n \"rows\": [\n {\n \"id\": 1,\n \"column1\": \"value1\",\n \"column2\": \"value2\"\n }\n ]\n} \n To insert multiple rows at a time, use the same API method but send a list of dictionaries as the \"rows\" key: \n POST //
/-/insert\nContent-Type: application/json\nAuthorization: Bearer dstok_ \n {\n \"rows\": [\n {\n \"column1\": \"value1\",\n \"column2\": \"value2\"\n },\n {\n \"column1\": \"value3\",\n \"column2\": \"value4\"\n }\n ]\n} \n If successful, this will return a 201 status code and a {\"ok\": true} response body. \n The maximum number rows that can be submitted at once defaults to 100, but this can be changed using the max_insert_rows setting. \n To return the newly inserted rows, add the \"return\": true key to the request body: \n {\n \"rows\": [\n {\n \"column1\": \"value1\",\n \"column2\": \"value2\"\n },\n {\n \"column1\": \"value3\",\n \"column2\": \"value4\"\n }\n ],\n \"return\": true\n} \n This will return the same \"rows\" key as the single row example above. There is a small performance penalty for using this option. \n If any of your rows have a primary key that is already in use, you will get an error and none of the rows will be inserted: \n {\n \"ok\": false,\n \"errors\": [\n \"UNIQUE constraint failed: new_table.id\"\n ]\n} \n Pass \"ignore\": true to ignore these errors and insert the other rows: \n {\n \"rows\": [\n {\n \"id\": 1,\n \"column1\": \"value1\",\n \"column2\": \"value2\"\n },\n {\n \"id\": 2,\n \"column1\": \"value3\",\n \"column2\": \"value4\"\n }\n ],\n \"ignore\": true\n} \n Or you can pass \"replace\": true to replace any rows with conflicting primary keys with the new values. This requires the update-row permission. \n Pass \"alter: true to automatically add any missing columns to the table. This requires the alter-table permission.", "breadcrumbs": "[\"JSON API\", \"The JSON write API\"]", "references": "[]"} {"id": "json_api:tabledropview", "page": "json_api", "ref": "tabledropview", "title": "Dropping tables", "content": "To drop a table, make a POST to //
/-/drop . This requires the drop-table permission. \n POST //
/-/drop\nContent-Type: application/json\nAuthorization: Bearer dstok_ \n Without a POST body this will return a status 200 with a note about how many rows will be deleted: \n {\n \"ok\": true,\n \"database\": \"\",\n \"table\": \"
\",\n \"row_count\": 5,\n \"message\": \"Pass \\\"confirm\\\": true to confirm\"\n} \n If you pass the following POST body: \n {\n \"confirm\": true\n} \n Then the table will be dropped and a status 200 response of {\"ok\": true} will be returned. \n Any errors will return {\"errors\": [\"... descriptive message ...\"], \"ok\": false} , and a 400 status code for a bad input or a 403 status code for an authentication or permission error.", "breadcrumbs": "[\"JSON API\", \"The JSON write API\"]", "references": "[]"} {"id": "json_api:tablecreateview-example", "page": "json_api", "ref": "tablecreateview-example", "title": "Creating a table from example data", "content": "Instead of specifying columns directly you can instead pass a single example row or a list of rows .\n Datasette will create a table with a schema that matches those rows and insert them for you: \n POST //-/create\nContent-Type: application/json\nAuthorization: Bearer dstok_ \n {\n \"table\": \"creatures\",\n \"rows\": [\n {\n \"id\": 1,\n \"name\": \"Tarantula\"\n },\n {\n \"id\": 2,\n \"name\": \"K\u0101k\u0101p\u014d\"\n }\n ],\n \"pk\": \"id\"\n} \n Doing this requires both the create-table and insert-row permissions. \n The 201 response here will be similar to the columns form, but will also include the number of rows that were inserted as row_count : \n {\n \"ok\": true,\n \"database\": \"data\",\n \"table\": \"creatures\",\n \"table_url\": \"http://127.0.0.1:8001/data/creatures\",\n \"table_api_url\": \"http://127.0.0.1:8001/data/creatures.json\",\n \"schema\": \"CREATE TABLE [creatures] (\\n [id] INTEGER PRIMARY KEY,\\n [name] TEXT\\n)\",\n \"row_count\": 2\n} \n You can call the create endpoint multiple times for the same table provided you are specifying the table using the rows or row option. New rows will be inserted into the table each time. This means you can use this API if you are unsure if the relevant table has been created yet. \n If you pass a row to the create endpoint with a primary key that already exists you will get an error that looks like this: \n {\n \"ok\": false,\n \"errors\": [\n \"UNIQUE constraint failed: creatures.id\"\n ]\n} \n You can avoid this error by passing the same \"ignore\": true or \"replace\": true options to the create endpoint as you can to the insert endpoint . \n To use the \"replace\": true option you will also need the update-row permission. \n Pass \"alter\": true to automatically add any missing columns to the existing table that are present in the rows you are submitting. This requires the alter-table permission.", "breadcrumbs": "[\"JSON API\", \"The JSON write API\"]", "references": "[]"} {"id": "json_api:tablecreateview", "page": "json_api", "ref": "tablecreateview", "title": "Creating a table", "content": "To create a table, make a POST to //-/create . This requires the create-table permission. \n POST //-/create\nContent-Type: application/json\nAuthorization: Bearer dstok_ \n {\n \"table\": \"name_of_new_table\",\n \"columns\": [\n {\n \"name\": \"id\",\n \"type\": \"integer\"\n },\n {\n \"name\": \"title\",\n \"type\": \"text\"\n }\n ],\n \"pk\": \"id\"\n} \n The JSON here describes the table that will be created: \n \n \n table is the name of the table to create. This field is required. \n \n \n columns is a list of columns to create. Each column is a dictionary with name and type keys. \n \n \n name is the name of the column. This is required. \n \n \n type is the type of the column. This is optional - if not provided, text will be assumed. The valid types are text , integer , float and blob . \n \n \n \n \n pk is the primary key for the table. This is optional - if not provided, Datasette will create a SQLite table with a hidden rowid column. \n If the primary key is an integer column, it will be configured to automatically increment for each new record. \n If you set this to id without including an id column in the list of columns , Datasette will create an auto-incrementing integer ID column for you. \n \n \n pks can be used instead of pk to create a compound primary key. It should be a JSON list of column names to use in that primary key. \n \n \n ignore can be set to true to ignore existing rows by primary key if the table already exists. \n \n \n replace can be set to true to replace existing rows by primary key if the table already exists. This requires the update-row permission. \n \n \n alter can be set to true if you want to automatically add any missing columns to the table. This requires the alter-table permission. \n \n \n If the table is successfully created this will return a 201 status code and the following response: \n {\n \"ok\": true,\n \"database\": \"data\",\n \"table\": \"name_of_new_table\",\n \"table_url\": \"http://127.0.0.1:8001/data/name_of_new_table\",\n \"table_api_url\": \"http://127.0.0.1:8001/data/name_of_new_table.json\",\n \"schema\": \"CREATE TABLE [name_of_new_table] (\\n [id] INTEGER PRIMARY KEY,\\n [title] TEXT\\n)\"\n}", "breadcrumbs": "[\"JSON API\", \"The JSON write API\"]", "references": "[]"} {"id": "metadata:table-level-metadata", "page": "metadata", "ref": "table-level-metadata", "title": "Table-level metadata", "content": "\"Table-level\" metadata refers to fields that can be specified for each table in a Datasette instance. These attributes should be listed under a specific table using the \"tables\" field. \n The following are the full list of allowed table-level metadata fields: \n \n \n source \n \n \n source_url \n \n \n license \n \n \n license_url \n \n \n about \n \n \n about_url \n \n \n hidden \n \n \n sort/sort_desc \n \n \n size \n \n \n sortable_columns \n \n \n label_column \n \n \n facets \n \n \n fts_table \n \n \n fts_pk \n \n \n searchmode \n \n \n columns", "breadcrumbs": "[\"Metadata\", \"Metadata reference\"]", "references": "[]"} {"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 \n SQLite 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 \n You can also use the sqlite-utils tool to create a view : \n sqlite-utils create-view sf-trees.db demo_view \"select qSpecies from Street_Tree_List\"", "breadcrumbs": "[\"Running SQL queries\"]", "references": "[{\"href\": \"https://sqlite-utils.datasette.io/\", \"label\": \"sqlite-utils\"}, {\"href\": \"https://sqlite-utils.datasette.io/en/stable/cli.html#creating-views\", \"label\": \"create a view\"}]"} {"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. See Controlling the ability to execute arbitrary SQL . \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 \n SQLite 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 [[[cog\nmetadata_example(cog, {\n \"databases\": {\n \"database1\": {\n \"tables\": {\n \"example_table\": {\n \"units\": {\n \"column1\": \"metres\",\n \"column2\": \"Hz\"\n }\n }\n }\n }\n }\n}) \n ]]] \n [[[end]]] \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 [[[cog\nmetadata_example(cog, {\n \"custom_units\": [\n \"decibel = [] = dB\"\n ]\n}) \n ]]] \n [[[end]]]", "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": "Datasette documentation now shows YAML examples for Metadata by default, with a tab interface for switching to JSON. ( #1153 ) \n \n \n register_output_renderer(datasette) plugins now have access to error and truncated arguments, allowing them to display error messages and take into account truncated results. ( #2130 ) \n \n \n render_cell() plugin hook now also supports an optional request argument. ( #2007 ) \n \n \n New Justfile to support development workflows for Datasette using Just . \n \n \n datasette.render_template() can now accepts a datasette.views.Context subclass as an alternative to a dictionary. ( #2127 ) \n \n \n datasette install -e path option for editable installations, useful while developing plugins. ( #2106 ) \n \n \n When started with the --cors option Datasette now serves an Access-Control-Max-Age: 3600 header, ensuring CORS OPTIONS requests are repeated no more than once an hour. ( #2079 ) \n \n \n Fixed a bug where the _internal database could display None instead of null for in-memory databases. ( #1970 )", "breadcrumbs": "[\"Changelog\", \"1.0a3 (2023-08-09)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/1153\", \"label\": \"#1153\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2130\", \"label\": \"#2130\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2007\", \"label\": \"#2007\"}, {\"href\": \"https://github.com/casey/just\", \"label\": \"Just\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2127\", \"label\": \"#2127\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2106\", \"label\": \"#2106\"}, {\"href\": \"https://github.com/simonw/datasette/issues/2079\", \"label\": \"#2079\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1970\", \"label\": \"#1970\"}]"} {"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": "changelog:signed-api-tokens", "page": "changelog", "ref": "signed-api-tokens", "title": "Signed API tokens", "content": "New /-/create-token page allowing authenticated users to create signed API tokens that can act on their behalf, see API Tokens . ( #1852 ) \n \n \n New datasette create-token command for creating tokens from the command line: datasette create-token . \n \n \n New allow_signed_tokens setting which can be used to turn off signed token support. ( #1856 ) \n \n \n New max_signed_tokens_ttl setting for restricting the maximum allowed duration of a signed token. ( #1858 )", "breadcrumbs": "[\"Changelog\", \"1.0a0 (2022-11-29)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/1852\", \"label\": \"#1852\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1856\", \"label\": \"#1856\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1858\", \"label\": \"#1858\"}]"} {"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 and API tokens 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\ndatasette 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-signed-tokens-ttl", "page": "settings", "ref": "setting-max-signed-tokens-ttl", "title": "max_signed_tokens_ttl", "content": "Maximum allowed expiry time for signed API tokens created by users. \n Defaults to 0 which means no limit - tokens can be created that will never expire. \n Set this to a value in seconds to limit the maximum expiry time. For example, to set that limit to 24 hours you would use: \n datasette mydatabase.db --setting max_signed_tokens_ttl 86400 \n This setting is enforced when incoming tokens are processed.", "breadcrumbs": "[\"Settings\", \"Settings\"]", "references": "[]"} {"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-insert-rows", "page": "settings", "ref": "setting-max-insert-rows", "title": "max_insert_rows", "content": "Maximum rows that can be inserted at a time using the bulk insert API, see Inserting rows . Defaults to 100. \n You can increase or decrease this limit like so: \n datasette mydatabase.db --setting max_insert_rows 1000", "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 Another way to achieve this is to add \"allow_sql\": false to your datasette.yaml 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-signed-tokens", "page": "settings", "ref": "setting-allow-signed-tokens", "title": "allow_signed_tokens", "content": "Should users be able to create signed API tokens to access Datasette? \n This is turned on by default. Use the following to turn it off: \n datasette mydatabase.db --setting allow_signed_tokens off \n Turning this setting off will disable the /-/create-token page, described here . It will also cause any incoming Authorization: Bearer dstok_... API tokens to be ignored.", "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~2Eorg~2Epublicwhip~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~2Eorg~2Epublicwhip~2Fperson~2F10001.json", "breadcrumbs": "[\"Pages and API endpoints\"]", "references": "[{\"href\": \"https://register-of-members-interests.datasettes.com/regmem/people/uk~2Eorg~2Epublicwhip~2Fperson~2F10001\", \"label\": \"../people/uk~2Eorg~2Epublicwhip~2Fperson~2F10001\"}, {\"href\": \"https://register-of-members-interests.datasettes.com/regmem/people/uk~2Eorg~2Epublicwhip~2Fperson~2F10001.json\", \"label\": \"../people/uk~2Eorg~2Epublicwhip~2Fperson~2F10001.json\"}]"} {"id": "json_api:rowupdateview", "page": "json_api", "ref": "rowupdateview", "title": "Updating a row", "content": "To update a row, make a POST to //
//-/update . This requires the update-row permission. \n POST //
//-/update\nContent-Type: application/json\nAuthorization: Bearer dstok_ \n {\n \"update\": {\n \"text_column\": \"New text string\",\n \"integer_column\": 3,\n \"float_column\": 3.14\n }\n} \n here is the tilde-encoded primary key value of the row to update - or a comma-separated list of primary key values if the table has a composite primary key. \n You only need to pass the columns you want to update. Any other columns will be left unchanged. \n If successful, this will return a 200 status code and a {\"ok\": true} response body. \n Add \"return\": true to the request body to return the updated row: \n {\n \"update\": {\n \"title\": \"New title\"\n },\n \"return\": true\n} \n The returned JSON will look like this: \n {\n \"ok\": true,\n \"row\": {\n \"id\": 1,\n \"title\": \"New title\",\n \"other_column\": \"Will be present here too\"\n }\n} \n Any errors will return {\"errors\": [\"... descriptive message ...\"], \"ok\": false} , and a 400 status code for a bad input or a 403 status code for an authentication or permission error. \n Pass \"alter: true to automatically add any missing columns to the table. This requires the alter-table permission.", "breadcrumbs": "[\"JSON API\", \"The JSON write API\"]", "references": "[]"} {"id": "json_api:rowdeleteview", "page": "json_api", "ref": "rowdeleteview", "title": "Deleting a row", "content": "To delete a row, make a POST to //
//-/delete . This requires the delete-row permission. \n POST //
//-/delete\nContent-Type: application/json\nAuthorization: Bearer dstok_ \n here is the tilde-encoded primary key value of the row to delete - or a comma-separated list of primary key values if the table has a composite primary key. \n If successful, this will return a 200 status code and a {\"ok\": true} response body. \n Any errors will return {\"errors\": [\"... descriptive message ...\"], \"ok\": false} , and a 400 status code for a bad input or a 403 status code for an authentication or permission error.", "breadcrumbs": "[\"JSON API\", \"The JSON write API\"]", "references": "[]"} {"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\"]", "references": "[]"}