{"id": "internals:datasette-sign", "page": "internals", "ref": "datasette-sign", "title": ".sign(value, namespace=\"default\")", "content": "value - any serializable type \n \n The value to be signed. \n \n \n \n namespace - string, optional \n \n An alternative namespace, see the itsdangerous salt documentation . \n \n \n \n Utility method for signing values, such that you can safely pass data to and from an untrusted environment. This is a wrapper around the itsdangerous library. \n This method returns a signed string, which can be decoded and verified using .unsign(value, namespace=\"default\") .", "breadcrumbs": "[\"Internals for plugins\", \"Datasette class\"]", "references": "[{\"href\": \"https://itsdangerous.palletsprojects.com/en/1.1.x/serializer/#the-salt\", \"label\": \"itsdangerous salt documentation\"}, {\"href\": \"https://itsdangerous.palletsprojects.com/\", \"label\": \"itsdangerous\"}]"} {"id": "internals:datasette-render-template", "page": "internals", "ref": "datasette-render-template", "title": "await .render_template(template, context=None, request=None)", "content": "template - string, list of strings or jinja2.Template \n \n The template file to be rendered, e.g. my_plugin.html . Datasette will search for this file first in the --template-dir= location, if it was specified - then in the plugin's bundled templates and finally in Datasette's set of default templates. \n If this is a list of template file names then the first one that exists will be loaded and rendered. \n If this is a Jinja Template object it will be used directly. \n \n \n \n context - None or a Python dictionary \n \n The context variables to pass to the template. \n \n \n \n request - request object or None \n \n If you pass a Datasette request object here it will be made available to the template. \n \n \n \n Renders a Jinja template using Datasette's preconfigured instance of Jinja and returns the resulting string. The template will have access to Datasette's default template functions and any functions that have been made available by other plugins.", "breadcrumbs": "[\"Internals for plugins\", \"Datasette class\"]", "references": "[{\"href\": \"https://jinja.palletsprojects.com/en/2.11.x/api/#jinja2.Template\", \"label\": \"Template object\"}, {\"href\": \"https://jinja.palletsprojects.com/en/2.11.x/\", \"label\": \"Jinja template\"}]"} {"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": "full_text_search:configuring-fts-using-sqlite-utils", "page": "full_text_search", "ref": "configuring-fts-using-sqlite-utils", "title": "Configuring FTS using sqlite-utils", "content": "sqlite-utils is a CLI utility and Python library for manipulating SQLite databases. You can use it from Python code to configure FTS search, or you can achieve the same goal using the accompanying command-line tool . \n Here's how to use sqlite-utils to enable full-text search for an items table across the name and description columns: \n $ sqlite-utils enable-fts mydatabase.db items name description", "breadcrumbs": "[\"Full-text search\", \"Enabling full-text search for a SQLite table\"]", "references": "[{\"href\": \"https://sqlite-utils.datasette.io/\", \"label\": \"sqlite-utils\"}, {\"href\": \"https://sqlite-utils.datasette.io/en/latest/python-api.html#enabling-full-text-search\", \"label\": \"it from Python code\"}, {\"href\": \"https://sqlite-utils.datasette.io/en/latest/cli.html#configuring-full-text-search\", \"label\": \"using the accompanying command-line tool\"}]"} {"id": "internals:datasette-unsign", "page": "internals", "ref": "datasette-unsign", "title": ".unsign(value, namespace=\"default\")", "content": "signed - any serializable type \n \n The signed string that was created using .sign(value, namespace=\"default\") . \n \n \n \n namespace - string, optional \n \n The alternative namespace, if one was used. \n \n \n \n Returns the original, decoded object that was passed to .sign(value, namespace=\"default\") . If the signature is not valid this raises a itsdangerous.BadSignature exception.", "breadcrumbs": "[\"Internals for plugins\", \"Datasette class\"]", "references": "[]"} {"id": "changelog:id70", "page": "changelog", "ref": "id70", "title": "0.33 (2019-12-22)", "content": "rowid is now included in dropdown menus for filtering tables ( #636 ) \n \n \n Columns are now only suggested for faceting if they have at least one value with more than one record ( #638 ) \n \n \n Queries with no results now display \"0 results\" ( #637 ) \n \n \n Improved documentation for the --static option ( #641 ) \n \n \n asyncio task information is now included on the /-/threads debug page \n \n \n Bumped Uvicorn dependency 0.11 \n \n \n You can now use --port 0 to listen on an available port \n \n \n New template_debug setting for debugging templates, e.g. https://latest.datasette.io/fixtures/roadside_attractions?_context=1 ( #654 )", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/636\", \"label\": \"#636\"}, {\"href\": \"https://github.com/simonw/datasette/issues/638\", \"label\": \"#638\"}, {\"href\": \"https://github.com/simonw/datasette/issues/637\", \"label\": \"#637\"}, {\"href\": \"https://github.com/simonw/datasette/issues/641\", \"label\": \"#641\"}, {\"href\": \"https://latest.datasette.io/fixtures/roadside_attractions?_context=1\", \"label\": \"https://latest.datasette.io/fixtures/roadside_attractions?_context=1\"}, {\"href\": \"https://github.com/simonw/datasette/issues/654\", \"label\": \"#654\"}]"} {"id": "internals:internals-multiparams", "page": "internals", "ref": "internals-multiparams", "title": "The MultiParams class", "content": "request.args is a MultiParams object - a dictionary-like object which provides access to query string parameters that may have multiple values. \n Consider the query string ?foo=1&foo=2&bar=3 - with two values for foo and one value for bar . \n \n \n request.args[key] - string \n \n Returns the first value for that key, or raises a KeyError if the key is missing. For the above example request.args[\"foo\"] would return \"1\" . \n \n \n \n request.args.get(key) - string or None \n \n Returns the first value for that key, or None if the key is missing. Pass a second argument to specify a different default, e.g. q = request.args.get(\"q\", \"\") . \n \n \n \n request.args.getlist(key) - list of strings \n \n Returns the list of strings for that key. request.args.getlist(\"foo\") would return [\"1\", \"2\"] in the above example. request.args.getlist(\"bar\") would return [\"3\"] . If the key is missing an empty list will be returned. \n \n \n \n request.args.keys() - list of strings \n \n Returns the list of available keys - for the example this would be [\"foo\", \"bar\"] . \n \n \n \n key in request.args - True or False \n \n You can use if key in request.args to check if a key is present. \n \n \n \n for key in request.args - iterator \n \n This lets you loop through every available key. \n \n \n \n len(request.args) - integer \n \n Returns the number of keys.", "breadcrumbs": "[\"Internals for plugins\"]", "references": "[]"} {"id": "internals:datasette-absolute-url", "page": "internals", "ref": "datasette-absolute-url", "title": ".absolute_url(request, path)", "content": "request - Request \n \n The current Request object \n \n \n \n path - string \n \n A path, for example /dbname/table.json \n \n \n \n Returns the absolute URL for the given path, including the protocol and host. For example: \n absolute_url = datasette.absolute_url(\n request, \"/dbname/table.json\"\n)\n# Would return \"http://localhost:8001/dbname/table.json\" \n The current request object is used to determine the hostname and protocol that should be used for the returned URL. The force_https_urls configuration setting is taken into account.", "breadcrumbs": "[\"Internals for plugins\", \"Datasette class\"]", "references": "[]"} {"id": "internals:datasette-add-message", "page": "internals", "ref": "datasette-add-message", "title": ".add_message(request, message, type=datasette.INFO)", "content": "request - Request \n \n The current Request object \n \n \n \n message - string \n \n The message string \n \n \n \n type - constant, optional \n \n The message type - datasette.INFO , datasette.WARNING or datasette.ERROR \n \n \n \n Datasette's flash messaging mechanism allows you to add a message that will be displayed to the user on the next page that they visit. Messages are persisted in a ds_messages cookie. This method adds a message to that cookie. \n You can try out these messages (including the different visual styling of the three message types) using the /-/messages debugging tool.", "breadcrumbs": "[\"Internals for plugins\", \"Datasette class\"]", "references": "[]"} {"id": "plugin_hooks:plugin-hook-filters-from-request", "page": "plugin_hooks", "ref": "plugin-hook-filters-from-request", "title": "filters_from_request(request, database, table, datasette)", "content": "request - Request object \n \n The current HTTP request. \n \n \n \n database - string \n \n The name of the database. \n \n \n \n table - string \n \n The name of the table. \n \n \n \n datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to execute SQL queries. \n \n \n \n This hook runs on the table page, and can influence the where clause of the SQL query used to populate that page, based on query string arguments on the incoming request. \n The hook should return an instance of datasette.filters.FilterArguments which has one required and three optional arguments: \n return FilterArguments(\n where_clauses=[\"id > :max_id\"],\n params={\"max_id\": 5},\n human_descriptions=[\"max_id is greater than 5\"],\n extra_context={},\n) \n The arguments to the FilterArguments class constructor are as follows: \n \n \n where_clauses - list of strings, required \n \n A list of SQL fragments that will be inserted into the SQL query, joined by the and operator. These can include :named parameters which will be populated using data in params . \n \n \n \n params - dictionary, optional \n \n Additional keyword arguments to be used when the query is executed. These should match any :arguments in the where clauses. \n \n \n \n human_descriptions - list of strings, optional \n \n These strings will be included in the human-readable description at the top of the page and the page . \n \n \n \n extra_context - dictionary, optional \n \n Additional context variables that should be made available to the table.html template when it is rendered. \n \n \n \n This example plugin causes 0 results to be returned if ?_nothing=1 is added to the URL: \n from datasette import hookimpl\nfrom datasette.filters import FilterArguments\n\n\n@hookimpl\ndef filters_from_request(self, request):\n if request.args.get(\"_nothing\"):\n return FilterArguments(\n [\"1 = 0\"], human_descriptions=[\"NOTHING\"]\n ) \n Example: datasette-leaflet-freedraw", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://datasette.io/plugins/datasette-leaflet-freedraw\", \"label\": \"datasette-leaflet-freedraw\"}]"} {"id": "changelog:new-plugin-hooks", "page": "changelog", "ref": "new-plugin-hooks", "title": "New plugin hooks", "content": "register_magic_parameters(datasette) can be used to define new types of magic canned query parameters. \n \n \n startup(datasette) can run custom code when Datasette first starts up. datasette-init is a new plugin that uses this hook to create database tables and views on startup if they have not yet been created. ( #834 ) \n \n \n canned_queries(datasette, database, actor) lets plugins provide additional canned queries beyond those defined in Datasette's metadata. See datasette-saved-queries for an example of this hook in action. ( #852 ) \n \n \n forbidden(datasette, request, message) is a hook for customizing how Datasette responds to 403 forbidden errors. ( #812 )", "breadcrumbs": "[\"Changelog\", \"0.45 (2020-07-01)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette-init\", \"label\": \"datasette-init\"}, {\"href\": \"https://github.com/simonw/datasette/issues/834\", \"label\": \"#834\"}, {\"href\": \"https://github.com/simonw/datasette-saved-queries\", \"label\": \"datasette-saved-queries\"}, {\"href\": \"https://github.com/simonw/datasette/issues/852\", \"label\": \"#852\"}, {\"href\": \"https://github.com/simonw/datasette/issues/812\", \"label\": \"#812\"}]"} {"id": "plugin_hooks:plugin-hook-publish-subcommand", "page": "plugin_hooks", "ref": "plugin-hook-publish-subcommand", "title": "publish_subcommand(publish)", "content": "publish - Click publish command group \n \n The Click command group for the datasette publish subcommand \n \n \n \n This hook allows you to create new providers for the datasette publish \n command. Datasette uses this hook internally to implement the default cloudrun \n and heroku subcommands, so you can read\n their source \n to see examples of this hook in action. \n Let's say you want to build a plugin that adds a datasette publish my_hosting_provider --api_key=xxx mydatabase.db publish command. Your implementation would start like this: \n from datasette import hookimpl\nfrom datasette.publish.common import (\n add_common_publish_arguments_and_options,\n)\nimport click\n\n\n@hookimpl\ndef publish_subcommand(publish):\n @publish.command()\n @add_common_publish_arguments_and_options\n @click.option(\n \"-k\",\n \"--api_key\",\n help=\"API key for talking to my hosting provider\",\n )\n def my_hosting_provider(\n files,\n metadata,\n extra_options,\n branch,\n template_dir,\n plugins_dir,\n static,\n install,\n plugin_secret,\n version_note,\n secret,\n title,\n license,\n license_url,\n source,\n source_url,\n about,\n about_url,\n api_key,\n ):\n ... \n Examples: datasette-publish-fly , datasette-publish-vercel", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/tree/main/datasette/publish\", \"label\": \"their source\"}, {\"href\": \"https://datasette.io/plugins/datasette-publish-fly\", \"label\": \"datasette-publish-fly\"}, {\"href\": \"https://datasette.io/plugins/datasette-publish-vercel\", \"label\": \"datasette-publish-vercel\"}]"} {"id": "internals:datasette-plugin-config", "page": "internals", "ref": "datasette-plugin-config", "title": ".plugin_config(plugin_name, database=None, table=None)", "content": "plugin_name - string \n \n The name of the plugin to look up configuration for. Usually this is something similar to datasette-cluster-map . \n \n \n \n database - None or string \n \n The database the user is interacting with. \n \n \n \n table - None or string \n \n The table the user is interacting with. \n \n \n \n This method lets you read plugin configuration values that were set in metadata.json . See Writing plugins that accept configuration for full details of how this method should be used. \n The return value will be the value from the configuration file - usually a dictionary. \n If the plugin is not configured the return value will be None .", "breadcrumbs": "[\"Internals for plugins\", \"Datasette class\"]", "references": "[]"} {"id": "installation:installation-pipx", "page": "installation", "ref": "installation-pipx", "title": "Using pipx", "content": "pipx is a tool for installing Python software with all of its dependencies in an isolated environment, to ensure that they will not conflict with any other installed Python software. \n If you use Homebrew on macOS you can install pipx like this: \n brew install pipx\npipx ensurepath \n Without Homebrew you can install it like so: \n python3 -m pip install --user pipx\npython3 -m pipx ensurepath \n The pipx ensurepath command configures your shell to ensure it can find commands that have been installed by pipx - generally by making sure ~/.local/bin has been added to your PATH . \n Once pipx is installed you can use it to install Datasette like this: \n pipx install datasette \n Then run datasette --version to confirm that it has been successfully installed.", "breadcrumbs": "[\"Installation\", \"Advanced installation options\"]", "references": "[{\"href\": \"https://pipxproject.github.io/pipx/\", \"label\": \"pipx\"}, {\"href\": \"https://brew.sh/\", \"label\": \"Homebrew\"}]"} {"id": "internals:datasette-get-database", "page": "internals", "ref": "datasette-get-database", "title": ".get_database(name)", "content": "name - string, optional \n \n The name of the database - optional. \n \n \n \n Returns the specified database object. Raises a KeyError if the database does not exist. Call this method without an argument to return the first connected database.", "breadcrumbs": "[\"Internals for plugins\", \"Datasette class\"]", "references": "[]"} {"id": "internals:datasette-remove-database", "page": "internals", "ref": "datasette-remove-database", "title": ".remove_database(name)", "content": "name - string \n \n The name of the database to be removed. \n \n \n \n This removes a database that has been previously added. name= is the unique name of that database.", "breadcrumbs": "[\"Internals for plugins\", \"Datasette class\"]", "references": "[]"} {"id": "contributing:general-guidelines", "page": "contributing", "ref": "general-guidelines", "title": "General guidelines", "content": "main should always be releasable . Incomplete features should live in branches. This ensures that any small bug fixes can be quickly released. \n \n \n The ideal commit should bundle together the implementation, unit tests and associated documentation updates. The commit message should link to an associated issue. \n \n \n New plugin hooks should only be shipped if accompanied by a separate release of a non-demo plugin that uses them.", "breadcrumbs": "[\"Contributing\"]", "references": "[]"} {"id": "internals:datasette-setting", "page": "internals", "ref": "datasette-setting", "title": ".setting(key)", "content": "key - string \n \n The name of the setting, e.g. base_url . \n \n \n \n Returns the configured value for the specified setting . This can be a string, boolean or integer depending on the requested setting. \n For example: \n downloads_are_allowed = datasette.setting(\"allow_download\")", "breadcrumbs": "[\"Internals for plugins\", \"Datasette class\"]", "references": "[]"} {"id": "plugin_hooks:plugin-hook-prepare-jinja2-environment", "page": "plugin_hooks", "ref": "plugin-hook-prepare-jinja2-environment", "title": "prepare_jinja2_environment(env, datasette)", "content": "env - jinja2 Environment \n \n The template environment that is being prepared \n \n \n \n datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) \n \n \n \n This hook is called with the Jinja2 environment that is used to evaluate\n Datasette HTML templates. You can use it to do things like register custom\n template filters , for\n example: \n from datasette import hookimpl\n\n\n@hookimpl\ndef prepare_jinja2_environment(env):\n env.filters[\"uppercase\"] = lambda u: u.upper() \n You can now use this filter in your custom templates like so: \n Table name: {{ table|uppercase }} \n This function can return an awaitable function if it needs to run any async code. \n Examples: datasette-edit-templates", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"http://jinja.pocoo.org/docs/2.10/api/#custom-filters\", \"label\": \"register custom\\n template filters\"}, {\"href\": \"https://datasette.io/plugins/datasette-edit-templates\", \"label\": \"datasette-edit-templates\"}]"} {"id": "authentication:authentication-ds-actor-expiry", "page": "authentication", "ref": "authentication-ds-actor-expiry", "title": "Including an expiry time", "content": "ds_actor cookies can optionally include a signed expiry timestamp, after which the cookies will no longer be valid. Authentication plugins may chose to use this mechanism to limit the lifetime of the cookie. For example, if a plugin implements single-sign-on against another source it may decide to set short-lived cookies so that if the user is removed from the SSO system their existing Datasette cookies will stop working shortly afterwards. \n To include an expiry, add a \"e\" key to the cookie value containing a base62-encoded integer representing the timestamp when the cookie should expire. For example, here's how to set a cookie that expires after 24 hours: \n import time\nfrom datasette.utils import baseconv\n\nexpires_at = int(time.time()) + (24 * 60 * 60)\n\nresponse = Response.redirect(\"/\")\nresponse.set_cookie(\n \"ds_actor\",\n datasette.sign(\n {\n \"a\": {\"id\": \"cleopaws\"},\n \"e\": baseconv.base62.encode(expires_at),\n },\n \"actor\",\n ),\n) \n The resulting cookie will encode data that looks something like this: \n {\n \"a\": {\n \"id\": \"cleopaws\"\n },\n \"e\": \"1jjSji\"\n}", "breadcrumbs": "[\"Authentication and permissions\", \"The ds_actor cookie\"]", "references": "[]"} {"id": "internals:datasette-add-database", "page": "internals", "ref": "datasette-add-database", "title": ".add_database(db, name=None, route=None)", "content": "db - datasette.database.Database instance \n \n The database to be attached. \n \n \n \n name - string, optional \n \n The name to be used for this database . If not specified Datasette will pick one based on the filename or memory name. \n \n \n \n route - string, optional \n \n This will be used in the URL path. If not specified, it will default to the same thing as the name . \n \n \n \n The datasette.add_database(db) method lets you add a new database to the current Datasette instance. \n The db parameter should be an instance of the datasette.database.Database class. For example: \n from datasette.database import Database\n\ndatasette.add_database(\n Database(\n datasette,\n path=\"path/to/my-new-database.db\",\n )\n) \n This will add a mutable database and serve it at /my-new-database . \n Use is_mutable=False to add an immutable database. \n .add_database() returns the Database instance, with its name set as the database.name attribute. Any time you are working with a newly added database you should use the return value of .add_database() , for example: \n db = datasette.add_database(\n Database(datasette, memory_name=\"statistics\")\n)\nawait db.execute_write(\n \"CREATE TABLE foo(id integer primary key)\"\n)", "breadcrumbs": "[\"Internals for plugins\", \"Datasette class\"]", "references": "[]"} {"id": "changelog:id87", "page": "changelog", "ref": "id87", "title": "0.26 (2019-01-02)", "content": "datasette serve --reload now restarts Datasette if a database file changes on disk. \n \n \n datasette publish now now takes an optional --alias mysite.now.sh argument. This will attempt to set an alias after the deploy completes. \n \n \n Fixed a bug where the advanced CSV export form failed to include the currently selected filters ( #393 )", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/393\", \"label\": \"#393\"}]"} {"id": "changelog:id88", "page": "changelog", "ref": "id88", "title": "0.25.2 (2018-12-16)", "content": "datasette publish heroku now uses the python-3.6.7 runtime \n \n \n Added documentation on how to build the documentation \n \n \n Added documentation covering our release process \n \n \n Upgraded to pytest 4.0.2", "breadcrumbs": "[\"Changelog\"]", "references": "[]"} {"id": "publish:publish-custom-metadata-and-plugins", "page": "publish", "ref": "publish-custom-metadata-and-plugins", "title": "Custom metadata and plugins", "content": "datasette publish accepts a number of additional options which can be used to further customize your Datasette instance. \n You can define your own Metadata and deploy that with your instance like so: \n datasette publish cloudrun --service=my-service mydatabase.db -m metadata.json \n If you just want to set the title, license or source information you can do that directly using extra options to datasette publish : \n datasette publish cloudrun mydatabase.db --service=my-service \\\n --title=\"Title of my database\" \\\n --source=\"Where the data originated\" \\\n --source_url=\"http://www.example.com/\" \n You can also specify plugins you would like to install. For example, if you want to include the datasette-vega visualization plugin you can use the following: \n datasette publish cloudrun mydatabase.db --service=my-service --install=datasette-vega \n If a plugin has any Secret configuration values you can use the --plugin-secret option to set those secrets at publish time. For example, using Heroku with datasette-auth-github you might run the following command: \n $ datasette publish heroku my_database.db \\\n --name my-heroku-app-demo \\\n --install=datasette-auth-github \\\n --plugin-secret datasette-auth-github client_id your_client_id \\\n --plugin-secret datasette-auth-github client_secret your_client_secret", "breadcrumbs": "[\"Publishing data\", \"datasette publish\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette-vega\", \"label\": \"datasette-vega\"}, {\"href\": \"https://github.com/simonw/datasette-auth-github\", \"label\": \"datasette-auth-github\"}]"} {"id": "plugin_hooks:plugin-hook-get-metadata", "page": "plugin_hooks", "ref": "plugin-hook-get-metadata", "title": "get_metadata(datasette, key, database, table)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) . \n \n \n \n actor - dictionary or None \n \n The currently authenticated actor . \n \n \n \n database - string or None \n \n The name of the database metadata is being asked for. \n \n \n \n table - string or None \n \n The name of the table. \n \n \n \n key - string or None \n \n The name of the key for which data is being asked for. \n \n \n \n This hook is responsible for returning a dictionary corresponding to Datasette Metadata . This function is passed the database , table and key which were passed to the upstream internal request for metadata. Regardless, it is important to return a global metadata object, where \"databases\": [] would be a top-level key. The dictionary returned here, will be merged with, and overwritten by, the contents of the physical metadata.yaml if one is present. \n \n The design of this plugin hook does not currently provide a mechanism for interacting with async code, and may change in the future. See issue 1384 . \n \n @hookimpl\ndef get_metadata(datasette, key, database, table):\n metadata = {\n \"title\": \"This will be the Datasette landing page title!\",\n \"description\": get_instance_description(datasette),\n \"databases\": [],\n }\n for db_name, db_data_dict in get_my_database_meta(\n datasette, database, table, key\n ):\n metadata[\"databases\"][db_name] = db_data_dict\n # whatever we return here will be merged with any other plugins using this hook and\n # will be overwritten by a local metadata.yaml if one exists!\n return metadata \n Example: datasette-remote-metadata plugin", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/1384\", \"label\": \"issue 1384\"}, {\"href\": \"https://datasette.io/plugins/datasette-remote-metadata\", \"label\": \"datasette-remote-metadata plugin\"}]"} {"id": "plugin_hooks:plugin-hook-register-magic-parameters", "page": "plugin_hooks", "ref": "plugin-hook-register-magic-parameters", "title": "register_magic_parameters(datasette)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) . \n \n \n \n Magic parameters can be used to add automatic parameters to canned queries . This plugin hook allows additional magic parameters to be defined by plugins. \n Magic parameters all take this format: _prefix_rest_of_parameter . The prefix indicates which magic parameter function should be called - the rest of the parameter is passed as an argument to that function. \n To register a new function, return it as a tuple of (string prefix, function) from this hook. The function you register should take two arguments: key and request , where key is the rest_of_parameter portion of the parameter and request is the current Request object . \n This example registers two new magic parameters: :_request_http_version returning the HTTP version of the current request, and :_uuid_new which returns a new UUID: \n from uuid import uuid4\n\n\ndef uuid(key, request):\n if key == \"new\":\n return str(uuid4())\n else:\n raise KeyError\n\n\ndef request(key, request):\n if key == \"http_version\":\n return request.scope[\"http_version\"]\n else:\n raise KeyError\n\n\n@hookimpl\ndef register_magic_parameters(datasette):\n return [\n (\"request\", request),\n (\"uuid\", uuid),\n ]", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[]"} {"id": "plugin_hooks:plugin-hook-forbidden", "page": "plugin_hooks", "ref": "plugin-hook-forbidden", "title": "forbidden(datasette, request, message)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to render templates or execute SQL queries. \n \n \n \n request - Request object \n \n The current HTTP request. \n \n \n \n message - string \n \n A message hinting at why the request was forbidden. \n \n \n \n Plugins can use this to customize how Datasette responds when a 403 Forbidden error occurs - usually because a page failed a permission check, see Permissions . \n If a plugin hook wishes to react to the error, it should return a Response object . \n This example returns a redirect to a /-/login page: \n from datasette import hookimpl\nfrom urllib.parse import urlencode\n\n\n@hookimpl\ndef forbidden(request, message):\n return Response.redirect(\n \"/-/login?=\" + urlencode({\"message\": message})\n ) \n The function can alternatively return an awaitable function if it needs to make any asynchronous method calls. This example renders a template: \n from datasette import hookimpl, Response\n\n\n@hookimpl\ndef forbidden(datasette):\n async def inner():\n return Response.html(\n await datasette.render_template(\n \"render_message.html\", request=request\n )\n )\n\n return inner", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[]"} {"id": "plugin_hooks:plugin-hook-handle-exception", "page": "plugin_hooks", "ref": "plugin-hook-handle-exception", "title": "handle_exception(datasette, request, exception)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to render templates or execute SQL queries. \n \n \n \n request - Request object \n \n The current HTTP request. \n \n \n \n exception - Exception \n \n The exception that was raised. \n \n \n \n This hook is called any time an unexpected exception is raised. You can use it to record the exception. \n If your handler returns a Response object it will be returned to the client in place of the default Datasette error page. \n The handler can return a response directly, or it can return return an awaitable function that returns a response. \n This example logs an error to Sentry and then renders a custom error page: \n from datasette import hookimpl, Response\nimport sentry_sdk\n\n\n@hookimpl\ndef handle_exception(datasette, exception):\n sentry_sdk.capture_exception(exception)\n\n async def inner():\n return Response.html(\n await datasette.render_template(\n \"custom_error.html\", request=request\n )\n )\n\n return inner \n Example: datasette-sentry", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://sentry.io/\", \"label\": \"Sentry\"}, {\"href\": \"https://datasette.io/plugins/datasette-sentry\", \"label\": \"datasette-sentry\"}]"} {"id": "plugin_hooks:plugin-hook-skip-csrf", "page": "plugin_hooks", "ref": "plugin-hook-skip-csrf", "title": "skip_csrf(datasette, scope)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to execute SQL queries. \n \n \n \n scope - dictionary \n \n The ASGI scope for the incoming HTTP request. \n \n \n \n This hook can be used to skip CSRF protection for a specific incoming request. For example, you might have a custom path at /submit-comment which is designed to accept comments from anywhere, whether or not the incoming request originated on the site and has an accompanying CSRF token. \n This example will disable CSRF protection for that specific URL path: \n from datasette import hookimpl\n\n\n@hookimpl\ndef skip_csrf(scope):\n return scope[\"path\"] == \"/submit-comment\" \n If any of the currently active skip_csrf() plugin hooks return True , CSRF protection will be skipped for the request.", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope\", \"label\": \"ASGI scope\"}]"} {"id": "plugin_hooks:plugin-hook-actor-from-request", "page": "plugin_hooks", "ref": "plugin-hook-actor-from-request", "title": "actor_from_request(datasette, request)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to execute SQL queries. \n \n \n \n request - Request object \n \n The current HTTP request. \n \n \n \n This is part of Datasette's authentication and permissions system . The function should attempt to authenticate an actor (either a user or an API actor of some sort) based on information in the request. \n If it cannot authenticate an actor, it should return None . Otherwise it should return a dictionary representing that actor. \n Here's an example that authenticates the actor based on an incoming API key: \n from datasette import hookimpl\nimport secrets\n\nSECRET_KEY = \"this-is-a-secret\"\n\n\n@hookimpl\ndef actor_from_request(datasette, request):\n authorization = (\n request.headers.get(\"authorization\") or \"\"\n )\n expected = \"Bearer {}\".format(SECRET_KEY)\n\n if secrets.compare_digest(authorization, expected):\n return {\"id\": \"bot\"} \n If you install this in your plugins directory you can test it like this: \n $ curl -H 'Authorization: Bearer this-is-a-secret' http://localhost:8003/-/actor.json \n Instead of returning a dictionary, this function can return an awaitable function which itself returns either None or a dictionary. This is useful for authentication functions that need to make a database query - for example: \n from datasette import hookimpl\n\n\n@hookimpl\ndef actor_from_request(datasette, request):\n async def inner():\n token = request.args.get(\"_token\")\n if not token:\n return None\n # Look up ?_token=xxx in sessions table\n result = await datasette.get_database().execute(\n \"select count(*) from sessions where token = ?\",\n [token],\n )\n if result.first()[0]:\n return {\"token\": token}\n else:\n return None\n\n return inner \n Example: datasette-auth-tokens", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://datasette.io/plugins/datasette-auth-tokens\", \"label\": \"datasette-auth-tokens\"}]"} {"id": "plugin_hooks:plugin-hook-canned-queries", "page": "plugin_hooks", "ref": "plugin-hook-canned-queries", "title": "canned_queries(datasette, database, actor)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to execute SQL queries. \n \n \n \n database - string \n \n The name of the database. \n \n \n \n actor - dictionary or None \n \n The currently authenticated actor . \n \n \n \n Use this hook to return a dictionary of additional canned query definitions for the specified database. The return value should be the same shape as the JSON described in the canned query documentation. \n from datasette import hookimpl\n\n\n@hookimpl\ndef canned_queries(datasette, database):\n if database == \"mydb\":\n return {\n \"my_query\": {\n \"sql\": \"select * from my_table where id > :min_id\"\n }\n } \n The hook can alternatively return an awaitable function that returns a list. Here's an example that returns queries that have been stored in the saved_queries database table, if one exists: \n from datasette import hookimpl\n\n\n@hookimpl\ndef canned_queries(datasette, database):\n async def inner():\n db = datasette.get_database(database)\n if await db.table_exists(\"saved_queries\"):\n results = await db.execute(\n \"select name, sql from saved_queries\"\n )\n return {\n result[\"name\"]: {\"sql\": result[\"sql\"]}\n for result in results\n }\n\n return inner \n The actor parameter can be used to include the currently authenticated actor in your decision. Here's an example that returns saved queries that were saved by that actor: \n from datasette import hookimpl\n\n\n@hookimpl\ndef canned_queries(datasette, database, actor):\n async def inner():\n db = datasette.get_database(database)\n if actor is not None and await db.table_exists(\n \"saved_queries\"\n ):\n results = await db.execute(\n \"select name, sql from saved_queries where actor_id = :id\",\n {\"id\": actor[\"id\"]},\n )\n return {\n result[\"name\"]: {\"sql\": result[\"sql\"]}\n for result in results\n }\n\n return inner \n Example: datasette-saved-queries", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://datasette.io/plugins/datasette-saved-queries\", \"label\": \"datasette-saved-queries\"}]"} {"id": "plugin_hooks:plugin-hook-menu-links", "page": "plugin_hooks", "ref": "plugin-hook-menu-links", "title": "menu_links(datasette, actor, request)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to execute SQL queries. \n \n \n \n actor - dictionary or None \n \n The currently authenticated actor . \n \n \n \n request - Request object or None \n \n The current HTTP request. This can be None if the request object is not available. \n \n \n \n This hook allows additional items to be included in the menu displayed by Datasette's top right menu icon. \n The hook should return a list of {\"href\": \"...\", \"label\": \"...\"} menu items. These will be added to the menu. \n It can alternatively return an async def awaitable function which returns a list of menu items. \n This example adds a new menu item but only if the signed in user is \"root\" : \n from datasette import hookimpl\n\n\n@hookimpl\ndef menu_links(datasette, actor):\n if actor and actor.get(\"id\") == \"root\":\n return [\n {\n \"href\": datasette.urls.path(\n \"/-/edit-schema\"\n ),\n \"label\": \"Edit schema\",\n },\n ] \n Using datasette.urls here ensures that links in the menu will take the base_url setting into account. \n Examples: datasette-search-all , datasette-graphql", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://datasette.io/plugins/datasette-search-all\", \"label\": \"datasette-search-all\"}, {\"href\": \"https://datasette.io/plugins/datasette-graphql\", \"label\": \"datasette-graphql\"}]"} {"id": "plugin_hooks:plugin-hook-table-actions", "page": "plugin_hooks", "ref": "plugin-hook-table-actions", "title": "table_actions(datasette, actor, database, table, request)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to execute SQL queries. \n \n \n \n actor - dictionary or None \n \n The currently authenticated actor . \n \n \n \n database - string \n \n The name of the database. \n \n \n \n table - string \n \n The name of the table. \n \n \n \n request - Request object or None \n \n The current HTTP request. This can be None if the request object is not available. \n \n \n \n This hook allows table actions to be displayed in a menu accessed via an action icon at the top of the table page. It should return a list of {\"href\": \"...\", \"label\": \"...\"} menu items. \n It can alternatively return an async def awaitable function which returns a list of menu items. \n This example adds a new table action if the signed in user is \"root\" : \n from datasette import hookimpl\n\n\n@hookimpl\ndef table_actions(datasette, actor, database, table):\n if actor and actor.get(\"id\") == \"root\":\n return [\n {\n \"href\": datasette.urls.path(\n \"/-/edit-schema/{}/{}\".format(\n database, table\n )\n ),\n \"label\": \"Edit schema for this table\",\n }\n ] \n Example: datasette-graphql", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://datasette.io/plugins/datasette-graphql\", \"label\": \"datasette-graphql\"}]"} {"id": "plugin_hooks:plugin-hook-database-actions", "page": "plugin_hooks", "ref": "plugin-hook-database-actions", "title": "database_actions(datasette, actor, database, request)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to execute SQL queries. \n \n \n \n actor - dictionary or None \n \n The currently authenticated actor . \n \n \n \n database - string \n \n The name of the database. \n \n \n \n request - Request object \n \n The current HTTP request. \n \n \n \n This hook is similar to table_actions(datasette, actor, database, table, request) but populates an actions menu on the database page. \n Example: datasette-graphql", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://datasette.io/plugins/datasette-graphql\", \"label\": \"datasette-graphql\"}]"} {"id": "plugin_hooks:plugin-hook-permission-allowed", "page": "plugin_hooks", "ref": "plugin-hook-permission-allowed", "title": "permission_allowed(datasette, actor, action, resource)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to execute SQL queries. \n \n \n \n actor - dictionary \n \n The current actor, as decided by actor_from_request(datasette, request) . \n \n \n \n action - string \n \n The action to be performed, e.g. \"edit-table\" . \n \n \n \n resource - string or None \n \n An identifier for the individual resource, e.g. the name of the table. \n \n \n \n Called to check that an actor has permission to perform an action on a resource. Can return True if the action is allowed, False if the action is not allowed or None if the plugin does not have an opinion one way or the other. \n Here's an example plugin which randomly selects if a permission should be allowed or denied, except for view-instance which always uses the default permission scheme instead. \n from datasette import hookimpl\nimport random\n\n\n@hookimpl\ndef permission_allowed(action):\n if action != \"view-instance\":\n # Return True or False at random\n return random.random() > 0.5\n # Returning None falls back to default permissions \n This function can alternatively return an awaitable function which itself returns True , False or None . You can use this option if you need to execute additional database queries using await datasette.execute(...) . \n Here's an example that allows users to view the admin_log table only if their actor id is present in the admin_users table. It aso disallows arbitrary SQL queries for the staff.db database for all users. \n @hookimpl\ndef permission_allowed(datasette, actor, action, resource):\n async def inner():\n if action == \"execute-sql\" and resource == \"staff\":\n return False\n if action == \"view-table\" and resource == (\n \"staff\",\n \"admin_log\",\n ):\n if not actor:\n return False\n user_id = actor[\"id\"]\n return await datasette.get_database(\n \"staff\"\n ).execute(\n \"select count(*) from admin_users where user_id = :user_id\",\n {\"user_id\": user_id},\n )\n\n return inner \n See built-in permissions for a full list of permissions that are included in Datasette core. \n Example: datasette-permissions-sql", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://datasette.io/plugins/datasette-permissions-sql\", \"label\": \"datasette-permissions-sql\"}]"} {"id": "plugin_hooks:plugin-register-output-renderer", "page": "plugin_hooks", "ref": "plugin-register-output-renderer", "title": "register_output_renderer(datasette)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) \n \n \n \n Registers a new output renderer, to output data in a custom format. The hook function should return a dictionary, or a list of dictionaries, of the following shape: \n @hookimpl\ndef register_output_renderer(datasette):\n return {\n \"extension\": \"test\",\n \"render\": render_demo,\n \"can_render\": can_render_demo, # Optional\n } \n This will register render_demo to be called when paths with the extension .test (for example /database.test , /database/table.test , or /database/table/row.test ) are requested. \n render_demo is a Python function. It can be a regular function or an async def render_demo() awaitable function, depending on if it needs to make any asynchronous calls. \n can_render_demo is a Python function (or async def function) which accepts the same arguments as render_demo but just returns True or False . It lets Datasette know if the current SQL query can be represented by the plugin - and hence influnce if a link to this output format is displayed in the user interface. If you omit the \"can_render\" key from the dictionary every query will be treated as being supported by the plugin. \n When a request is received, the \"render\" callback function is called with zero or more of the following arguments. Datasette will inspect your callback function and pass arguments that match its function signature. \n \n \n datasette - Datasette class \n \n For accessing plugin configuration and executing queries. \n \n \n \n columns - list of strings \n \n The names of the columns returned by this query. \n \n \n \n rows - list of sqlite3.Row objects \n \n The rows returned by the query. \n \n \n \n sql - string \n \n The SQL query that was executed. \n \n \n \n query_name - string or None \n \n If this was the execution of a canned query , the name of that query. \n \n \n \n database - string \n \n The name of the database. \n \n \n \n table - string or None \n \n The table or view, if one is being rendered. \n \n \n \n request - Request object \n \n The current HTTP request. \n \n \n \n view_name - string \n \n The name of the current view being called. index , database , table , and row are the most important ones. \n \n \n \n The callback function can return None , if it is unable to render the data, or a Response class that will be returned to the caller. \n It can also return a dictionary with the following keys. This format is deprecated as-of Datasette 0.49 and will be removed by Datasette 1.0. \n \n \n body - string or bytes, optional \n \n The response body, default empty \n \n \n \n content_type - string, optional \n \n The Content-Type header, default text/plain \n \n \n \n status_code - integer, optional \n \n The HTTP status code, default 200 \n \n \n \n headers - dictionary, optional \n \n Extra HTTP headers to be returned in the response. \n \n \n \n An example of an output renderer callback function: \n def render_demo():\n return Response.text(\"Hello World\") \n Here is a more complex example: \n async def render_demo(datasette, columns, rows):\n db = datasette.get_database()\n result = await db.execute(\"select sqlite_version()\")\n first_row = \" | \".join(columns)\n lines = [first_row]\n lines.append(\"=\" * len(first_row))\n for row in rows:\n lines.append(\" | \".join(row))\n return Response(\n \"\\n\".join(lines),\n content_type=\"text/plain; charset=utf-8\",\n headers={\"x-sqlite-version\": result.first()[0]},\n ) \n And here is an example can_render function which returns True only if the query results contain the columns atom_id , atom_title and atom_updated : \n def can_render_demo(columns):\n return {\n \"atom_id\",\n \"atom_title\",\n \"atom_updated\",\n }.issubset(columns) \n Examples: datasette-atom , datasette-ics , datasette-geojson , datasette-copyable", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://datasette.io/plugins/datasette-atom\", \"label\": \"datasette-atom\"}, {\"href\": \"https://datasette.io/plugins/datasette-ics\", \"label\": \"datasette-ics\"}, {\"href\": \"https://datasette.io/plugins/datasette-geojson\", \"label\": \"datasette-geojson\"}, {\"href\": \"https://datasette.io/plugins/datasette-copyable\", \"label\": \"datasette-copyable\"}]"} {"id": "plugin_hooks:plugin-register-routes", "page": "plugin_hooks", "ref": "plugin-register-routes", "title": "register_routes(datasette)", "content": "datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) \n \n \n \n Register additional view functions to execute for specified URL routes. \n Return a list of (regex, view_function) pairs, something like this: \n from datasette import hookimpl, Response\nimport html\n\n\nasync def hello_from(request):\n name = request.url_vars[\"name\"]\n return Response.html(\n \"Hello from {}\".format(html.escape(name))\n )\n\n\n@hookimpl\ndef register_routes():\n return [(r\"^/hello-from/(?P<name>.*)$\", hello_from)] \n The view functions can take a number of different optional arguments. The corresponding argument will be passed to your function depending on its named parameters - a form of dependency injection. \n The optional view function arguments are as follows: \n \n \n datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) , or to execute SQL queries. \n \n \n \n request - Request object \n \n The current HTTP request. \n \n \n \n scope - dictionary \n \n The incoming ASGI scope dictionary. \n \n \n \n send - function \n \n The ASGI send function. \n \n \n \n receive - function \n \n The ASGI receive function. \n \n \n \n The view function can be a regular function or an async def function, depending on if it needs to use any await APIs. \n The function can either return a Response class or it can return nothing and instead respond directly to the request using the ASGI send function (for advanced uses only). \n It can also raise the datasette.NotFound exception to return a 404 not found error, or the datasette.Forbidden exception for a 403 forbidden. \n See Designing URLs for your plugin for tips on designing the URL routes used by your plugin. \n Examples: datasette-auth-github , datasette-psutil", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://datasette.io/plugins/datasette-auth-github\", \"label\": \"datasette-auth-github\"}, {\"href\": \"https://datasette.io/plugins/datasette-psutil\", \"label\": \"datasette-psutil\"}]"} {"id": "plugin_hooks:plugin-hook-prepare-connection", "page": "plugin_hooks", "ref": "plugin-hook-prepare-connection", "title": "prepare_connection(conn, database, datasette)", "content": "conn - sqlite3 connection object \n \n The connection that is being opened \n \n \n \n database - string \n \n The name of the database \n \n \n \n datasette - Datasette class \n \n You can use this to access plugin configuration options via datasette.plugin_config(your_plugin_name) \n \n \n \n This hook is called when a new SQLite database connection is created. You can\n use it to register custom SQL functions ,\n aggregates and collations. For example: \n from datasette import hookimpl\nimport random\n\n\n@hookimpl\ndef prepare_connection(conn):\n conn.create_function(\n \"random_integer\", 2, random.randint\n ) \n This registers a SQL function called random_integer which takes two\n arguments and can be called like this: \n select random_integer(1, 10); \n Examples: datasette-jellyfish , datasette-jq , datasette-haversine , datasette-rure", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://docs.python.org/2/library/sqlite3.html#sqlite3.Connection.create_function\", \"label\": \"register custom SQL functions\"}, {\"href\": \"https://datasette.io/plugins/datasette-jellyfish\", \"label\": \"datasette-jellyfish\"}, {\"href\": \"https://datasette.io/plugins/datasette-jq\", \"label\": \"datasette-jq\"}, {\"href\": \"https://datasette.io/plugins/datasette-haversine\", \"label\": \"datasette-haversine\"}, {\"href\": \"https://datasette.io/plugins/datasette-rure\", \"label\": \"datasette-rure\"}]"} {"id": "plugin_hooks:plugin-hook-register-commands", "page": "plugin_hooks", "ref": "plugin-hook-register-commands", "title": "register_commands(cli)", "content": "cli - the root Datasette Click command group \n \n Use this to register additional CLI commands \n \n \n \n Register additional CLI commands that can be run using datsette yourcommand ... . This provides a mechanism by which plugins can add new CLI commands to Datasette. \n This example registers a new datasette verify file1.db file2.db command that checks if the provided file paths are valid SQLite databases: \n from datasette import hookimpl\nimport click\nimport sqlite3\n\n\n@hookimpl\ndef register_commands(cli):\n @cli.command()\n @click.argument(\n \"files\", type=click.Path(exists=True), nargs=-1\n )\n def verify(files):\n \"Verify that files can be opened by Datasette\"\n for file in files:\n conn = sqlite3.connect(str(file))\n try:\n conn.execute(\"select * from sqlite_master\")\n except sqlite3.DatabaseError:\n raise click.ClickException(\n \"Invalid database: {}\".format(file)\n ) \n The new command can then be executed like so: \n datasette verify fixtures.db \n Help text (from the docstring for the function plus any defined Click arguments or options) will become available using: \n datasette verify --help \n Plugins can register multiple commands by making multiple calls to the @cli.command() decorator. Consult the Click documentation for full details on how to build a CLI command, including how to define arguments and options. \n Note that register_commands() plugins cannot used with the --plugins-dir mechanism - they need to be installed into the same virtual environment as Datasette using pip install . Provided it has a setup.py file (see Packaging a plugin ) you can run pip install directly against the directory in which you are developing your plugin like so: \n pip install -e path/to/my/datasette-plugin \n Examples: datasette-auth-passwords , datasette-verify", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://click.palletsprojects.com/en/latest/commands/#callback-invocation\", \"label\": \"Click command group\"}, {\"href\": \"https://click.palletsprojects.com/\", \"label\": \"Click documentation\"}, {\"href\": \"https://datasette.io/plugins/datasette-auth-passwords\", \"label\": \"datasette-auth-passwords\"}, {\"href\": \"https://datasette.io/plugins/datasette-verify\", \"label\": \"datasette-verify\"}]"} {"id": "internals:datasette-ensure-permissions", "page": "internals", "ref": "datasette-ensure-permissions", "title": "await .ensure_permissions(actor, permissions)", "content": "actor - dictionary \n \n The authenticated actor. This is usually request.actor . \n \n \n \n permissions - list \n \n A list of permissions to check. Each permission in that list can be a string action name or a 2-tuple of (action, resource) . \n \n \n \n This method allows multiple permissions to be checked at once. It raises a datasette.Forbidden exception if any of the checks are denied before one of them is explicitly granted. \n This is useful when you need to check multiple permissions at once. For example, an actor should be able to view a table if either one of the following checks returns True or not a single one of them returns False : \n await self.ds.ensure_permissions(\n request.actor,\n [\n (\"view-table\", (database, table)),\n (\"view-database\", database),\n \"view-instance\",\n ],\n)", "breadcrumbs": "[\"Internals for plugins\", \"Datasette class\"]", "references": "[]"} {"id": "internals:datasette-check-visibility", "page": "internals", "ref": "datasette-check-visibility", "title": "await .check_visibility(actor, action=None, resource=None, permissions=None)", "content": "actor - dictionary \n \n The authenticated actor. This is usually request.actor . \n \n \n \n action - string, optional \n \n The name of the action that is being permission checked. \n \n \n \n resource - string or tuple, optional \n \n The resource, e.g. the name of the database, or a tuple of two strings containing the name of the database and the name of the table. Only some permissions apply to a resource. \n \n \n \n permissions - list of action strings or (action, resource) tuples, optional \n \n Provide this instead of action and resource to check multiple permissions at once. \n \n \n \n This convenience method can be used to answer the question \"should this item be considered private, in that it is visible to me but it is not visible to anonymous users?\" \n It returns a tuple of two booleans, (visible, private) . visible indicates if the actor can see this resource. private will be True if an anonymous user would not be able to view the resource. \n This example checks if the user can access a specific table, and sets private so that a padlock icon can later be displayed: \n visible, private = await self.ds.check_visibility(\n request.actor,\n action=\"view-table\",\n resource=(database, table),\n) \n The following example runs three checks in a row, similar to await .ensure_permissions(actor, permissions) . If any of the checks are denied before one of them is explicitly granted then visible will be False . private will be True if an anonymous user would not be able to view the resource. \n visible, private = await self.ds.check_visibility(\n request.actor,\n permissions=[\n (\"view-table\", (database, table)),\n (\"view-database\", database),\n \"view-instance\",\n ],\n)", "breadcrumbs": "[\"Internals for plugins\", \"Datasette class\"]", "references": "[]"} {"id": "internals:datasette-permission-allowed", "page": "internals", "ref": "datasette-permission-allowed", "title": "await .permission_allowed(actor, action, resource=None, default=False)", "content": "actor - dictionary \n \n The authenticated actor. This is usually request.actor . \n \n \n \n action - string \n \n The name of the action that is being permission checked. \n \n \n \n resource - string or tuple, optional \n \n The resource, e.g. the name of the database, or a tuple of two strings containing the name of the database and the name of the table. Only some permissions apply to a resource. \n \n \n \n default - optional, True or False \n \n Should this permission check be default allow or default deny. \n \n \n \n Check if the given actor has permission to perform the given action on the given resource. \n Some permission checks are carried out against rules defined in metadata.json , while other custom permissions may be decided by plugins that implement the permission_allowed(datasette, actor, action, resource) plugin hook. \n If neither metadata.json nor any of the plugins provide an answer to the permission query the default argument will be returned. \n See Built-in permissions for a full list of permission actions included in Datasette core.", "breadcrumbs": "[\"Internals for plugins\", \"Datasette class\"]", "references": "[]"} {"id": "changelog:id69", "page": "changelog", "ref": "id69", "title": "0.34 (2020-01-29)", "content": "_search= queries are now correctly escaped using a new escape_fts() custom SQL function. This means you can now run searches for strings like park. without seeing errors. ( #651 ) \n \n \n Google Cloud Run is no longer in beta, so datasette publish cloudrun has been updated to work even if the user has not installed the gcloud beta components package. Thanks, Katie McLaughlin ( #660 ) \n \n \n datasette package now accepts a --port option for specifying which port the resulting Docker container should listen on. ( #661 )", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/651\", \"label\": \"#651\"}, {\"href\": \"https://cloud.google.com/run/\", \"label\": \"Google Cloud Run\"}, {\"href\": \"https://github.com/simonw/datasette/pull/660\", \"label\": \"#660\"}, {\"href\": \"https://github.com/simonw/datasette/issues/661\", \"label\": \"#661\"}]"} {"id": "deploying:deploying-proxy", "page": "deploying", "ref": "deploying-proxy", "title": "Running Datasette behind a proxy", "content": "You may wish to run Datasette behind an Apache or nginx proxy, using a path within your existing site. \n You can use the base_url configuration setting to tell Datasette to serve traffic with a specific URL prefix. For example, you could run Datasette like this: \n datasette my-database.db --setting base_url /my-datasette/ -p 8009 \n This will run Datasette with the following URLs: \n \n \n http://127.0.0.1:8009/my-datasette/ - the Datasette homepage \n \n \n http://127.0.0.1:8009/my-datasette/my-database - the page for the my-database.db database \n \n \n http://127.0.0.1:8009/my-datasette/my-database/some_table - the page for the some_table table \n \n \n You can now set your nginx or Apache server to proxy the /my-datasette/ path to this Datasette instance.", "breadcrumbs": "[\"Deploying Datasette\"]", "references": "[]"} {"id": "writing_plugins:id1", "page": "writing_plugins", "ref": "id1", "title": "Writing plugins", "content": "You can write one-off plugins that apply to just one Datasette instance, or you can write plugins which can be installed using pip and can be shipped to the Python Package Index ( PyPI ) for other people to install. \n Want to start by looking at an example? The Datasette plugins directory lists more than 90 open source plugins with code you can explore. The plugin hooks page includes links to example plugins for each of the documented hooks.", "breadcrumbs": "[]", "references": "[{\"href\": \"https://pypi.org/\", \"label\": \"PyPI\"}, {\"href\": \"https://datasette.io/plugins\", \"label\": \"Datasette plugins directory\"}]"} {"id": "custom_templates:custom-pages-redirects", "page": "custom_templates", "ref": "custom-pages-redirects", "title": "Custom redirects", "content": "You can use the custom_redirect(location) function to redirect users to another page, for example in a file called pages/datasette.html : \n {{ custom_redirect(\"https://github.com/simonw/datasette\") }} \n Now requests to http://localhost:8001/datasette will result in a redirect. \n These redirects are served with a 302 Found status code by default. You can send a 301 Moved Permanently code by passing 301 as the second argument to the function: \n {{ custom_redirect(\"https://github.com/simonw/datasette\", 301) }}", "breadcrumbs": "[\"Custom pages and templates\", \"Custom pages\"]", "references": "[]"} {"id": "installation:upgrading-packages-using-pipx", "page": "installation", "ref": "upgrading-packages-using-pipx", "title": "Upgrading packages using pipx", "content": "You can upgrade your pipx installation to the latest release of Datasette using pipx upgrade datasette : \n $ pipx upgrade datasette\nupgraded package datasette from 0.39 to 0.40 (location: /Users/simon/.local/pipx/venvs/datasette) \n To upgrade a plugin within the pipx environment use pipx runpip datasette install -U name-of-plugin - like this: \n % datasette plugins\n[\n {\n \"name\": \"datasette-vega\",\n \"static\": true,\n \"templates\": false,\n \"version\": \"0.6\"\n }\n]\n\n$ pipx runpip datasette install -U datasette-vega\nCollecting datasette-vega\nDownloading datasette_vega-0.6.2-py3-none-any.whl (1.8 MB)\n |\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 1.8 MB 2.0 MB/s\n...\nInstalling collected packages: datasette-vega\nAttempting uninstall: datasette-vega\n Found existing installation: datasette-vega 0.6\n Uninstalling datasette-vega-0.6:\n Successfully uninstalled datasette-vega-0.6\nSuccessfully installed datasette-vega-0.6.2\n\n$ datasette plugins\n[\n {\n \"name\": \"datasette-vega\",\n \"static\": true,\n \"templates\": false,\n \"version\": \"0.6.2\"\n }\n]", "breadcrumbs": "[\"Installation\", \"Advanced installation options\", \"Using pipx\"]", "references": "[]"} {"id": "facets:facets-metadata", "page": "facets", "ref": "facets-metadata", "title": "Facets in metadata.json", "content": "You can turn facets on by default for specific tables by adding them to a \"facets\" key in a Datasette Metadata file. \n Here's an example that turns on faceting by default for the qLegalStatus column in the Street_Tree_List table in the sf-trees database: \n {\n \"databases\": {\n \"sf-trees\": {\n \"tables\": {\n \"Street_Tree_List\": {\n \"facets\": [\"qLegalStatus\"]\n }\n }\n }\n }\n} \n Facets defined in this way will always be shown in the interface and returned in the API, regardless of the _facet arguments passed to the view. \n You can specify array or date facets in metadata using JSON objects with a single key of array or date and a value specifying the column, like this: \n {\n \"facets\": [\n {\"array\": \"tags\"},\n {\"date\": \"created\"}\n ]\n} \n You can change the default facet size (the number of results shown for each facet) for a table using facet_size : \n {\n \"databases\": {\n \"sf-trees\": {\n \"tables\": {\n \"Street_Tree_List\": {\n \"facets\": [\"qLegalStatus\"],\n \"facet_size\": 10\n }\n }\n }\n }\n}", "breadcrumbs": "[\"Facets\"]", "references": "[]"} {"id": "plugins:plugins-installed", "page": "plugins", "ref": "plugins-installed", "title": "Seeing what plugins are installed", "content": "You can see a list of installed plugins by navigating to the /-/plugins page of your Datasette instance - for example: https://fivethirtyeight.datasettes.com/-/plugins \n You can also use the datasette plugins command: \n $ datasette plugins\n[\n {\n \"name\": \"datasette_json_html\",\n \"static\": false,\n \"templates\": false,\n \"version\": \"0.4.0\"\n }\n] \n [[[cog\nfrom datasette import cli\nfrom click.testing import CliRunner\nimport textwrap, json\ncog.out(\"\\n\")\nresult = CliRunner().invoke(cli.cli, [\"plugins\", \"--all\"])\n# cog.out() with text containing newlines was unindenting for some reason\ncog.outl(\"If you run ``datasette plugins --all`` it will include default plugins that ship as part of Datasette::\\n\")\nplugins = [p for p in json.loads(result.output) if p[\"name\"].startswith(\"datasette.\")]\nindented = textwrap.indent(json.dumps(plugins, indent=4), \" \")\nfor line in indented.split(\"\\n\"):\n cog.outl(line)\ncog.out(\"\\n\\n\") \n ]]] \n If you run datasette plugins --all it will include default plugins that ship as part of Datasette: \n [\n {\n \"name\": \"datasette.actor_auth_cookie\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"actor_from_request\"\n ]\n },\n {\n \"name\": \"datasette.blob_renderer\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"register_output_renderer\"\n ]\n },\n {\n \"name\": \"datasette.default_magic_parameters\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"register_magic_parameters\"\n ]\n },\n {\n \"name\": \"datasette.default_menu_links\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"menu_links\"\n ]\n },\n {\n \"name\": \"datasette.default_permissions\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"permission_allowed\"\n ]\n },\n {\n \"name\": \"datasette.facets\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"register_facet_classes\"\n ]\n },\n {\n \"name\": \"datasette.filters\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"filters_from_request\"\n ]\n },\n {\n \"name\": \"datasette.forbidden\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"forbidden\"\n ]\n },\n {\n \"name\": \"datasette.handle_exception\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"handle_exception\"\n ]\n },\n {\n \"name\": \"datasette.publish.cloudrun\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"publish_subcommand\"\n ]\n },\n {\n \"name\": \"datasette.publish.heroku\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"publish_subcommand\"\n ]\n },\n {\n \"name\": \"datasette.sql_functions\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"prepare_connection\"\n ]\n }\n] \n [[[end]]] \n You can add the --plugins-dir= option to include any plugins found in that directory.", "breadcrumbs": "[\"Plugins\"]", "references": "[{\"href\": \"https://fivethirtyeight.datasettes.com/-/plugins\", \"label\": \"https://fivethirtyeight.datasettes.com/-/plugins\"}]"} {"id": "deploying:deploying-systemd", "page": "deploying", "ref": "deploying-systemd", "title": "Running Datasette using systemd", "content": "You can run Datasette on Ubuntu or Debian systems using systemd . \n First, ensure you have Python 3 and pip installed. On Ubuntu you can use sudo apt-get install python3 python3-pip . \n You can install Datasette into a virtual environment, or you can install it system-wide. To install system-wide, use sudo pip3 install datasette . \n Now create a folder for your Datasette databases, for example using mkdir /home/ubuntu/datasette-root . \n You can copy a test database into that folder like so: \n cd /home/ubuntu/datasette-root\ncurl -O https://latest.datasette.io/fixtures.db \n Create a file at /etc/systemd/system/datasette.service with the following contents: \n [Unit]\nDescription=Datasette\nAfter=network.target\n\n[Service]\nType=simple\nUser=ubuntu\nEnvironment=DATASETTE_SECRET=\nWorkingDirectory=/home/ubuntu/datasette-root\nExecStart=datasette serve . -h 127.0.0.1 -p 8000\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target \n Add a random value for the DATASETTE_SECRET - this will be used to sign Datasette cookies such as the CSRF token cookie. You can generate a suitable value like so: \n $ python3 -c 'import secrets; print(secrets.token_hex(32))' \n This configuration will run Datasette against all database files contained in the /home/ubuntu/datasette-root directory. If that directory contains a metadata.yml (or .json ) file or a templates/ or plugins/ sub-directory those will automatically be loaded by Datasette - see Configuration directory mode for details. \n You can start the Datasette process running using the following: \n sudo systemctl daemon-reload\nsudo systemctl start datasette.service \n You will need to restart the Datasette service after making changes to its metadata.json configuration or adding a new database file to that directory. You can do that using: \n sudo systemctl restart datasette.service \n Once the service has started you can confirm that Datasette is running on port 8000 like so: \n curl 127.0.0.1:8000/-/versions.json\n# Should output JSON showing the installed version \n Datasette will not be accessible from outside the server because it is listening on 127.0.0.1 . You can expose it by instead listening on 0.0.0.0 , but a better way is to set up a proxy such as nginx - see Running Datasette behind a proxy .", "breadcrumbs": "[\"Deploying Datasette\"]", "references": "[]"} {"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 <https://datasette.io/plugins> . \n If your plugin includes functionality that relates to a specific database you could also register a URL route like this: \n \n \n /dbname/-/upload-excel \n \n \n Or for a specific table like this: \n \n \n /dbname/tablename/-/modify-table-schema \n \n \n Note that a row could have a primary key of - and this URL scheme will still work, because Datasette row pages do not ever have a trailing slash followed by additional path components.", "breadcrumbs": "[\"Writing plugins\"]", "references": "[]"} {"id": "changelog:control-http-caching-with-ttl", "page": "changelog", "ref": "control-http-caching-with-ttl", "title": "Control HTTP caching with ?_ttl=", "content": "You can now customize the HTTP max-age header that is sent on a per-URL basis, using the new ?_ttl= query string parameter. \n You can set this to any value in seconds, or you can set it to 0 to disable HTTP caching entirely. \n Consider for example this query which returns a randomly selected member of the Avengers: \n select * from [avengers/avengers] order by random() limit 1 \n If you hit the following page repeatedly you will get the same result, due to HTTP caching: \n /fivethirtyeight?sql=select+*+from+%5Bavengers%2Favengers%5D+order+by+random%28%29+limit+1 \n By adding ?_ttl=0 to the zero you can ensure the page will not be cached and get back a different super hero every time: \n /fivethirtyeight?sql=select+*+from+%5Bavengers%2Favengers%5D+order+by+random%28%29+limit+1&_ttl=0", "breadcrumbs": "[\"Changelog\", \"0.23 (2018-06-18)\"]", "references": "[{\"href\": \"https://fivethirtyeight.datasettes.com/fivethirtyeight?sql=select+*+from+%5Bavengers%2Favengers%5D+order+by+random%28%29+limit+1\", \"label\": \"/fivethirtyeight?sql=select+*+from+%5Bavengers%2Favengers%5D+order+by+random%28%29+limit+1\"}, {\"href\": \"https://fivethirtyeight.datasettes.com/fivethirtyeight?sql=select+*+from+%5Bavengers%2Favengers%5D+order+by+random%28%29+limit+1&_ttl=0\", \"label\": \"/fivethirtyeight?sql=select+*+from+%5Bavengers%2Favengers%5D+order+by+random%28%29+limit+1&_ttl=0\"}]"} {"id": "changelog:id61", "page": "changelog", "ref": "id61", "title": "0.41 (2020-05-06)", "content": "You can now create custom pages within your Datasette instance using a custom template file. For example, adding a template file called templates/pages/about.html will result in a new page being served at /about on your instance. See the custom pages documentation for full details, including how to return custom HTTP headers, redirects and status codes. ( #648 ) \n Configuration directory mode ( #731 ) allows you to define a custom Datasette instance as a directory. So instead of running the following: \n $ datasette one.db two.db \\\n --metadata=metadata.json \\\n --template-dir=templates/ \\\n --plugins-dir=plugins \\\n --static css:css \n You can instead arrange your files in a single directory called my-project and run this: \n $ datasette my-project/ \n Also in this release: \n \n \n New NOT LIKE table filter: ?colname__notlike=expression . ( #750 ) \n \n \n Datasette now has a pattern portfolio at /-/patterns - e.g. https://latest.datasette.io/-/patterns . This is a page that shows every Datasette user interface component in one place, to aid core development and people building custom CSS themes. ( #151 ) \n \n \n SQLite PRAGMA functions such as pragma_table_info(tablename) are now allowed in Datasette SQL queries. ( #761 ) \n \n \n Datasette pages now consistently return a content-type of text/html; charset=utf-8\" . ( #752 ) \n \n \n Datasette now handles an ASGI raw_path value of None , which should allow compatibility with the Mangum adapter for running ASGI apps on AWS Lambda. Thanks, Colin Dellow. ( #719 ) \n \n \n Installation documentation now covers how to Using pipx . ( #756 ) \n \n \n Improved the documentation for Full-text search . ( #748 )", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/648\", \"label\": \"#648\"}, {\"href\": \"https://github.com/simonw/datasette/issues/731\", \"label\": \"#731\"}, {\"href\": \"https://github.com/simonw/datasette/issues/750\", \"label\": \"#750\"}, {\"href\": \"https://latest.datasette.io/-/patterns\", \"label\": \"https://latest.datasette.io/-/patterns\"}, {\"href\": \"https://github.com/simonw/datasette/issues/151\", \"label\": \"#151\"}, {\"href\": \"https://www.sqlite.org/pragma.html#pragfunc\", \"label\": \"PRAGMA functions\"}, {\"href\": \"https://github.com/simonw/datasette/issues/761\", \"label\": \"#761\"}, {\"href\": \"https://github.com/simonw/datasette/issues/752\", \"label\": \"#752\"}, {\"href\": \"https://github.com/erm/mangum\", \"label\": \"Mangum\"}, {\"href\": \"https://github.com/simonw/datasette/pull/719\", \"label\": \"#719\"}, {\"href\": \"https://github.com/simonw/datasette/issues/756\", \"label\": \"#756\"}, {\"href\": \"https://github.com/simonw/datasette/issues/748\", \"label\": \"#748\"}]"} {"id": "authentication:authentication-permissions-metadata", "page": "authentication", "ref": "authentication-permissions-metadata", "title": "Configuring permissions in metadata.json", "content": "You can limit who is allowed to view different parts of your Datasette instance using \"allow\" keys in your Metadata configuration. \n You can control the following: \n \n \n Access to the entire Datasette instance \n \n \n Access to specific databases \n \n \n Access to specific tables and views \n \n \n Access to specific Canned queries \n \n \n If a user cannot access a specific database, they will not be able to access tables, views or queries within that database. If a user cannot access the instance they will not be able to access any of the databases, tables, views or queries.", "breadcrumbs": "[\"Authentication and permissions\"]", "references": "[]"} {"id": "installation:installing-plugins-using-pipx", "page": "installation", "ref": "installing-plugins-using-pipx", "title": "Installing plugins using pipx", "content": "You can install additional datasette plugins with pipx inject like so: \n $ pipx inject datasette datasette-json-html\ninjected package datasette-json-html into venv datasette\ndone! \u2728 \ud83c\udf1f \u2728\n\n$ datasette plugins\n[\n {\n \"name\": \"datasette-json-html\",\n \"static\": false,\n \"templates\": false,\n \"version\": \"0.6\"\n }\n]", "breadcrumbs": "[\"Installation\", \"Advanced installation options\", \"Using pipx\"]", "references": "[]"} {"id": "full_text_search:full-text-search-custom-sql", "page": "full_text_search", "ref": "full-text-search-custom-sql", "title": "Searches using custom SQL", "content": "You can include full-text search results in custom SQL queries. The general pattern with SQLite search is to run the search as a sub-select that returns rowid values, then include those rowids in another part of the query. \n You can see the syntax for a basic search by running that search on a table page and then clicking \"View and edit SQL\" to see the underlying SQL. For example, consider this search for manafort is the US FARA database : \n /fara/FARA_All_ShortForms?_search=manafort \n If you click View and edit SQL you'll see that the underlying SQL looks like this: \n select\n rowid,\n Short_Form_Termination_Date,\n Short_Form_Date,\n Short_Form_Last_Name,\n Short_Form_First_Name,\n Registration_Number,\n Registration_Date,\n Registrant_Name,\n Address_1,\n Address_2,\n City,\n State,\n Zip\nfrom\n FARA_All_ShortForms\nwhere\n rowid in (\n select\n rowid\n from\n FARA_All_ShortForms_fts\n where\n FARA_All_ShortForms_fts match escape_fts(:search)\n )\norder by\n rowid\nlimit\n 101", "breadcrumbs": "[\"Full-text search\"]", "references": "[{\"href\": \"https://fara.datasettes.com/fara/FARA_All_ShortForms?_search=manafort\", \"label\": \"manafort is the US FARA database\"}, {\"href\": \"https://fara.datasettes.com/fara?sql=select%0D%0A++rowid%2C%0D%0A++Short_Form_Termination_Date%2C%0D%0A++Short_Form_Date%2C%0D%0A++Short_Form_Last_Name%2C%0D%0A++Short_Form_First_Name%2C%0D%0A++Registration_Number%2C%0D%0A++Registration_Date%2C%0D%0A++Registrant_Name%2C%0D%0A++Address_1%2C%0D%0A++Address_2%2C%0D%0A++City%2C%0D%0A++State%2C%0D%0A++Zip%0D%0Afrom%0D%0A++FARA_All_ShortForms%0D%0Awhere%0D%0A++rowid+in+%28%0D%0A++++select%0D%0A++++++rowid%0D%0A++++from%0D%0A++++++FARA_All_ShortForms_fts%0D%0A++++where%0D%0A++++++FARA_All_ShortForms_fts+match+escape_fts%28%3Asearch%29%0D%0A++%29%0D%0Aorder+by%0D%0A++rowid%0D%0Alimit%0D%0A++101&search=manafort\", \"label\": \"View and edit SQL\"}]"} {"id": "metadata:metadata-column-descriptions", "page": "metadata", "ref": "metadata-column-descriptions", "title": "Column descriptions", "content": "You can include descriptions for your columns by adding a \"columns\": {\"name-of-column\": \"description-of-column\"} block to your table metadata: \n {\n \"databases\": {\n \"database1\": {\n \"tables\": {\n \"example_table\": {\n \"columns\": {\n \"column1\": \"Description of column 1\",\n \"column2\": \"Description of column 2\"\n }\n }\n }\n }\n }\n} \n These will be displayed at the top of the table page, and will also show in the cog menu for each column. \n You can see an example of how these look at latest.datasette.io/fixtures/roadside_attractions .", "breadcrumbs": "[\"Metadata\"]", "references": "[{\"href\": \"https://latest.datasette.io/fixtures/roadside_attractions\", \"label\": \"latest.datasette.io/fixtures/roadside_attractions\"}]"} {"id": "metadata:metadata-hiding-tables", "page": "metadata", "ref": "metadata-hiding-tables", "title": "Hiding tables", "content": "You can hide tables from the database listing view (in the same way that FTS and\n SpatiaLite tables are automatically hidden) using \"hidden\": true : \n {\n \"databases\": {\n \"database1\": {\n \"tables\": {\n \"example_table\": {\n \"hidden\": true\n }\n }\n }\n }\n}", "breadcrumbs": "[\"Metadata\"]", "references": "[]"} {"id": "json_api:column-filter-arguments", "page": "json_api", "ref": "column-filter-arguments", "title": "Column filter arguments", "content": "You can filter the data returned by the table based on column values using a query string argument. \n \n \n ?column__exact=value or ?_column=value \n \n Returns rows where the specified column exactly matches the value. \n \n \n \n ?column__not=value \n \n Returns rows where the column does not match the value. \n \n \n \n ?column__contains=value \n \n Rows where the string column contains the specified value ( column like \"%value%\" in SQL). \n \n \n \n ?column__endswith=value \n \n Rows where the string column ends with the specified value ( column like \"%value\" in SQL). \n \n \n \n ?column__startswith=value \n \n Rows where the string column starts with the specified value ( column like \"value%\" in SQL). \n \n \n \n ?column__gt=value \n \n Rows which are greater than the specified value. \n \n \n \n ?column__gte=value \n \n Rows which are greater than or equal to the specified value. \n \n \n \n ?column__lt=value \n \n Rows which are less than the specified value. \n \n \n \n ?column__lte=value \n \n Rows which are less than or equal to the specified value. \n \n \n \n ?column__like=value \n \n Match rows with a LIKE clause, case insensitive and with % as the wildcard character. \n \n \n \n ?column__notlike=value \n \n Match rows that do not match the provided LIKE clause. \n \n \n \n ?column__glob=value \n \n Similar to LIKE but uses Unix wildcard syntax and is case sensitive. \n \n \n \n ?column__in=value1,value2,value3 \n \n Rows where column matches any of the provided values. \n You can use a comma separated string, or you can use a JSON array. \n The JSON array option is useful if one of your matching values itself contains a comma: \n ?column__in=[\"value\",\"value,with,commas\"] \n \n \n \n ?column__notin=value1,value2,value3 \n \n Rows where column does not match any of the provided values. The inverse of __in= . Also supports JSON arrays. \n \n \n \n ?column__arraycontains=value \n \n Works against columns that contain JSON arrays - matches if any of the values in that array match the provided value. \n This is only available if the json1 SQLite extension is enabled. \n \n \n \n ?column__arraynotcontains=value \n \n Works against columns that contain JSON arrays - matches if none of the values in that array match the provided value. \n This is only available if the json1 SQLite extension is enabled. \n \n \n \n ?column__date=value \n \n Column is a datestamp occurring on the specified YYYY-MM-DD date, e.g. 2018-01-02 . \n \n \n \n ?column__isnull=1 \n \n Matches rows where the column is null. \n \n \n \n ?column__notnull=1 \n \n Matches rows where the column is not null. \n \n \n \n ?column__isblank=1 \n \n Matches rows where the column is blank, meaning null or the empty string. \n \n \n \n ?column__notblank=1 \n \n Matches rows where the column is not blank.", "breadcrumbs": "[\"JSON API\", \"Table arguments\"]", "references": "[]"} {"id": "custom_templates:custom-pages-parameters", "page": "custom_templates", "ref": "custom-pages-parameters", "title": "Path parameters for pages", "content": "You can define custom pages that match multiple paths by creating files with {variable} definitions in their filenames. \n For example, to capture any request to a URL matching /about/* , you would create a template in the following location: \n templates/pages/about/{slug}.html \n A hit to /about/news would render that template and pass in a variable called slug with a value of \"news\" . \n If you use this mechanism don't forget to return a 404 if the referenced content could not be found. You can do this using {{ raise_404() }} described below. \n Templates defined using custom page routes work particularly well with the sql() template function from datasette-template-sql or the graphql() template function from datasette-graphql .", "breadcrumbs": "[\"Custom pages and templates\", \"Custom pages\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette-template-sql\", \"label\": \"datasette-template-sql\"}, {\"href\": \"https://github.com/simonw/datasette-graphql#the-graphql-template-function\", \"label\": \"datasette-graphql\"}]"} {"id": "plugins:one-off-plugins-using-plugins-dir", "page": "plugins", "ref": "one-off-plugins-using-plugins-dir", "title": "One-off plugins using --plugins-dir", "content": "You can also define one-off per-project plugins by saving them as plugin_name.py functions in a plugins/ folder and then passing that folder to datasette using the --plugins-dir option: \n datasette mydb.db --plugins-dir=plugins/", "breadcrumbs": "[\"Plugins\", \"Installing plugins\"]", "references": "[]"} {"id": "custom_templates:id1", "page": "custom_templates", "ref": "id1", "title": "Custom pages", "content": "You can add templated pages to your Datasette instance by creating HTML files in a pages directory within your templates directory. \n For example, to add a custom page that is served at http://localhost/about you would create a file in templates/pages/about.html , then start Datasette like this: \n $ datasette mydb.db --template-dir=templates/ \n You can nest directories within pages to create a nested structure. To create a http://localhost:8001/about/map page you would create templates/pages/about/map.html .", "breadcrumbs": "[\"Custom pages and templates\"]", "references": "[]"} {"id": "changelog:flash-messages", "page": "changelog", "ref": "flash-messages", "title": "Flash messages", "content": "Writable canned queries needed a mechanism to let the user know that the query has been successfully executed. The new flash messaging system ( #790 ) allows messages to persist in signed cookies which are then displayed to the user on the next page that they visit. Plugins can use this mechanism to display their own messages, see .add_message(request, message, type=datasette.INFO) for details. \n You can try out the new messages using the /-/messages debug tool, for example at https://latest.datasette.io/-/messages", "breadcrumbs": "[\"Changelog\", \"0.44 (2020-06-11)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/790\", \"label\": \"#790\"}, {\"href\": \"https://latest.datasette.io/-/messages\", \"label\": \"https://latest.datasette.io/-/messages\"}]"} {"id": "sql_queries:canned-queries-json-api", "page": "sql_queries", "ref": "canned-queries-json-api", "title": "JSON API for writable canned queries", "content": "Writable canned queries can also be accessed using a JSON API. You can POST data to them using JSON, and you can request that their response is returned to you as JSON. \n To submit JSON to a writable canned query, encode key/value parameters as a JSON document: \n POST /mydatabase/add_message\n\n{\"message\": \"Message goes here\"} \n You can also continue to submit data using regular form encoding, like so: \n POST /mydatabase/add_message\n\nmessage=Message+goes+here \n There are three options for specifying that you would like the response to your request to return JSON data, as opposed to an HTTP redirect to another page. \n \n \n Set an Accept: application/json header on your request \n \n \n Include ?_json=1 in the URL that you POST to \n \n \n Include \"_json\": 1 in your JSON body, or &_json=1 in your form encoded body \n \n \n The JSON response will look like this: \n {\n \"ok\": true,\n \"message\": \"Query executed, 1 row affected\",\n \"redirect\": \"/data/add_name\"\n} \n The \"message\" and \"redirect\" values here will take into account on_success_message , on_success_redirect , on_error_message and on_error_redirect , if they have been set.", "breadcrumbs": "[\"Running SQL queries\", \"Canned queries\"]", "references": "[]"} {"id": "changelog:smaller-changes", "page": "changelog", "ref": "smaller-changes", "title": "Smaller changes", "content": "Wide tables shown within Datasette now scroll horizontally ( #998 ). This is achieved using a new <div class=\"table-wrapper\"> element which may impact the implementation of some plugins (for example this change to datasette-cluster-map ). \n \n \n New debug-menu permission. ( #1068 ) \n \n \n Removed --debug option, which didn't do anything. ( #814 ) \n \n \n Link: HTTP header pagination. ( #1014 ) \n \n \n x button for clearing filters. ( #1016 ) \n \n \n Edit SQL button on canned queries, ( #1019 ) \n \n \n --load-extension=spatialite shortcut. ( #1028 ) \n \n \n scale-in animation for column action menu. ( #1039 ) \n \n \n Option to pass a list of templates to .render_template() is now documented. ( #1045 ) \n \n \n New datasette.urls.static_plugins() method. ( #1033 ) \n \n \n datasette -o option now opens the most relevant page. ( #976 ) \n \n \n datasette --cors option now enables access to /database.db downloads. ( #1057 ) \n \n \n Database file downloads now implement cascading permissions, so you can download a database if you have view-database-download permission even if you do not have permission to access the Datasette instance. ( #1058 ) \n \n \n New documentation on Designing URLs for your plugin . ( #1053 )", "breadcrumbs": "[\"Changelog\", \"0.51 (2020-10-31)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/998\", \"label\": \"#998\"}, {\"href\": \"https://github.com/simonw/datasette-cluster-map/commit/fcb4abbe7df9071c5ab57defd39147de7145b34e\", \"label\": \"this change to datasette-cluster-map\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1068\", \"label\": \"#1068\"}, {\"href\": \"https://github.com/simonw/datasette/issues/814\", \"label\": \"#814\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1014\", \"label\": \"#1014\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1016\", \"label\": \"#1016\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1019\", \"label\": \"#1019\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1028\", \"label\": \"#1028\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1039\", \"label\": \"#1039\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1045\", \"label\": \"#1045\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1033\", \"label\": \"#1033\"}, {\"href\": \"https://github.com/simonw/datasette/issues/976\", \"label\": \"#976\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1057\", \"label\": \"#1057\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1058\", \"label\": \"#1058\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1053\", \"label\": \"#1053\"}]"} {"id": "custom_templates:customization-css-and-javascript", "page": "custom_templates", "ref": "customization-css-and-javascript", "title": "Custom CSS and JavaScript", "content": "When you launch Datasette, you can specify a custom metadata file like this: \n datasette mydb.db --metadata metadata.json \n Your metadata.json file can include links that look like this: \n {\n \"extra_css_urls\": [\n \"https://simonwillison.net/static/css/all.bf8cd891642c.css\"\n ],\n \"extra_js_urls\": [\n \"https://code.jquery.com/jquery-3.2.1.slim.min.js\"\n ]\n} \n The extra CSS and JavaScript files will be linked in the <head> of every page: \n <link rel=\"stylesheet\" href=\"https://simonwillison.net/static/css/all.bf8cd891642c.css\">\n<script src=\"https://code.jquery.com/jquery-3.2.1.slim.min.js\"></script> \n You can also specify a SRI (subresource integrity hash) for these assets: \n {\n \"extra_css_urls\": [\n {\n \"url\": \"https://simonwillison.net/static/css/all.bf8cd891642c.css\",\n \"sri\": \"sha384-9qIZekWUyjCyDIf2YK1FRoKiPJq4PHt6tp/ulnuuyRBvazd0hG7pWbE99zvwSznI\"\n }\n ],\n \"extra_js_urls\": [\n {\n \"url\": \"https://code.jquery.com/jquery-3.2.1.slim.min.js\",\n \"sri\": \"sha256-k2WSCIexGzOj3Euiig+TlR8gA0EmPjuc79OEeY5L45g=\"\n }\n ]\n} \n This will produce: \n <link rel=\"stylesheet\" href=\"https://simonwillison.net/static/css/all.bf8cd891642c.css\"\n integrity=\"sha384-9qIZekWUyjCyDIf2YK1FRoKiPJq4PHt6tp/ulnuuyRBvazd0hG7pWbE99zvwSznI\"\n crossorigin=\"anonymous\">\n<script src=\"https://code.jquery.com/jquery-3.2.1.slim.min.js\"\n integrity=\"sha256-k2WSCIexGzOj3Euiig+TlR8gA0EmPjuc79OEeY5L45g=\"\n crossorigin=\"anonymous\"></script> \n Modern browsers will only execute the stylesheet or JavaScript if the SRI hash\n matches the content served. You can generate hashes using www.srihash.org \n Items in \"extra_js_urls\" can specify \"module\": true if they reference JavaScript that uses JavaScript modules . This configuration: \n {\n \"extra_js_urls\": [\n {\n \"url\": \"https://example.datasette.io/module.js\",\n \"module\": true\n }\n ]\n} \n Will produce this HTML: \n <script type=\"module\" src=\"https://example.datasette.io/module.js\"></script>", "breadcrumbs": "[\"Custom pages and templates\"]", "references": "[{\"href\": \"https://www.srihash.org/\", \"label\": \"www.srihash.org\"}, {\"href\": \"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules\", \"label\": \"JavaScript modules\"}]"} {"id": "writing_plugins:writing-plugins-configuration", "page": "writing_plugins", "ref": "writing-plugins-configuration", "title": "Writing plugins that accept configuration", "content": "When you are writing plugins, you can access plugin configuration like this using the datasette plugin_config() method. If you know you need plugin configuration for a specific table, you can access it like this: \n plugin_config = datasette.plugin_config(\n \"datasette-cluster-map\", database=\"sf-trees\", table=\"Street_Tree_List\"\n) \n This will return the {\"latitude_column\": \"lat\", \"longitude_column\": \"lng\"} in the above example. \n If there is no configuration for that plugin, the method will return None . \n If it cannot find the requested configuration at the table layer, it will fall back to the database layer and then the root layer. For example, a user may have set the plugin configuration option like so: \n {\n \"databases: {\n \"sf-trees\": {\n \"plugins\": {\n \"datasette-cluster-map\": {\n \"latitude_column\": \"xlat\",\n \"longitude_column\": \"xlng\"\n }\n }\n }\n }\n} \n In this case, the above code would return that configuration for ANY table within the sf-trees database. \n The plugin configuration could also be set at the top level of metadata.json : \n {\n \"title\": \"This is the top-level title in metadata.json\",\n \"plugins\": {\n \"datasette-cluster-map\": {\n \"latitude_column\": \"xlat\",\n \"longitude_column\": \"xlng\"\n }\n }\n} \n Now that datasette-cluster-map plugin configuration will apply to every table in every database.", "breadcrumbs": "[\"Writing plugins\"]", "references": "[]"} {"id": "testing_plugins:testing-plugins-register-in-test", "page": "testing_plugins", "ref": "testing-plugins-register-in-test", "title": "Registering a plugin for the duration of a test", "content": "When writing tests for plugins you may find it useful to register a test plugin just for the duration of a single test. You can do this using pm.register() and pm.unregister() like this: \n from datasette import hookimpl\nfrom datasette.app import Datasette\nfrom datasette.plugins import pm\nimport pytest\n\n\n@pytest.mark.asyncio\nasync def test_using_test_plugin():\n class TestPlugin:\n __name__ = \"TestPlugin\"\n\n # Use hookimpl and method names to register hooks\n @hookimpl\n def register_routes(self):\n return [\n (r\"^/error$\", lambda: 1 / 0),\n ]\n\n pm.register(TestPlugin(), name=\"undo\")\n try:\n # The test implementation goes here\n datasette = Datasette()\n response = await datasette.client.get(\"/error\")\n assert response.status_code == 500\n finally:\n pm.unregister(name=\"undo\")", "breadcrumbs": "[\"Testing plugins\"]", "references": "[]"} {"id": "changelog:foreign-key-expansions", "page": "changelog", "ref": "foreign-key-expansions", "title": "Foreign key expansions", "content": "When Datasette detects a foreign key reference it attempts to resolve a label\n for that reference (automatically or using the Specifying the label column for a table metadata\n option) so it can display a link to the associated row. \n This expansion is now also available for JSON and CSV representations of the\n table, using the new _labels=on query string option. See\n Expanding foreign key references for more details.", "breadcrumbs": "[\"Changelog\", \"0.23 (2018-06-18)\"]", "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": "full_text_search:configuring-fts-by-hand", "page": "full_text_search", "ref": "configuring-fts-by-hand", "title": "Configuring FTS by hand", "content": "We recommend using sqlite-utils , but if you want to hand-roll a SQLite full-text search table you can do so using the following SQL. \n To enable full-text search for a table called items that works against the name and description columns, you would run this SQL to create a new items_fts FTS virtual table: \n CREATE VIRTUAL TABLE \"items_fts\" USING FTS4 (\n name,\n description,\n content=\"items\"\n); \n This creates a set of tables to power full-text search against items . The new items_fts table will be detected by Datasette as the fts_table for the items table. \n Creating the table is not enough: you also need to populate it with a copy of the data that you wish to make searchable. You can do that using the following SQL: \n INSERT INTO \"items_fts\" (rowid, name, description)\n SELECT rowid, name, description FROM items; \n If your table has columns that are foreign key references to other tables you can include that data in your full-text search index using a join. Imagine the items table has a foreign key column called category_id which refers to a categories table - you could create a full-text search table like this: \n CREATE VIRTUAL TABLE \"items_fts\" USING FTS4 (\n name,\n description,\n category_name,\n content=\"items\"\n); \n And then populate it like this: \n INSERT INTO \"items_fts\" (rowid, name, description, category_name)\n SELECT items.rowid,\n items.name,\n items.description,\n categories.name\n FROM items JOIN categories ON items.category_id=categories.id; \n You can use this technique to populate the full-text search index from any combination of tables and joins that makes sense for your project.", "breadcrumbs": "[\"Full-text search\", \"Enabling full-text search for a SQLite table\"]", "references": "[{\"href\": \"https://sqlite-utils.datasette.io/\", \"label\": \"sqlite-utils\"}]"} {"id": "testing_plugins:id1", "page": "testing_plugins", "ref": "id1", "title": "Testing plugins", "content": "We recommend using pytest to write automated tests for your plugins. \n If you use the template described in Starting an installable plugin using cookiecutter your plugin will start with a single test in your tests/ directory that looks like this: \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 installed_plugins = {p[\"name\"] for p in response.json()}\n assert (\n \"datasette-plugin-template-demo\"\n in installed_plugins\n ) \n This test uses the datasette.client object to exercise a test instance of Datasette. datasette.client is a wrapper around the HTTPX Python library which can imitate HTTP requests using ASGI. This is the recommended way to write tests against a Datasette instance. \n This test also uses the pytest-asyncio package to add support for async def test functions running under pytest. \n You can install these packages like so: \n pip install pytest pytest-asyncio \n If you are building an installable package you can add them as test dependencies to your setup.py module like this: \n setup(\n name=\"datasette-my-plugin\",\n # ...\n extras_require={\"test\": [\"pytest\", \"pytest-asyncio\"]},\n tests_require=[\"datasette-my-plugin[test]\"],\n) \n You can then install the test dependencies like so: \n pip install -e '.[test]' \n Then run the tests using pytest like so: \n pytest", "breadcrumbs": "[]", "references": "[{\"href\": \"https://docs.pytest.org/\", \"label\": \"pytest\"}, {\"href\": \"https://www.python-httpx.org/\", \"label\": \"HTTPX\"}, {\"href\": \"https://pypi.org/project/pytest-asyncio/\", \"label\": \"pytest-asyncio\"}]"} {"id": "changelog:id83", "page": "changelog", "ref": "id83", "title": "Small changes", "content": "We now show the size of the database file next to the download link ( #172 ) \n \n \n New /-/databases introspection page shows currently connected databases ( #470 ) \n \n \n Binary data is no longer displayed on the table and row pages ( #442 - thanks, Russ Garrett) \n \n \n New show/hide SQL links on custom query pages ( #415 ) \n \n \n The extra_body_script plugin hook now accepts an optional view_name argument ( #443 - thanks, Russ Garrett) \n \n \n Bumped Jinja2 dependency to 2.10.1 ( #426 ) \n \n \n All table filters are now documented, and documentation is enforced via unit tests ( 2c19a27 ) \n \n \n New project guideline: master should stay shippable at all times! ( 31f36e1 ) \n \n \n Fixed a bug where sqlite_timelimit() occasionally failed to clean up after itself ( bac4e01 ) \n \n \n We no longer load additional plugins when executing pytest ( #438 ) \n \n \n Homepage now links to database views if there are less than five tables in a database ( #373 ) \n \n \n The --cors option is now respected by error pages ( #453 ) \n \n \n datasette publish heroku now uses the --include-vcs-ignore option, which means it works under Travis CI ( #407 ) \n \n \n datasette publish heroku now publishes using Python 3.6.8 ( 666c374 ) \n \n \n Renamed datasette publish now to datasette publish nowv1 ( #472 ) \n \n \n datasette publish nowv1 now accepts multiple --alias parameters ( 09ef305 ) \n \n \n Removed the datasette skeleton command ( #476 ) \n \n \n The documentation on how to build the documentation now recommends sphinx-autobuild", "breadcrumbs": "[\"Changelog\", \"0.28 (2019-05-19)\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/172\", \"label\": \"#172\"}, {\"href\": \"https://github.com/simonw/datasette/issues/470\", \"label\": \"#470\"}, {\"href\": \"https://github.com/simonw/datasette/pull/442\", \"label\": \"#442\"}, {\"href\": \"https://github.com/simonw/datasette/issues/415\", \"label\": \"#415\"}, {\"href\": \"https://github.com/simonw/datasette/pull/443\", \"label\": \"#443\"}, {\"href\": \"https://github.com/simonw/datasette/pull/426\", \"label\": \"#426\"}, {\"href\": \"https://github.com/simonw/datasette/commit/2c19a27d15a913e5f3dd443f04067169a6f24634\", \"label\": \"2c19a27\"}, {\"href\": \"https://github.com/simonw/datasette/commit/31f36e1b97ccc3f4387c80698d018a69798b6228\", \"label\": \"31f36e1\"}, {\"href\": \"https://github.com/simonw/datasette/commit/bac4e01f40ae7bd19d1eab1fb9349452c18de8f5\", \"label\": \"bac4e01\"}, {\"href\": \"https://github.com/simonw/datasette/issues/438\", \"label\": \"#438\"}, {\"href\": \"https://github.com/simonw/datasette/issues/373\", \"label\": \"#373\"}, {\"href\": \"https://github.com/simonw/datasette/issues/453\", \"label\": \"#453\"}, {\"href\": \"https://github.com/simonw/datasette/pull/407\", \"label\": \"#407\"}, {\"href\": \"https://github.com/simonw/datasette/commit/666c37415a898949fae0437099d62a35b1e9c430\", \"label\": \"666c374\"}, {\"href\": \"https://github.com/simonw/datasette/issues/472\", \"label\": \"#472\"}, {\"href\": \"https://github.com/simonw/datasette/commit/09ef305c687399384fe38487c075e8669682deb4\", \"label\": \"09ef305\"}, {\"href\": \"https://github.com/simonw/datasette/issues/476\", \"label\": \"#476\"}]"} {"id": "publish:publish-vercel", "page": "publish", "ref": "publish-vercel", "title": "Publishing to Vercel", "content": "Vercel - previously known as Zeit Now - provides a layer over AWS Lambda to allow for quick, scale-to-zero deployment. You can deploy Datasette instances to Vercel using the datasette-publish-vercel plugin. \n pip install datasette-publish-vercel\ndatasette publish vercel mydatabase.db --project my-database-project \n Not every feature is supported: consult the datasette-publish-vercel README for more details.", "breadcrumbs": "[\"Publishing data\", \"datasette publish\"]", "references": "[{\"href\": \"https://vercel.com/\", \"label\": \"Vercel\"}, {\"href\": \"https://github.com/simonw/datasette-publish-vercel\", \"label\": \"datasette-publish-vercel\"}, {\"href\": \"https://github.com/simonw/datasette-publish-vercel/blob/main/README.md\", \"label\": \"datasette-publish-vercel README\"}]"} {"id": "changelog:id209", "page": "changelog", "ref": "id209", "title": "0.8 (2017-11-13)", "content": "V0.8 - added PyPI metadata, ready to ship. \n \n \n Implemented offset/limit pagination for views ( #70 ). \n \n \n Improved pagination. ( #78 ) \n \n \n Limit on max rows returned, controlled by --max_returned_rows option. ( #69 ) \n If someone executes 'select * from table' against a table with a million rows\n in it, we could run into problems: just serializing that much data as JSON is\n likely to lock up the server. \n Solution: we now have a hard limit on the maximum number of rows that can be\n returned by a query. If that limit is exceeded, the server will return a\n \"truncated\": true field in the JSON. \n This limit can be optionally controlled by the new --max_returned_rows \n option. Setting that option to 0 disables the limit entirely.", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/70\", \"label\": \"#70\"}, {\"href\": \"https://github.com/simonw/datasette/issues/78\", \"label\": \"#78\"}, {\"href\": \"https://github.com/simonw/datasette/issues/69\", \"label\": \"#69\"}]"} {"id": "internals:internals-utils-await-me-maybe", "page": "internals", "ref": "internals-utils-await-me-maybe", "title": "await_me_maybe(value)", "content": "Utility function for calling await on a return value if it is awaitable, otherwise returning the value. This is used by Datasette to support plugin hooks that can optionally return awaitable functions. Read more about this function in The \u201cawait me maybe\u201d pattern for Python asyncio . \n \n \n async datasette.utils. await_me_maybe value : Any Any \n \n If value is callable, call it. If awaitable, await it. Otherwise return it.", "breadcrumbs": "[\"Internals for plugins\", \"The datasette.utils module\"]", "references": "[{\"href\": \"https://simonwillison.net/2020/Sep/2/await-me-maybe/\", \"label\": \"The \u201cawait me maybe\u201d pattern for Python asyncio\"}]"} {"id": "cli-reference:cli-help-uninstall-help", "page": "cli-reference", "ref": "cli-help-uninstall-help", "title": "datasette uninstall", "content": "Uninstall one or more plugins. \n [[[cog\nhelp([\"uninstall\", \"--help\"]) \n ]]] \n Usage: datasette uninstall [OPTIONS] PACKAGES...\n\n Uninstall plugins and Python packages from the Datasette environment\n\nOptions:\n -y, --yes Don't ask for confirmation\n --help Show this message and exit. \n [[[end]]]", "breadcrumbs": "[\"CLI reference\"]", "references": "[]"} {"id": "authentication:permissions-view-instance", "page": "authentication", "ref": "permissions-view-instance", "title": "view-instance", "content": "Top level permission - Actor is allowed to view any pages within this instance, starting at https://latest.datasette.io/ \n Default allow .", "breadcrumbs": "[\"Authentication and permissions\", \"Built-in permissions\"]", "references": "[{\"href\": \"https://latest.datasette.io/\", \"label\": \"https://latest.datasette.io/\"}]"} {"id": "facets:facets-in-query-strings", "page": "facets", "ref": "facets-in-query-strings", "title": "Facets in query strings", "content": "To turn on faceting for specific columns on a Datasette table view, add one or more _facet=COLUMN parameters to the URL.\n For example, if you want to turn on facets for the city_id and state columns, construct a URL that looks like this: \n /dbname/tablename?_facet=state&_facet=city_id \n This works for both the HTML interface and the .json view.\n When enabled, facets will cause a facet_results block to be added to the JSON output, looking something like this: \n {\n \"state\": {\n \"name\": \"state\",\n \"results\": [\n {\n \"value\": \"CA\",\n \"label\": \"CA\",\n \"count\": 10,\n \"toggle_url\": \"http://...?_facet=city_id&_facet=state&state=CA\",\n \"selected\": false\n },\n {\n \"value\": \"MI\",\n \"label\": \"MI\",\n \"count\": 4,\n \"toggle_url\": \"http://...?_facet=city_id&_facet=state&state=MI\",\n \"selected\": false\n },\n {\n \"value\": \"MC\",\n \"label\": \"MC\",\n \"count\": 1,\n \"toggle_url\": \"http://...?_facet=city_id&_facet=state&state=MC\",\n \"selected\": false\n }\n ],\n \"truncated\": false\n }\n \"city_id\": {\n \"name\": \"city_id\",\n \"results\": [\n {\n \"value\": 1,\n \"label\": \"San Francisco\",\n \"count\": 6,\n \"toggle_url\": \"http://...?_facet=city_id&_facet=state&city_id=1\",\n \"selected\": false\n },\n {\n \"value\": 2,\n \"label\": \"Los Angeles\",\n \"count\": 4,\n \"toggle_url\": \"http://...?_facet=city_id&_facet=state&city_id=2\",\n \"selected\": false\n },\n {\n \"value\": 3,\n \"label\": \"Detroit\",\n \"count\": 4,\n \"toggle_url\": \"http://...?_facet=city_id&_facet=state&city_id=3\",\n \"selected\": false\n },\n {\n \"value\": 4,\n \"label\": \"Memnonia\",\n \"count\": 1,\n \"toggle_url\": \"http://...?_facet=city_id&_facet=state&city_id=4\",\n \"selected\": false\n }\n ],\n \"truncated\": false\n }\n} \n If Datasette detects that a column is a foreign key, the \"label\" property will be automatically derived from the detected label column on the referenced table. \n The default number of facet results returned is 30, controlled by the default_facet_size setting.\n You can increase this on an individual page by adding ?_facet_size=100 to the query string, up to a maximum of max_returned_rows (which defaults to 1000).", "breadcrumbs": "[\"Facets\"]", "references": "[]"} {"id": "internals:internals-response-set-cookie", "page": "internals", "ref": "internals-response-set-cookie", "title": "Setting cookies with response.set_cookie()", "content": "To set cookies on the response, use the response.set_cookie(...) method. The method signature looks like this: \n def set_cookie(\n self,\n key,\n value=\"\",\n max_age=None,\n expires=None,\n path=\"/\",\n domain=None,\n secure=False,\n httponly=False,\n samesite=\"lax\",\n):\n ... \n You can use this with datasette.sign() to set signed cookies. Here's how you would set the ds_actor cookie for use with Datasette authentication : \n response = Response.redirect(\"/\")\nresponse.set_cookie(\n \"ds_actor\",\n datasette.sign({\"a\": {\"id\": \"cleopaws\"}}, \"actor\"),\n)\nreturn response", "breadcrumbs": "[\"Internals for plugins\", \"Response class\"]", "references": "[]"} {"id": "contributing:contributing-using-fixtures", "page": "contributing", "ref": "contributing-using-fixtures", "title": "Using fixtures", "content": "To run Datasette itself, type datasette . \n You're going to need at least one SQLite database. A quick way to get started is to use the fixtures database that Datasette uses for its own tests. \n You can create a copy of that database by running this command: \n python tests/fixtures.py fixtures.db \n Now you can run Datasette against the new fixtures database like so: \n datasette fixtures.db \n This will start a server at http://127.0.0.1:8001/ . \n Any changes you make in the datasette/templates or datasette/static folder will be picked up immediately (though you may need to do a force-refresh in your browser to see changes to CSS or JavaScript). \n If you want to change Datasette's Python code you can use the --reload option to cause Datasette to automatically reload any time the underlying code changes: \n datasette --reload fixtures.db \n You can also use the fixtures.py script to recreate the testing version of metadata.json used by the unit tests. To do that: \n python tests/fixtures.py fixtures.db fixtures-metadata.json \n Or to output the plugins used by the tests, run this: \n python tests/fixtures.py fixtures.db fixtures-metadata.json fixtures-plugins\nTest tables written to fixtures.db\n- metadata written to fixtures-metadata.json\nWrote plugin: fixtures-plugins/register_output_renderer.py\nWrote plugin: fixtures-plugins/view_name.py\nWrote plugin: fixtures-plugins/my_plugin.py\nWrote plugin: fixtures-plugins/messages_output_renderer.py\nWrote plugin: fixtures-plugins/my_plugin_2.py \n Then run Datasette like this: \n datasette fixtures.db -m fixtures-metadata.json --plugins-dir=fixtures-plugins/", "breadcrumbs": "[\"Contributing\"]", "references": "[]"} {"id": "publish:publish-heroku", "page": "publish", "ref": "publish-heroku", "title": "Publishing to Heroku", "content": "To publish your data using Heroku , first create an account there and install and configure the Heroku CLI tool . \n You can publish one or more databases to Heroku using the following command: \n datasette publish heroku mydatabase.db \n This will output some details about the new deployment, including a URL like this one: \n https://limitless-reef-88278.herokuapp.com/ deployed to Heroku \n You can specify a custom app name by passing -n my-app-name to the publish command. This will also allow you to overwrite an existing app. \n Rather than deploying directly you can use the --generate-dir option to output the files that would be deployed to a directory: \n datasette publish heroku mydatabase.db --generate-dir=/tmp/deploy-this-to-heroku \n See datasette publish heroku for the full list of options for this command.", "breadcrumbs": "[\"Publishing data\", \"datasette publish\"]", "references": "[{\"href\": \"https://www.heroku.com/\", \"label\": \"Heroku\"}, {\"href\": \"https://devcenter.heroku.com/articles/heroku-cli\", \"label\": \"Heroku CLI tool\"}]"} {"id": "authentication:authentication-permissions-table", "page": "authentication", "ref": "authentication-permissions-table", "title": "Controlling access to specific tables and views", "content": "To limit access to the users table in your bakery.db database: \n {\n \"databases\": {\n \"bakery\": {\n \"tables\": {\n \"users\": {\n \"allow\": {\n \"id\": \"*\"\n }\n }\n }\n }\n }\n} \n This works for SQL views as well - you can list their names in the \"tables\" block above in the same way as regular tables. \n \n Restricting access to tables and views in this way will NOT prevent users from querying them using arbitrary SQL queries, like this for example. \n If you are restricting access to specific tables you should also use the \"allow_sql\" block to prevent users from bypassing the limit with their own SQL queries - see Controlling the ability to execute arbitrary SQL .", "breadcrumbs": "[\"Authentication and permissions\", \"Configuring permissions in metadata.json\"]", "references": "[{\"href\": \"https://latest.datasette.io/fixtures?sql=select+*+from+facetable\", \"label\": \"like this\"}]"} {"id": "authentication:authentication-permissions-database", "page": "authentication", "ref": "authentication-permissions-database", "title": "Controlling access to specific databases", "content": "To limit access to a specific private.db database to just authenticated users, use the \"allow\" block like this: \n {\n \"databases\": {\n \"private\": {\n \"allow\": {\n \"id\": \"*\"\n }\n }\n }\n}", "breadcrumbs": "[\"Authentication and permissions\", \"Configuring permissions in metadata.json\"]", "references": "[]"} {"id": "contributing:contributing-formatting-prettier", "page": "contributing", "ref": "contributing-formatting-prettier", "title": "Prettier", "content": "To install Prettier, install Node.js and then run the following in the root of your datasette repository checkout: \n $ npm install \n This will install Prettier in a node_modules directory. You can then check that your code matches the coding style like so: \n $ npm run prettier -- --check\n> prettier\n> prettier 'datasette/static/*[!.min].js' \"--check\"\n\nChecking formatting...\n[warn] datasette/static/plugins.js\n[warn] Code style issues found in the above file(s). Forgot to run Prettier? \n You can fix any problems by running: \n $ npm run fix", "breadcrumbs": "[\"Contributing\", \"Code formatting\"]", "references": "[{\"href\": \"https://nodejs.org/en/download/package-manager/\", \"label\": \"install Node.js\"}]"} {"id": "custom_templates:custom-pages-404", "page": "custom_templates", "ref": "custom-pages-404", "title": "Returning 404s", "content": "To indicate that content could not be found and display the default 404 page you can use the raise_404(message) function: \n {% if not rows %}\n {{ raise_404(\"Content not found\") }}\n{% endif %} \n If you call raise_404() the other content in your template will be ignored.", "breadcrumbs": "[\"Custom pages and templates\", \"Custom pages\"]", "references": "[]"} {"id": "changelog:id84", "page": "changelog", "ref": "id84", "title": "0.27.1 (2019-05-09)", "content": "Tiny bugfix release: don't install tests/ in the wrong place. Thanks, Veit Heller.", "breadcrumbs": "[\"Changelog\"]", "references": "[]"} {"id": "authentication:authentication-actor", "page": "authentication", "ref": "authentication-actor", "title": "Actors", "content": "Through plugins, Datasette can support both authenticated users (with cookies) and authenticated API agents (via authentication tokens). The word \"actor\" is used to cover both of these cases. \n Every request to Datasette has an associated actor value, available in the code as request.actor . This can be None for unauthenticated requests, or a JSON compatible Python dictionary for authenticated users or API agents. \n The actor dictionary can be any shape - the design of that data structure is left up to the plugins. A useful convention is to include an \"id\" string, as demonstrated by the \"root\" actor below. \n Plugins can use the actor_from_request(datasette, request) hook to implement custom logic for authenticating an actor based on the incoming HTTP request.", "breadcrumbs": "[\"Authentication and permissions\"]", "references": "[]"} {"id": "changelog:id74", "page": "changelog", "ref": "id74", "title": "0.31 (2019-11-11)", "content": "This version adds compatibility with Python 3.8 and breaks compatibility with Python 3.5. \n If you are still running Python 3.5 you should stick with 0.30.2 , which you can install like this: \n pip install datasette==0.30.2 \n \n \n Format SQL button now works with read-only SQL queries - thanks, Tobias Kunze ( #602 ) \n \n \n New ?column__notin=x,y,z filter for table views ( #614 ) \n \n \n Table view now uses select col1, col2, col3 instead of select * \n \n \n Database filenames can now contain spaces - thanks, Tobias Kunze ( #590 ) \n \n \n Removed obsolete ?_group_count=col feature ( #504 ) \n \n \n Improved user interface and documentation for datasette publish cloudrun ( #608 ) \n \n \n Tables with indexes now show the CREATE INDEX statements on the table page ( #618 ) \n \n \n Current version of uvicorn is now shown on /-/versions \n \n \n Python 3.8 is now supported! ( #622 ) \n \n \n Python 3.5 is no longer supported.", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/pull/602\", \"label\": \"#602\"}, {\"href\": \"https://github.com/simonw/datasette/issues/614\", \"label\": \"#614\"}, {\"href\": \"https://github.com/simonw/datasette/pull/590\", \"label\": \"#590\"}, {\"href\": \"https://github.com/simonw/datasette/issues/504\", \"label\": \"#504\"}, {\"href\": \"https://github.com/simonw/datasette/issues/608\", \"label\": \"#608\"}, {\"href\": \"https://github.com/simonw/datasette/issues/618\", \"label\": \"#618\"}, {\"href\": \"https://www.uvicorn.org/\", \"label\": \"uvicorn\"}, {\"href\": \"https://github.com/simonw/datasette/issues/622\", \"label\": \"#622\"}]"} {"id": "plugin_hooks:plugin-hook-extra-js-urls", "page": "plugin_hooks", "ref": "plugin-hook-extra-js-urls", "title": "extra_js_urls(template, database, table, columns, view_name, request, datasette)", "content": "This takes the same arguments as extra_template_vars(...) \n This works in the same way as extra_css_urls() but for JavaScript. You can\n return a list of URLs, a list of dictionaries or an awaitable function that returns those things: \n from datasette import hookimpl\n\n\n@hookimpl\ndef extra_js_urls():\n return [\n {\n \"url\": \"https://code.jquery.com/jquery-3.3.1.slim.min.js\",\n \"sri\": \"sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo\",\n }\n ] \n You can also return URLs to files from your plugin's static/ directory, if\n you have one: \n @hookimpl\ndef extra_js_urls():\n return [\"/-/static-plugins/your-plugin/app.js\"] \n Note that your-plugin here should be the hyphenated plugin name - the name that is displayed in the list on the /-/plugins debug page. \n If your code uses JavaScript modules you should include the \"module\": True key. See Custom CSS and JavaScript for more details. \n @hookimpl\ndef extra_js_urls():\n return [\n {\n \"url\": \"/-/static-plugins/your-plugin/app.js\",\n \"module\": True,\n }\n ] \n Examples: datasette-cluster-map , datasette-vega", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules\", \"label\": \"JavaScript modules\"}, {\"href\": \"https://datasette.io/plugins/datasette-cluster-map\", \"label\": \"datasette-cluster-map\"}, {\"href\": \"https://datasette.io/plugins/datasette-vega\", \"label\": \"datasette-vega\"}]"} {"id": "plugin_hooks:plugin-hook-extra-css-urls", "page": "plugin_hooks", "ref": "plugin-hook-extra-css-urls", "title": "extra_css_urls(template, database, table, columns, view_name, request, datasette)", "content": "This takes the same arguments as extra_template_vars(...) \n Return a list of extra CSS URLs that should be included on the page. These can\n take advantage of the CSS class hooks described in Custom pages and templates . \n This can be a list of URLs: \n from datasette import hookimpl\n\n\n@hookimpl\ndef extra_css_urls():\n return [\n \"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css\"\n ] \n Or a list of dictionaries defining both a URL and an\n SRI hash : \n @hookimpl\ndef extra_css_urls():\n return [\n {\n \"url\": \"https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css\",\n \"sri\": \"sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4\",\n }\n ] \n This function can also return an awaitable function, useful if it needs to run any async code: \n @hookimpl\ndef extra_css_urls(datasette):\n async def inner():\n db = datasette.get_database()\n results = await db.execute(\n \"select url from css_files\"\n )\n return [r[0] for r in results]\n\n return inner \n Examples: datasette-cluster-map , datasette-vega", "breadcrumbs": "[\"Plugin hooks\"]", "references": "[{\"href\": \"https://www.srihash.org/\", \"label\": \"SRI hash\"}, {\"href\": \"https://datasette.io/plugins/datasette-cluster-map\", \"label\": \"datasette-cluster-map\"}, {\"href\": \"https://datasette.io/plugins/datasette-vega\", \"label\": \"datasette-vega\"}]"} {"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-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": "authentication:id1", "page": "authentication", "ref": "id1", "title": "Built-in permissions", "content": "This section lists all of the permission checks that are carried out by Datasette core, along with the resource if it was passed.", "breadcrumbs": "[\"Authentication and permissions\"]", "references": "[]"} {"id": "changelog:id149", "page": "changelog", "ref": "id149", "title": "0.18 (2018-04-14)", "content": "This release introduces support for units ,\n contributed by Russ Garrett ( #203 ).\n You can now optionally specify the units for specific columns using metadata.json .\n Once specified, units will be displayed in the HTML view of your table. They also become\n available for use in filters - if a column is configured with a unit of distance, you can\n request all rows where that column is less than 50 meters or more than 20 feet for example. \n \n \n Link foreign keys which don't have labels. [Russ Garrett] \n This renders unlabeled FKs as simple links. \n Also includes bonus fixes for two minor issues: \n \n \n In foreign key link hrefs the primary key was escaped using HTML\n escaping rather than URL escaping. This broke some non-integer PKs. \n \n \n Print tracebacks to console when handling 500 errors. \n \n \n \n \n Fix SQLite error when loading rows with no incoming FKs. [Russ\n Garrett] \n This fixes an error caused by an invalid query when loading incoming FKs. \n The error was ignored due to async but it still got printed to the\n console. \n \n \n Allow custom units to be registered with Pint. [Russ Garrett] \n \n \n Support units in filters. [Russ Garrett] \n \n \n Tidy up units support. [Russ Garrett] \n \n \n Add units to exported JSON \n \n \n Units key in metadata skeleton \n \n \n Docs \n \n \n \n \n Initial units support. [Russ Garrett] \n Add support for specifying units for a column in metadata.json and\n rendering them on display using\n pint", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://docs.datasette.io/en/stable/metadata.html#specifying-units-for-a-column\", \"label\": \"support for units\"}, {\"href\": \"https://github.com/simonw/datasette/issues/203\", \"label\": \"#203\"}, {\"href\": \"https://pint.readthedocs.io/en/latest/\", \"label\": \"pint\"}]"} {"id": "changelog:id41", "page": "changelog", "ref": "id41", "title": "0.52 (2020-11-28)", "content": "This release includes a number of changes relating to an internal rebranding effort: Datasette's configuration mechanism (things like datasette --config default_page_size:10 ) has been renamed to settings . \n \n \n New --setting default_page_size 10 option as a replacement for --config default_page_size:10 (note the lack of a colon). The --config option is deprecated but will continue working until Datasette 1.0. ( #992 ) \n \n \n The /-/config introspection page is now /-/settings , and the previous page redirects to the new one. ( #1103 ) \n \n \n The config.json file in Configuration directory mode is now called settings.json . ( #1104 ) \n \n \n The undocumented datasette.config() internal method has been replaced by a documented .setting(key) method. ( #1107 ) \n \n \n Also in this release: \n \n \n New plugin hook: database_actions(datasette, actor, database, request) , which adds menu items to a new cog menu shown at the top of the database page. ( #1077 ) \n \n \n datasette publish cloudrun has a new --apt-get-install option that can be used to install additional Ubuntu packages as part of the deployment. This is useful for deploying the new datasette-ripgrep plugin . ( #1110 ) \n \n \n Swept the documentation to remove words that minimize involved difficulty. ( #1089 ) \n \n \n And some bug fixes: \n \n \n Foreign keys linking to rows with blank label columns now display as a hyphen, allowing those links to be clicked. ( #1086 ) \n \n \n Fixed bug where row pages could sometimes 500 if the underlying queries exceeded a time limit. ( #1088 ) \n \n \n Fixed a bug where the table action menu could appear partially obscured by the edge of the page. ( #1084 )", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/992\", \"label\": \"#992\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1103\", \"label\": \"#1103\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1104\", \"label\": \"#1104\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1107\", \"label\": \"#1107\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1077\", \"label\": \"#1077\"}, {\"href\": \"https://github.com/simonw/datasette-ripgrep\", \"label\": \"datasette-ripgrep plugin\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1110\", \"label\": \"#1110\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1089\", \"label\": \"#1089\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1086\", \"label\": \"#1086\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1088\", \"label\": \"#1088\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1084\", \"label\": \"#1084\"}]"} {"id": "changelog:id30", "page": "changelog", "ref": "id30", "title": "0.56.1 (2021-06-05)", "content": "This release fixes a reflected cross-site scripting security hole with the ?_trace=1 feature. You should upgrade to this version, or to Datasette 0.57, as soon as possible. ( #1360 )", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://owasp.org/www-community/attacks/xss/#reflected-xss-attacks\", \"label\": \"reflected cross-site scripting\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1360\", \"label\": \"#1360\"}]"} {"id": "changelog:id29", "page": "changelog", "ref": "id29", "title": "0.57 (2021-06-05)", "content": "This release fixes a reflected cross-site scripting security hole with the ?_trace=1 feature. You should upgrade to this version, or to Datasette 0.56.1, as soon as possible. ( #1360 ) \n \n In addition to the security fix, this release includes ?_col= and ?_nocol= options for controlling which columns are displayed for a table, ?_facet_size= for increasing the number of facet results returned, re-display of your SQL query should an error occur and numerous bug fixes.", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://owasp.org/www-community/attacks/xss/#reflected-xss-attacks\", \"label\": \"reflected cross-site scripting\"}, {\"href\": \"https://github.com/simonw/datasette/issues/1360\", \"label\": \"#1360\"}]"} {"id": "changelog:id111", "page": "changelog", "ref": "id111", "title": "0.23 (2018-06-18)", "content": "This release features CSV export, improved options for foreign key expansions,\n new configuration settings and improved support for SpatiaLite. \n See datasette/compare/0.22.1...0.23 for a full list of\n commits added since the last release.", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/compare/0.22.1...0.23\", \"label\": \"datasette/compare/0.22.1...0.23\"}]"} {"id": "changelog:id54", "page": "changelog", "ref": "id54", "title": "0.46 (2020-08-09)", "content": "This release contains a security fix related to authenticated writable canned queries. If you are using this feature you should upgrade as soon as possible. \n \n \n \n Security fix: CSRF tokens were incorrectly included in read-only canned query forms, which could allow them to be leaked to a sophisticated attacker. See issue 918 for details. \n \n \n Datasette now supports GraphQL via the new datasette-graphql plugin - see GraphQL in Datasette with the new datasette-graphql plugin . \n \n \n Principle git branch has been renamed from master to main . ( #849 ) \n \n \n New debugging tool: /-/allow-debug tool ( demo here ) helps test allow blocks against actors, as described in Defining permissions with \"allow\" blocks . ( #908 ) \n \n \n New logo for the documentation, and a new project tagline: \"An open source multi-tool for exploring and publishing data\". \n \n \n Whitespace in column values is now respected on display, using white-space: pre-wrap . ( #896 ) \n \n \n New await request.post_body() method for accessing the raw POST body, see Request object . ( #897 ) \n \n \n Database file downloads now include a content-length HTTP header, enabling download progress bars. ( #905 ) \n \n \n File downloads now also correctly set the suggested file name using a content-disposition HTTP header. ( #909 ) \n \n \n tests are now excluded from the Datasette package properly - thanks, abeyerpath. ( #456 ) \n \n \n The Datasette package published to PyPI now includes sdist as well as bdist_wheel . \n \n \n Better titles for canned query pages. ( #887 ) \n \n \n Now only loads Python files from a directory passed using the --plugins-dir option - thanks, Amjith Ramanujam. ( #890 ) \n \n \n New documentation section on Publishing to Vercel .", "breadcrumbs": "[\"Changelog\"]", "references": "[{\"href\": \"https://github.com/simonw/datasette/issues/918\", \"label\": \"issue 918\"}, {\"href\": \"https://github.com/simonw/datasette-graphql\", \"label\": \"datasette-graphql\"}, {\"href\": \"https://simonwillison.net/2020/Aug/7/datasette-graphql/\", \"label\": \"GraphQL in Datasette with the new datasette-graphql plugin\"}, {\"href\": \"https://github.com/simonw/datasette/issues/849\", \"label\": \"#849\"}, {\"href\": \"https://latest.datasette.io/-/allow-debug\", \"label\": \"demo here\"}, {\"href\": \"https://github.com/simonw/datasette/issues/908\", \"label\": \"#908\"}, {\"href\": \"https://github.com/simonw/datasette/issues/896\", \"label\": \"#896\"}, {\"href\": \"https://github.com/simonw/datasette/issues/897\", \"label\": \"#897\"}, {\"href\": \"https://github.com/simonw/datasette/issues/905\", \"label\": \"#905\"}, {\"href\": \"https://github.com/simonw/datasette/issues/909\", \"label\": \"#909\"}, {\"href\": \"https://github.com/simonw/datasette/issues/456\", \"label\": \"#456\"}, {\"href\": \"https://github.com/simonw/datasette/issues/887\", \"label\": \"#887\"}, {\"href\": \"https://github.com/simonw/datasette/pull/890\", \"label\": \"#890\"}]"}