sections_fts
766 rows
This data as json, CSV (advanced)
| Link | rowid ▼ | title | content | sections_fts | rank |
|---|---|---|---|---|---|
| 1 | 1 | Writing plugins | 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. 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. | 220 | |
| 2 | 2 | Tracing plugin hooks | The DATASETTE_TRACE_PLUGINS environment variable turns on detailed tracing showing exactly which hooks are being run. This can be useful for understanding how Datasette is using your plugin. DATASETTE_TRACE_PLUGINS=1 datasette mydb.db Example output: actor_from_request: { 'datasette': <datasette.app.Datasette object at 0x100bc7220>, 'request': <asgi.Request method="GET" url="http://127.0.0.1:4433/">} Hook implementations: [ <HookImpl plugin_name='codespaces', plugin=<module 'datasette_codespaces' from '.../site-packages/datasette_codespaces/__init__.py'>>, <HookImpl plugin_name='datasette.actor_auth_cookie', plugin=<module 'datasette.actor_auth_cookie' from '.../datasette/datasette/actor_auth_cookie.py'>>, <HookImpl plugin_name='datasette.default_permissions', plugin=<module 'datasette.default_permissions' from '.../datasette/default_permissions.py'>>] Results: [{'id': 'root'}] | 220 | |
| 3 | 3 | Writing one-off plugins | The quickest way to start writing a plugin is to create a my_plugin.py file and drop it into your plugins/ directory. Here is an example plugin, which adds a new custom SQL function called hello_world() which takes no arguments and returns the string Hello world! . from datasette import hookimpl @hookimpl def prepare_connection(conn): conn.create_function( "hello_world", 0, lambda: "Hello world!" ) If you save this in plugins/my_plugin.py you can then start Datasette like this: datasette serve mydb.db --plugins-dir=plugins/ Now you can navigate to http://localhost:8001/mydb and run this SQL: select hello_world(); To see the output of your plugin. | 220 | |
| 4 | 4 | Starting an installable plugin using cookiecutter | Plugins that can be installed should be written as Python packages using a setup.py file. The quickest way to start writing one an installable plugin is to use the datasette-plugin cookiecutter template. This creates a new plugin structure for you complete with an example test and GitHub Actions workflows for testing and publishing your plugin. Install cookiecutter and then run this command to start building a plugin using the template: cookiecutter gh:simonw/datasette-plugin Read a cookiecutter template for writing Datasette plugins for more information about this template. | 220 | |
| 5 | 5 | Packaging a plugin | Plugins can be packaged using Python setuptools. You can see an example of a packaged plugin at https://github.com/simonw/datasette-plugin-demos The example consists of two files: a setup.py file that defines the plugin: from setuptools import setup VERSION = "0.1" setup( name="datasette-plugin-demos", description="Examples of plugins for Datasette", author="Simon Willison", url="https://github.com/simonw/datasette-plugin-demos", license="Apache License, Version 2.0", version=VERSION, py_modules=["datasette_plugin_demos"], entry_points={ "datasette": [ "plugin_demos = datasette_plugin_demos" ] }, install_requires=["datasette"], ) And a Python module file, datasette_plugin_demos.py , that implements the plugin: from datasette import hookimpl import random @hookimpl def prepare_jinja2_environment(env): env.filters["uppercase"] = lambda u: u.upper() @hookimpl def prepare_connection(conn): conn.create_function( "random_integer", 2, random.randint ) Having built a plugin in this way you can turn it into an installable package using the following command: python3 setup.py sdist This will create a .tar.gz file in the dist/ directory. You can then install your new plugin into a Datasette virtual environment or Docker container using pip : pip install datasette-plugin-demos-0.1.tar.gz To learn how to upload your plugin to PyPI for use by other people, read the PyPA guide to Packaging and distributing projects . | 220 | |
| 6 | 6 | Static assets | If your plugin has a static/ directory, Datasette will automatically configure itself to serve those static assets from the following path: /-/static-plugins/NAME_OF_PLUGIN_PACKAGE/yourfile.js Use the datasette.urls.static_plugins(plugin_name, path) method to generate URLs to that asset that take the base_url setting into account, see datasette.urls . To bundle the static assets for a plugin in the package that you publish to PyPI, add the following to the plugin's setup.py : package_data = ( { "datasette_plugin_name": [ "static/plugin.js", ], }, ) Where datasette_plugin_name is the name of the plugin package (note that it uses underscores, not hyphens) and static/plugin.js is the path within that package to the static file. datasette-cluster-map is a useful example of a plugin that includes packaged static assets in this way. See Writing custom CSS for tips on writing CSS that is compatible with Datasette's default CSS, including details of the core class for applying Datasette's default form element styles. | 220 | |
| 7 | 7 | Custom templates | If your plugin has a templates/ directory, Datasette will attempt to load templates from that directory before it uses its own default templates. The priority order for template loading is: templates from the --template-dir argument, if specified templates from the templates/ directory in any installed plugins default templates that ship with Datasette See Custom pages and templates for more details on how to write custom templates, including which filenames to use to customize which parts of the Datasette UI. Templates should be bundled for distribution using the same package_data mechanism in setup.py described for static assets above, for example: package_data = ( { "datasette_plugin_name": [ "templates/my_template.html", ], }, ) You can also use wildcards here such as templates/*.html . See datasette-edit-schema for an example of this pattern. | 220 | |
| 8 | 8 | Writing plugins that accept configuration | When you are writing plugins, you can access plugin configuration like this using the datasette plugin_config() method. If you know you need plugin configuration for a specific table, you can access it like this: plugin_config = datasette.plugin_config( "datasette-cluster-map", database="sf-trees", table="Street_Tree_List" ) This will return the {"latitude_column": "lat", "longitude_column": "lng"} in the above example. If there is no configuration for that plugin, the method will return None . If it cannot find the requested configuration at the table layer, it will fall back to the database layer and then the root layer. For example, a user may have set the plugin configuration option inside datasette.yaml like so: [[[cog from metadata_doc import metadata_example metadata_example(cog, { "databases": { "sf-trees": { "plugins": { "datasette-cluster-map": { "latitude_column": "xlat", "longitude_column": "xlng" } } } } }) ]]] [[[end]]] In this case, the above code would return that configuration for ANY table within the sf-trees database. The plugin configuration could also be set at the top level of datasette.yaml : [[[cog metadata_example(cog, { "plugins": { "datasette-cluster-map": { "latitude_column": "xlat", "longitude_column": "xlng" } } }) ]]] [[[end]]] Now that datasette-cluster-map plugin configuration will apply to every table in every database. | 220 | |
| 9 | 9 | Designing URLs for your plugin | You can register new URL routes within Datasette using the register_routes(datasette) plugin hook. Datasette's default URLs include these: /dbname - database page /dbname/tablename - table page /dbname/tablename/pk - row page See Pages and API endpoints and Introspection for more default URL routes. 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: /-/upload-excel 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 . If your plugin includes functionality that relates to a specific database you could also register a URL route like this: /dbname/-/upload-excel Or for a specific table like this: /dbname/tablename/-/modify-table-schema 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. | 220 | |
| 10 | 10 | Building URLs within plugins | Plugins that define their own custom user interface elements may need to link to other pages within Datasette. This can be a bit tricky if the Datasette instance is using the base_url configuration setting to run behind a proxy, since that can cause Datasette's URLs to include an additional prefix. The datasette.urls object provides internal methods for correctly generating URLs to different pages within Datasette, taking any base_url configuration into account. This object is exposed in templates as the urls variable, which can be used like this: Back to the <a href="{{ urls.instance() }}">Homepage</a> See datasette.urls for full details on this object. | 220 | |
| 11 | 11 | Plugins that define new plugin hooks | Plugins can define new plugin hooks that other plugins can use to further extend their functionality. datasette-graphql is one example of a plugin that does this. It defines a new hook called graphql_extra_fields , described here , which other plugins can use to define additional fields that should be included in the GraphQL schema. To define additional hooks, add a file to the plugin called datasette_your_plugin/hookspecs.py with content that looks like this: from pluggy import HookspecMarker hookspec = HookspecMarker("datasette") @hookspec def name_of_your_hook_goes_here(datasette): "Description of your hook." You should define your own hook name and arguments here, following the documentation for Pluggy specifications . Make sure to pick a name that is unlikely to clash with hooks provided by any other plugins. Then, to register your plugin hooks, add the following code to your datasette_your_plugin/__init__.py file: from datasette.plugins import pm from . import hookspecs pm.add_hookspecs(hookspecs) This will register your plugin hooks as part of the datasette plugin hook namespace. Within your plugin code you can trigger the hook using this pattern: from datasette.plugins import pm for ( plugin_return_value ) in pm.hook.name_of_your_hook_goes_here( datasette=datasette ): # Do something with plugin_return_value pass Other plugins will then be able to register their own implementations of your hook using this syntax: from datasette import hookimpl @hookimpl def name_of_your_hook_goes_here(datasette): return "Response from this plugin hook" These plugin implementations can accept 0 or more of the named arguments that you defined in your hook specification. | 220 | |
| 12 | 12 | CLI reference | The datasette CLI tool provides a number of commands. Running datasette without specifying a command runs the default command, datasette serve . See datasette serve for the full list of options for that command. [[[cog from datasette import cli from click.testing import CliRunner import textwrap def help(args): title = "datasette " + " ".join(args) cog.out("\n::\n\n") result = CliRunner().invoke(cli.cli, args) output = result.output.replace("Usage: cli ", "Usage: datasette ") cog.out(textwrap.indent(output, ' ')) cog.out("\n\n") ]]] [[[end]]] | 220 | |
| 13 | 13 | datasette --help | Running datasette --help shows a list of all of the available commands. [[[cog help(["--help"]) ]]] Usage: datasette [OPTIONS] COMMAND [ARGS]... Datasette is an open source multi-tool for exploring and publishing data About Datasette: https://datasette.io/ Full documentation: https://docs.datasette.io/ Options: --version Show the version and exit. --help Show this message and exit. Commands: serve* Serve up specified SQLite database files with a web UI create-token Create a signed API token for the specified actor ID inspect Generate JSON summary of provided database files install Install plugins and packages from PyPI into the same... package Package SQLite files into a Datasette Docker container plugins List currently installed plugins publish Publish specified SQLite database files to the internet... uninstall Uninstall plugins and Python packages from the Datasette... [[[end]]] Additional commands added by plugins that use the register_commands(cli) hook will be listed here as well. | 220 | |
| 14 | 14 | datasette serve | This command starts the Datasette web application running on your machine: datasette serve mydatabase.db Or since this is the default command you can run this instead: datasette mydatabase.db Once started you can access it at http://localhost:8001 [[[cog help(["serve", "--help"]) ]]] Usage: datasette serve [OPTIONS] [FILES]... Serve up specified SQLite database files with a web UI Options: -i, --immutable PATH Database files to open in immutable mode -h, --host TEXT Host for server. Defaults to 127.0.0.1 which means only connections from the local machine will be allowed. Use 0.0.0.0 to listen to all IPs and allow access from other machines. -p, --port INTEGER RANGE Port for server, defaults to 8001. Use -p 0 to automatically assign an available port. [0<=x<=65535] --uds TEXT Bind to a Unix domain socket --reload Automatically reload if code or metadata change detected - useful for development --cors Enable CORS by serving Access-Control-Allow- Origin: * --load-extension PATH:ENTRYPOINT? Path to a SQLite extension to load, and optional entrypoint --inspect-file TEXT Path to JSON file created using "datasette inspect" -m, --metadata FILENAME Path to JSON/YAML file containing license/source metadata --template-dir DIRECTORY Path to directory containing custom templates --plugins-dir DIRECTORY Path to directory containing custom plugins --static MOUNT:DIRECTORY Serve static files fr… | 220 | |
| 15 | 15 | Environment variables | Some of the datasette serve options can be provided by environment variables: DATASETTE_SECRET : Equivalent to the --secret option. DATASETTE_SSL_KEYFILE : Equivalent to the --ssl-keyfile option. DATASETTE_SSL_CERTFILE : Equivalent to the --ssl-certfile option. DATASETTE_LOAD_EXTENSION : Equivalent to the --load-extension option. | 220 | |
| 16 | 16 | datasette --get | The --get option to datasette serve (or just datasette ) specifies the path to a page within Datasette and causes Datasette to output the content from that path without starting the web server. This means that all of Datasette's functionality can be accessed directly from the command-line. For example: datasette --get '/-/versions.json' | jq . { "python": { "version": "3.8.5", "full": "3.8.5 (default, Jul 21 2020, 10:48:26) \n[Clang 11.0.3 (clang-1103.0.32.62)]" }, "datasette": { "version": "0.46+15.g222a84a.dirty" }, "asgi": "3.0", "uvicorn": "0.11.8", "sqlite": { "version": "3.32.3", "fts_versions": [ "FTS5", "FTS4", "FTS3" ], "extensions": { "json1": null }, "compile_options": [ "COMPILER=clang-11.0.3", "ENABLE_COLUMN_METADATA", "ENABLE_FTS3", "ENABLE_FTS3_PARENTHESIS", "ENABLE_FTS4", "ENABLE_FTS5", "ENABLE_GEOPOLY", "ENABLE_JSON1", "ENABLE_PREUPDATE_HOOK", "ENABLE_RTREE", "ENABLE_SESSION", "MAX_VARIABLE_NUMBER=250000", "THREADSAFE=1" ] } } You can use the --token TOKEN option to send an API token with the simulated request. Or you can make a request as a specific actor by passing a JSON representation of that actor to --actor : datasette --memory --actor '{"id": "root"}' --get '/-/actor.json' The exit code of datasette --get will be 0 if the request succeeds and 1 if the request produced an HTTP status code other than 200 - e.g. a 404 or 500 error. This lets you use datasette --get / to run tests against a Datasette application in a continuous integration environment such as GitHub Actions. | 220 | |
| 17 | 17 | datasette serve --help-settings | This command outputs all of the available Datasette settings . These can be passed to datasette serve using datasette serve --setting name value . [[[cog help(["--help-settings"]) ]]] Settings: default_page_size Default page size for the table view (default=100) max_returned_rows Maximum rows that can be returned from a table or custom query (default=1000) max_insert_rows Maximum rows that can be inserted at a time using the bulk insert API (default=100) num_sql_threads Number of threads in the thread pool for executing SQLite queries (default=3) sql_time_limit_ms Time limit for a SQL query in milliseconds (default=1000) default_facet_size Number of values to return for requested facets (default=30) facet_time_limit_ms Time limit for calculating a requested facet (default=200) facet_suggest_time_limit_ms Time limit for calculating a suggested facet (default=50) allow_facet Allow users to specify columns to facet using ?_facet= parameter (default=True) allow_download Allow users to download the original SQLite database files (default=True) allow_signed_tokens Allow users to create and use signed API tokens (default=True) default_allow_sql Allow anyone to run arbitrary SQL queries (default=True) max_signed_tokens_ttl Maximum allowed expiry time for signed API tokens (default=0) suggest_facets Calculate and display suggested facets (default… | 220 | |
| 18 | 18 | datasette plugins | Output JSON showing all currently installed plugins, their versions, whether they include static files or templates and which Plugin hooks they use. [[[cog help(["plugins", "--help"]) ]]] Usage: datasette plugins [OPTIONS] List currently installed plugins Options: --all Include built-in default plugins --requirements Output requirements.txt of installed plugins --plugins-dir DIRECTORY Path to directory containing custom plugins --help Show this message and exit. [[[end]]] Example output: [ { "name": "datasette-geojson", "static": false, "templates": false, "version": "0.3.1", "hooks": [ "register_output_renderer" ] }, { "name": "datasette-geojson-map", "static": true, "templates": false, "version": "0.4.0", "hooks": [ "extra_body_script", "extra_css_urls", "extra_js_urls" ] }, { "name": "datasette-leaflet", "static": true, "templates": false, "version": "0.2.2", "hooks": [ "extra_body_script", "extra_template_vars" ] } ] | 220 | |
| 19 | 19 | datasette install | Install new Datasette plugins. This command works like pip install but ensures that your plugins will be installed into the same environment as Datasette. This command: datasette install datasette-cluster-map Would install the datasette-cluster-map plugin. [[[cog help(["install", "--help"]) ]]] Usage: datasette install [OPTIONS] [PACKAGES]... Install plugins and packages from PyPI into the same environment as Datasette Options: -U, --upgrade Upgrade packages to latest version -r, --requirement PATH Install from requirements file -e, --editable TEXT Install a project in editable mode from this path --help Show this message and exit. [[[end]]] | 220 | |
| 20 | 20 | datasette uninstall | Uninstall one or more plugins. [[[cog help(["uninstall", "--help"]) ]]] Usage: datasette uninstall [OPTIONS] PACKAGES... Uninstall plugins and Python packages from the Datasette environment Options: -y, --yes Don't ask for confirmation --help Show this message and exit. [[[end]]] | 220 | |
| 21 | 21 | datasette publish | Shows a list of available deployment targets for publishing data with Datasette. Additional deployment targets can be added by plugins that use the publish_subcommand(publish) hook. [[[cog help(["publish", "--help"]) ]]] Usage: datasette publish [OPTIONS] COMMAND [ARGS]... Publish specified SQLite database files to the internet along with a Datasette-powered interface and API Options: --help Show this message and exit. Commands: cloudrun Publish databases to Datasette running on Cloud Run heroku Publish databases to Datasette running on Heroku [[[end]]] | 220 | |
| 22 | 22 | datasette publish cloudrun | See Publishing to Google Cloud Run . [[[cog help(["publish", "cloudrun", "--help"]) ]]] Usage: datasette publish cloudrun [OPTIONS] [FILES]... Publish databases to Datasette running on Cloud Run Options: -m, --metadata FILENAME Path to JSON/YAML file containing metadata to publish --extra-options TEXT Extra options to pass to datasette serve --branch TEXT Install datasette from a GitHub branch e.g. main --template-dir DIRECTORY Path to directory containing custom templates --plugins-dir DIRECTORY Path to directory containing custom plugins --static MOUNT:DIRECTORY Serve static files from this directory at /MOUNT/... --install TEXT Additional packages (e.g. plugins) to install --plugin-secret <TEXT TEXT TEXT>... Secrets to pass to plugins, e.g. --plugin- secret datasette-auth-github client_id xxx --version-note TEXT Additional note to show on /-/versions --secret TEXT Secret used for signing secure values, such as signed cookies --title TEXT Title for metadata --license TEXT License label for metadata --license_url TEXT License URL for metadata --source TEXT Source label for metadata --source_url TEXT Source URL for metadata --about TEXT About label for metadata --about_url TEXT About URL for metadata -n, --name TEXT Application name to use when building --service TEXT Cloud Run service to deploy (or over-write) --spatialite Enable SpatialLite extension --show-files Output the generated Dockerfile and metad… | 220 | |
| 23 | 23 | datasette publish heroku | See Publishing to Heroku . [[[cog help(["publish", "heroku", "--help"]) ]]] Usage: datasette publish heroku [OPTIONS] [FILES]... Publish databases to Datasette running on Heroku Options: -m, --metadata FILENAME Path to JSON/YAML file containing metadata to publish --extra-options TEXT Extra options to pass to datasette serve --branch TEXT Install datasette from a GitHub branch e.g. main --template-dir DIRECTORY Path to directory containing custom templates --plugins-dir DIRECTORY Path to directory containing custom plugins --static MOUNT:DIRECTORY Serve static files from this directory at /MOUNT/... --install TEXT Additional packages (e.g. plugins) to install --plugin-secret <TEXT TEXT TEXT>... Secrets to pass to plugins, e.g. --plugin- secret datasette-auth-github client_id xxx --version-note TEXT Additional note to show on /-/versions --secret TEXT Secret used for signing secure values, such as signed cookies --title TEXT Title for metadata --license TEXT License label for metadata --license_url TEXT License URL for metadata --source TEXT Source label for metadata --source_url TEXT Source URL for metadata --about TEXT About label for metadata --about_url TEXT About URL for metadata -n, --name TEXT Application name to use when deploying --tar TEXT --tar option to pass to Heroku, e.g. --tar=/usr/local/bin/gtar --generate-dir DIRECTORY Output generated application files and stop without deploying --h… | 220 | |
| 24 | 24 | datasette package | Package SQLite files into a Datasette Docker container, see datasette package . [[[cog help(["package", "--help"]) ]]] Usage: datasette package [OPTIONS] FILES... Package SQLite files into a Datasette Docker container Options: -t, --tag TEXT Name for the resulting Docker container, can optionally use name:tag format -m, --metadata FILENAME Path to JSON/YAML file containing metadata to publish --extra-options TEXT Extra options to pass to datasette serve --branch TEXT Install datasette from a GitHub branch e.g. main --template-dir DIRECTORY Path to directory containing custom templates --plugins-dir DIRECTORY Path to directory containing custom plugins --static MOUNT:DIRECTORY Serve static files from this directory at /MOUNT/... --install TEXT Additional packages (e.g. plugins) to install --spatialite Enable SpatialLite extension --version-note TEXT Additional note to show on /-/versions --secret TEXT Secret used for signing secure values, such as signed cookies -p, --port INTEGER RANGE Port to run the server on, defaults to 8001 [1<=x<=65535] --title TEXT Title for metadata --license TEXT License label for metadata --license_url TEXT License URL for metadata --source TEXT Source label for metadata --source_url TEXT Source URL for metadata --about TEXT About label for metadata --about_url TEXT About URL for metadata --help Show this message and exit. [[[end]]] | 220 | |
| 25 | 25 | datasette inspect | Outputs JSON representing introspected data about one or more SQLite database files. If you are opening an immutable database, you can pass this file to the --inspect-data option to improve Datasette's performance by allowing it to skip running row counts against the database when it first starts running: datasette inspect mydatabase.db > inspect-data.json datasette serve -i mydatabase.db --inspect-file inspect-data.json This performance optimization is used automatically by some of the datasette publish commands. You are unlikely to need to apply this optimization manually. [[[cog help(["inspect", "--help"]) ]]] Usage: datasette inspect [OPTIONS] [FILES]... Generate JSON summary of provided database files This can then be passed to "datasette --inspect-file" to speed up count operations against immutable database files. Options: --inspect-file TEXT --load-extension PATH:ENTRYPOINT? Path to a SQLite extension to load, and optional entrypoint --help Show this message and exit. [[[end]]] | 220 | |
| 26 | 26 | datasette create-token | Create a signed API token, see datasette create-token . [[[cog help(["create-token", "--help"]) ]]] Usage: datasette create-token [OPTIONS] ID Create a signed API token for the specified actor ID Example: datasette create-token root --secret mysecret To allow only "view-database-download" for all databases: datasette create-token root --secret mysecret \ --all view-database-download To allow "create-table" against a specific database: datasette create-token root --secret mysecret \ --database mydb create-table To allow "insert-row" against a specific table: datasette create-token root --secret myscret \ --resource mydb mytable insert-row Restricted actions can be specified multiple times using multiple --all, --database, and --resource options. Add --debug to see a decoded version of the token. Options: --secret TEXT Secret used for signing the API tokens [required] -e, --expires-after INTEGER Token should expire after this many seconds -a, --all ACTION Restrict token to this action -d, --database DB ACTION Restrict token to this action on this database -r, --resource DB RESOURCE ACTION Restrict token to this action on this database resource (a table, SQL view or named query) --debug Show decoded token --plugins-dir DIRECTORY Path to directory containing custom plugins --help Show this message and exit. [[[end]]] | 220 | |
| 27 | 27 | Internals for plugins | Many Plugin hooks are passed objects that provide access to internal Datasette functionality. The interface to these objects should not be considered stable with the exception of methods that are documented here. | 220 | |
| 28 | 28 | Request object | The request object is passed to various plugin hooks. It represents an incoming HTTP request. It has the following properties: .scope - dictionary The ASGI scope that was used to construct this request, described in the ASGI HTTP connection scope specification. .method - string The HTTP method for this request, usually GET or POST . .url - string The full URL for this request, e.g. https://latest.datasette.io/fixtures . .scheme - string The request scheme - usually https or http . .headers - dictionary (str -> str) A dictionary of incoming HTTP request headers. Header names have been converted to lowercase. .cookies - dictionary (str -> str) A dictionary of incoming cookies .host - string The host header from the incoming request, e.g. latest.datasette.io or localhost . .path - string The path of the request excluding the query string, e.g. /fixtures . .full_path - string The path of the… | 220 | |
| 29 | 29 | The MultiParams class | request.args is a MultiParams object - a dictionary-like object which provides access to query string parameters that may have multiple values. Consider the query string ?foo=1&foo=2&bar=3 - with two values for foo and one value for bar . request.args[key] - string 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" . request.args.get(key) - string or None 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", "") . request.args.getlist(key) - list of strings 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. request.args.keys() - list of strings Returns the list of available keys - for the example this would be ["foo", "bar"] . key in request.args - True or False You can use if key in request.args to check if a key is present. for key in request.args - iterator This lets you loop through every available key. le… | 220 | |
| 30 | 30 | The FormData class | await request.form() returns a FormData object - a dictionary-like object which provides access to form fields and uploaded files. It has a similar interface to MultiParams . form[key] - string or UploadedFile Returns the first value for that key, or raises a KeyError if the key is missing. form.get(key) - string, UploadedFile, or None Returns the first value for that key, or None if the key is missing. Pass a second argument to specify a different default. form.getlist(key) - list Returns the list of values for that key. If the key is missing an empty list will be returned. form.keys() - list of strings Returns the list of available keys. key in form - True or False You can use if key in form to check if a key is present. for key in form - iterator This lets you loop through every available key. len(form) - integer Returns the total number of submitted values. | 220 | |
| 31 | 31 | The UploadedFile class | When parsing multipart form data with files=True , file uploads are returned as UploadedFile objects with the following properties and methods: uploaded_file.name - string The form field name. uploaded_file.filename - string The original filename provided by the client. Note: This is sanitized to remove path components for security. uploaded_file.content_type - string or None The MIME type of the uploaded file, if provided by the client. uploaded_file.size - integer The size of the uploaded file in bytes. await uploaded_file.read(size=-1) - bytes Read and return up to size bytes from the file. If size is -1 (default), read the entire file. await uploaded_file.seek(offset, whence=0) - integer Seek to the given position in the file. Returns the new position. await uploaded_file.close() Close the underlying file. This is called automatically when the object is garbage collected. Files smaller than 1MB are stored in memory. Larger files are automatically spilled to temporary files on disk and cleaned up when the request completes. Example: form = await requ… | 220 | |
| 32 | 32 | Response class | The Response class can be returned from view functions that have been registered using the register_routes(datasette) hook. The Response() constructor takes the following arguments: body - string The body of the response. status - integer (optional) The HTTP status - defaults to 200. headers - dictionary (optional) A dictionary of extra HTTP headers, e.g. {"x-hello": "world"} . content_type - string (optional) The content-type for the response. Defaults to text/plain . For example: from datasette.utils.asgi import Response response = Response( "<xml>This is XML</xml>", content_type="application/xml; charset=utf-8", ) The quickest way to create responses is using the Response.text(...) , Response.html(...) , Response.json(...) or Response.redirect(...) helper methods: from datasette.utils.asgi import Response html_response = Response.html("This is HTML") json_response = Response.json({"this_is": "json"}) text_response = Response.text( "This will become utf-8 encoded text" ) # Redirects are served as 302, unless you pass status=301: redirect_response = Response.redirect( "https://latest.datasette.io/" ) Each of these responses will use the correct corresponding content-type - text/html; charset=utf-8 , application/json; charset=utf-8 or text/plain; charset=utf-8 respectively. Each of the helper methods take optional status= and headers= argument… | 220 | |
| 33 | 33 | Returning a response with .asgi_send(send) | In most cases you will return Response objects from your own view functions. You can also use a Response instance to respond at a lower level via ASGI, for example if you are writing code that uses the asgi_wrapper(datasette) hook. Create a Response object and then use await response.asgi_send(send) , passing the ASGI send function. For example: async def require_authorization(scope, receive, send): response = Response.text( "401 Authorization Required", headers={ "www-authenticate": 'Basic realm="Datasette", charset="UTF-8"' }, status=401, ) await response.asgi_send(send) | 220 | |
| 34 | 34 | Setting cookies with response.set_cookie() | To set cookies on the response, use the response.set_cookie(...) method. The method signature looks like this: def set_cookie( self, key, value="", max_age=None, expires=None, path="/", domain=None, secure=False, httponly=False, samesite="lax", ): ... 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 : response = Response.redirect("/") response.set_cookie( "ds_actor", datasette.sign({"a": {"id": "cleopaws"}}, "actor"), ) return response | 220 | |
| 35 | 35 | Datasette class | This object is an instance of the Datasette class, passed to many plugin hooks as an argument called datasette . You can create your own instance of this - for example to help write tests for a plugin - like so: from datasette.app import Datasette # With no arguments a single in-memory database will be attached datasette = Datasette() # The files= argument can load files from disk datasette = Datasette(files=["/path/to/my-database.db"]) # Pass metadata as a JSON dictionary like this datasette = Datasette( files=["/path/to/my-database.db"], metadata={ "databases": { "my-database": { "description": "This is my database" } } }, ) Constructor parameters include: files=[...] - a list of database files to open immutables=[...] - a list of database files to open in immutable mode metadata={...} - a dictionary of Metadata config_dir=... - the configuration directory to use, stored in datasette.config_dir | 220 | |
| 36 | 36 | .databases | Property exposing a collections.OrderedDict of databases currently connected to Datasette. The dictionary keys are the name of the database that is used in the URL - e.g. /fixtures would have a key of "fixtures" . The values are Database class instances. All databases are listed, irrespective of user permissions. | 220 | |
| 37 | 37 | .actions | Property exposing a dictionary of actions that have been registered using the register_actions(datasette) plugin hook. The dictionary keys are the action names - e.g. view-instance - and the values are Action() objects describing the permission. | 220 | |
| 38 | 38 | .plugin_config(plugin_name, database=None, table=None) | plugin_name - string The name of the plugin to look up configuration for. Usually this is something similar to datasette-cluster-map . database - None or string The database the user is interacting with. table - None or string The table the user is interacting with. This method lets you read plugin configuration values that were set in datasette.yaml . See Writing plugins that accept configuration for full details of how this method should be used. The return value will be the value from the configuration file - usually a dictionary. If the plugin is not configured the return value will be None . | 220 | |
| 39 | 39 | await .render_template(template, context=None, request=None) | template - string, list of strings or jinja2.Template 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. If this is a list of template file names then the first one that exists will be loaded and rendered. If this is a Jinja Template object it will be used directly. context - None or a Python dictionary The context variables to pass to the template. request - request object or None If you pass a Datasette request object here it will be made available to the template. 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. | 220 | |
| 40 | 40 | await .actors_from_ids(actor_ids) | actor_ids - list of strings or integers A list of actor IDs to look up. Returns a dictionary, where the keys are the IDs passed to it and the values are the corresponding actor dictionaries. This method is mainly designed to be used with plugins. See the actors_from_ids(datasette, actor_ids) documentation for details. If no plugins that implement that hook are installed, the default return value looks like this: { "1": {"id": "1"}, "2": {"id": "2"} } | 220 | |
| 41 | 41 | await .allowed(*, action, resource, actor=None) | action - string The name of the action that is being permission checked. resource - Resource object A Resource object representing the database, table, or other resource. Must be an instance of a Resource class such as TableResource , DatabaseResource , QueryResource , or InstanceResource . actor - dictionary, optional The authenticated actor. This is usually request.actor . Defaults to None for unauthenticated requests. This method checks if the given actor has permission to perform the given action on the given resource. All parameters must be passed as keyword arguments. Example usage: from datasette.resources import ( TableResource, DatabaseResource, ) # Check if actor can view a specific table can_view = await datasette.allowed( action="view-table", resource=TableResource( database="fixtures", table="facetable" ), actor=request.actor, ) # Check if actor can execute SQL on a database can_execute = await datasette.allowed( action="execute-sql", resource=DatabaseResource(database="fixtures"), actor=request.actor, ) The method returns True if the permission is granted, False if denied. Results are cached for the duration of the current request, so checking the same (actor, action, resource) combination twice within one request only does the underlying permission resolution work once. | 220 | |
| 42 | 42 | await .allowed_many(*, actions, resource, actor=None) | actions - list of strings The names of the actions to permission check. resource - Resource object A Resource object representing the database, table, or other resource that each action is checked against. Omit for global actions. actor - dictionary, optional The authenticated actor. This is usually request.actor . Defaults to None for unauthenticated requests. Checks several actions against the same resource for the same actor, returning a dictionary mapping each action name to True or False . The whole batch - including any actions pulled in through also_requires dependencies - is resolved with a single SQL query against the internal database, so this is much faster than calling datasette.allowed() once per action. Example usage: from datasette.resources import TableResource results = await datasette.allowed_many( actions=["insert-row", "delete-row", "drop-table"], resource=TableResource( database="fixtures", table="facetable" ), actor=request.actor, ) # {"insert-row": True, "delete-row": True, "drop-table": False} Each result is stored in the per-request permission check cache, so subsequent datasette.allowed() calls for the same checks within the same request are served from that cache. Datasette uses this before running the table_actions and database_actions plugin hooks: it resolves every registered table-level action against the current table and every database-level action against its database first, which means allowed() calls made by those plugin hooks are usually serve… | 220 | |
| 43 | 43 | await .allowed_resources(action, actor=None, *, parent=None, include_is_private=False, include_reasons=False, limit=100, next=None) | Returns a PaginatedResources object containing resources that the actor can access for the specified action, with support for keyset pagination. action - string The action name (e.g., "view-table", "view-database") actor - dictionary, optional The authenticated actor. Defaults to None for unauthenticated requests. parent - string, optional Optional parent filter (e.g., database name) to limit results include_is_private - boolean, optional If True, adds a .private attribute to each Resource indicating whether anonymous users can access it include_reasons - boolean, optional If True, adds a .reasons attribute with a list of strings describing why access was granted (useful for debugging) limit - integer, optional Maximum number of results to return per page (1-1000, default 100) next - string, optional Keyset token from a previous page for pagination The method returns a PaginatedResources object (from… | 220 | |
| 44 | 44 | await .allowed_resources_sql(*, action, actor=None, parent=None, include_is_private=False) | Builds the SQL query that Datasette uses to determine which resources an actor may access for a specific action. Returns a (sql: str, params: dict) namedtuple that can be executed against the internal catalog_* database tables. parent can be used to limit results to a specific database, and include_is_private adds a column indicating whether anonymous users would be denied access to that resource. Plugins that need to execute custom analysis over the raw allow/deny rules can use this helper to run the same query that powers the /-/allowed debugging interface. The SQL query built by this method will return the following columns: parent : The parent resource identifier (or NULL) child : The child resource identifier (or NULL) reason : The reason from the rule that granted access is_private : (if include_is_private ) 1 if anonymous users cannot access, 0 otherwise | 220 | |
| 45 | 45 | await .ensure_permission(action, resource=None, actor=None) | action - string The action to check. See Built-in actions for a list of available actions. resource - Resource object (optional) The resource to check the permission against. Must be an instance of InstanceResource , DatabaseResource , or TableResource from the datasette.resources module. If omitted, defaults to InstanceResource() for instance-level permissions. actor - dictionary (optional) The authenticated actor. This is usually request.actor . This is a convenience wrapper around await .allowed(*, action, resource, actor=None) that raises a datasette.Forbidden exception if the permission check fails. Use this when you want to enforce a permission check and halt execution if the actor is not authorized. Example: from datasette.resources import TableResource # Will raise Forbidden if actor cannot view the table await datasette.ensure_permission( action="view-table", resource=TableResource( database="fixtures", table="cities" ), actor=request.actor, ) # For instance-level actions, resource can be omitted: await datasette.ensure_permission( action="permissions-debug", actor=request.actor ) | 220 | |
| 46 | 46 | await .check_visibility(actor, action, resource=None) | actor - dictionary The authenticated actor. This is usually request.actor . action - string The name of the action that is being permission checked. resource - Resource object, optional The resource being checked, as a Resource object such as DatabaseResource(database=...) , TableResource(database=..., table=...) , or QueryResource(database=..., query=...) . Only some permissions apply to a resource. 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?" 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. This example checks if the user can access a specific table, and sets private so that a padlock icon can later be displayed: from datasette.resources import TableResource visible, private = await datasette.check_visibility( request.actor, action="view-table", resource=TableResource(database=database, table=table), ) | 220 | |
| 47 | 47 | await .create_token(actor_id, expires_after=None, restrictions=None, handler=None) | actor_id - string The ID of the actor to create a token for. expires_after - int, optional The number of seconds after which the token should expire. restrictions - TokenRestrictions , optional A TokenRestrictions object limiting which actions the token can perform. handler - string, optional The name of a specific token handler to use. If omitted, the first registered handler is used. See register_token_handler(datasette) . This is an async method that returns an API token string which can be used to authenticate requests to the Datasette API. The default SignedTokenHandler returns tokens of the format dstok_... . All tokens must have an actor_id string indicating the ID of the actor which the token will act on behalf of. Tokens default to lasting forever, but can be set to expire after a given number of seconds using the expires_after argument. The following code creates a token for user1 that will expire after an hour: token = await datasette.create_token( actor_id="user1", expires_after=3600, ) | 220 | |
| 48 | 48 | TokenRestrictions | The TokenRestrictions class uses a builder pattern to specify which actions a token is allowed to perform. Import it from datasette.tokens : from datasette.tokens import TokenRestrictions restrictions = ( TokenRestrictions() .allow_all("view-instance") .allow_all("view-table") .allow_database("docs", "view-query") .allow_resource("docs", "attachments", "insert-row") .allow_resource("docs", "attachments", "update-row") ) The builder methods are: allow_all(action) - allow an action across all databases and resources allow_database(database, action) - allow an action on a specific database allow_resource(database, resource, action) - allow an action on a specific resource (table, SQL view or stored query ) within a database Each method returns the TokenRestrictions instance so calls can be chained. TokenRestrictions also provides an abbreviated(datasette) method which returns the restrictions as a dictionary using the compact format described in Restricting the actions that a token can perform , with action names replaced by their registered abbreviations. It returns the inner dictionary only - the "_r" wrapping key shown in that section is not included. Returns None if no restrictions are set. This is useful when writing a custom register_token_handler(datasette) that needs to embed restrictions in a token payload. For example, the following restrictions: restrictions = ( TokenRestrictions() .allow_all("view-instance") .allow_database("docs", "view-query") .allow_resource("docs", "attachments", "insert-row") ) restrictions.abbreviated(dataset… | 220 | |
| 49 | 49 | await .verify_token(token) | token - string The token string to verify. This is an async method that verifies an API token by trying each registered token handler in order. Returns an actor dictionary from the first handler that recognizes the token, or None if no handler accepts it. actor = await datasette.verify_token(token) if actor: # Token was valid print(actor["id"]) | 220 | |
| 50 | 50 | .get_database(name) | name - string, optional The name of the database - optional. 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. | 220 | |
| 51 | 51 | .get_internal_database() | Returns a database object for reading and writing to the private internal database . | 220 | |
| 52 | 52 | Getting and setting metadata | Metadata about the instance, databases, tables and columns is stored in tables in Datasette's internal database . The following methods are the supported API for plugins to read and update that stored metadata. | 220 | |
| 53 | 53 | await .get_instance_metadata(self) | Returns metadata keys and values for the entire Datasette instance as a dictionary. Internally queries the metadata_instance table inside the internal database . | 220 | |
| 54 | 54 | await .get_database_metadata(self, database_name) | database_name - string The name of the database to query. Returns metadata keys and values for the specified database as a dictionary. Internally queries the metadata_databases table inside the internal database . | 220 | |
| 55 | 55 | await .get_resource_metadata(self, database_name, resource_name) | database_name - string The name of the database to query. resource_name - string The name of the resource (table, view, or stored query) inside database_name to query. Returns metadata keys and values for the specified "resource" as a dictionary. A "resource" in this context can be a table, view, or stored query. Internally queries the metadata_resources table inside the internal database . | 220 | |
| 56 | 56 | await .get_column_metadata(self, database_name, resource_name, column_name) | database_name - string The name of the database to query. resource_name - string The name of the resource (table, view, or stored query) inside database_name to query. column_name - string The name of the column inside resource_name to query. Returns metadata keys and values for the specified column, resource, and table as a dictionary. Internally queries the metadata_columns table inside the internal database . | 220 | |
| 57 | 57 | await .set_instance_metadata(self, key, value) | key - string The metadata entry key to insert (ex title , description , etc.) value - string The value of the metadata entry to insert. Adds a new metadata entry for the entire Datasette instance. Any previous instance-level metadata entry with the same key will be overwritten. Internally upserts the value into the the metadata_instance table inside the internal database . | 220 | |
| 58 | 58 | await .set_database_metadata(self, database_name, key, value) | database_name - string The database the metadata entry belongs to. key - string The metadata entry key to insert (ex title , description , etc.) value - string The value of the metadata entry to insert. Adds a new metadata entry for the specified database. Any previous database-level metadata entry with the same key will be overwritten. Internally upserts the value into the the metadata_databases table inside the internal database . | 220 | |
| 59 | 59 | await .set_resource_metadata(self, database_name, resource_name, key, value) | database_name - string The database the metadata entry belongs to. resource_name - string The resource (table, view, or stored query) the metadata entry belongs to. key - string The metadata entry key to insert (ex title , description , etc.) value - string The value of the metadata entry to insert. Adds a new metadata entry for the specified "resource". Any previous resource-level metadata entry with the same key will be overwritten. Internally upserts the value into the the metadata_resources table inside the internal database . | 220 | |
| 60 | 60 | await .set_column_metadata(self, database_name, resource_name, column_name, key, value) | database_name - string The database the metadata entry belongs to. resource_name - string The resource (table, view, or stored query) the metadata entry belongs to. column-name - string The column the metadata entry belongs to. key - string The metadata entry key to insert (ex title , description , etc.) value - string The value of the metadata entry to insert. Adds a new metadata entry for the specified column. Any previous column-level metadata entry with the same key will be overwritten. Internally upserts the value into the the metadata_columns table inside the internal database . | 220 | |
| 61 | 61 | Stored queries | Stored queries are stored in the queries table in the internal database . Plugins can use the following methods to add, update, list and remove stored queries. | 220 | |
| 62 | 62 | await .add_query(database, name, sql, ...) | Adds a stored query. async def add_query( self, database, name, sql, *, title=None, description=None, description_html=None, hide_sql=False, fragment=None, parameters=None, is_write=False, is_private=False, is_trusted=False, source="plugin", owner_id=None, on_success_message=None, on_success_message_sql=None, on_success_redirect=None, on_error_message=None, on_error_redirect=None, replace=True, ): ... database - string The name of the database this query should belong to. name - string The name of the stored query, used in the URL for that query. sql - string The SQL for the stored query. title - string, optional A display title for the query. description - string, optional A plain text description. description_html - string, optional An HTML description. hide_sql - boolean, optional … | 220 | |
| 63 | 63 | await .update_query(database, name, ...) | Updates fields for an existing stored query. Only keyword arguments that are provided will be changed. The available keyword arguments are the same as those for await .add_query(database, name, sql, ...) , except for replace . Pass None to clear optional text fields and options such as on_success_redirect . Passing hide_sql=False removes the hide_sql option. Example: await datasette.update_query( database="fixtures", name="recent_rows", title="Latest rows", is_private=True, owner_id="alice", ) | 220 | |
| 64 | 64 | await .get_query(database, name) | Returns a StoredQuery dataclass instance, or None if the query does not exist. StoredQuery has the following attributes: database , name , sql , title , description , description_html , hide_sql , fragment , parameters , is_write , is_private , is_trusted , source , owner_id , on_success_message , on_success_message_sql , on_success_redirect , on_error_message and on_error_redirect . parameters is a list of explicit parameter names. | 220 | |
| 65 | 65 | await .list_queries(database=None, ...) | Lists stored queries visible to the specified actor. async def list_queries( self, database=None, *, actor=None, limit=50, cursor=None, q=None, is_write=None, is_private=None, is_trusted=None, source=None, owner_id=None, include_private=False, ): ... database - string, optional Restrict results to a specific database. Omit this to list queries across all databases. actor - dictionary, optional The authenticated actor. Results are filtered using that actor's view-query permission. limit - integer, optional Number of queries to return. Values are clamped to the range 1-1000. cursor - string, optional Pagination cursor from the previous page's next value. q - string, optional Search string matched against query name, title, description and SQL. is_write , is_private , is_trusted - boolean, optional Filter by those stored query flags. … | 220 | |
| 66 | 66 | await .count_queries(database=None, ...) | Counts stored queries visible to the specified actor. This accepts the same filtering keyword arguments as await .list_queries(database=None, ...) , except for limit , cursor and include_private . | 220 | |
| 67 | 67 | await .remove_query(database, name, source=None) | Removes a stored query. database - string The database the query belongs to. name - string The query name. source - string, optional If provided, only a query with this source will be removed. | 220 | |
| 68 | 68 | Column types | Column types are stored in the column_types table in the internal database . The following methods provide the API for reading and modifying column type assignments. | 220 | |
| 69 | 69 | await .get_column_type(database, resource, column) | database - string The name of the database. resource - string The name of the table or view. column - string The name of the column. Returns a ColumnType subclass instance with .config populated for the specified column, or None if no column type is assigned. ct = await datasette.get_column_type( "mydb", "mytable", "email_col" ) if ct: print(ct.name) # "email" print(ct.config) # None or {...} | 220 | |
| 70 | 70 | await .get_column_types(database, resource) | database - string The name of the database. resource - string The name of the table or view. Returns a dictionary mapping column names to ColumnType subclass instances (with .config populated) for all columns that have assigned types on the given resource. ct_map = await datasette.get_column_types("mydb", "mytable") for col_name, ct in ct_map.items(): print(col_name, ct.name, ct.config) | 220 | |
| 71 | 71 | await .set_column_type(database, resource, column, column_type, config=None) | database - string The name of the database. resource - string The name of the table or view. column - string The name of the column. column_type - string The column type name to assign, e.g. "email" . config - dict, optional Optional configuration dict for the column type. Assigns a column type to a column. Overwrites any existing assignment for that column. Raises ValueError if the column type declares sqlite_types and the target column does not match one of those SQLite types. await datasette.set_column_type( "mydb", "mytable", "location", "point", config={"srid": 4326}, ) | 220 | |
| 72 | 72 | await .remove_column_type(database, resource, column) | database - string The name of the database. resource - string The name of the table or view. column - string The name of the column. Removes the column type assignment for the specified column. await datasette.remove_column_type( "mydb", "mytable", "location" ) | 220 | |
| 73 | 73 | .add_database(db, name=None, route=None) | db - datasette.database.Database instance The database to be attached. name - string, optional The name to be used for this database . If not specified Datasette will pick one based on the filename or memory name. route - string, optional This will be used in the URL path. If not specified, it will default to the same thing as the name . The datasette.add_database(db) method lets you add a new database to the current Datasette instance. The db parameter should be an instance of the datasette.database.Database class. For example: from datasette.database import Database datasette.add_database( Database( datasette, path="path/to/my-new-database.db", ) ) This will add a mutable database and serve it at /my-new-database . Use is_mutable=False to add an immutable database. .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: db = datasette.add_database( Database(datasette, memory_name="statistics") ) await db.execute_write( "CREATE TABLE foo(id integer primary key)" ) | 220 | |
| 74 | 74 | .add_memory_database(memory_name, name=None, route=None) | Adds a shared in-memory database with the specified name: datasette.add_memory_database("statistics") This is a shortcut for the following: from datasette.database import Database datasette.add_database( Database(datasette, memory_name="statistics") ) Using either of these patterns will result in the in-memory database being served at /statistics . The name and route parameters are optional and work the same way as they do for .add_database(db, name=None, route=None) . | 220 | |
| 75 | 75 | .remove_database(name) | name - string The name of the database to be removed. This removes a database that has been previously added. name= is the unique name of that database. | 220 | |
| 76 | 76 | .close() | Release all resources held by this Datasette instance. This calls db.close() on every attached database (including the internal database), shuts down the thread pool executor used to run SQL queries, and unlinks the temporary file used to back the internal database if one was created. close() is synchronous, idempotent and one-way: after a call to close() any attempt to use the Datasette instance to execute SQL will raise a datasette.database.DatasetteClosedError exception. A closed Datasette cannot be reopened — callers that need a fresh instance should construct a new one. If a call to Database.close() on one of the attached databases raises an exception, Datasette.close() will continue trying to close the remaining databases and will re-raise the first exception after every database has been processed. When Datasette is being served over ASGI the close() method is wired up to the lifespan shutdown event, so resources are released cleanly on SIGTERM / SIGINT . | 220 | |
| 77 | 77 | await .track_event(event) | event - Event An instance of a subclass of datasette.events.Event . Plugins can call this to track events, using classes they have previously registered. See Event tracking for details. The event will then be passed to all plugins that have registered to receive events using the track_event(datasette, event) hook. Example usage, assuming the plugin has previously registered the BanUserEvent class: await datasette.track_event( BanUserEvent(user={"id": 1, "username": "cleverbot"}) ) | 220 | |
| 78 | 78 | .sign(value, namespace="default") | value - any serializable type The value to be signed. namespace - string, optional An alternative namespace, see the itsdangerous salt documentation . 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. This method returns a signed string, which can be decoded and verified using .unsign(value, namespace="default") . | 220 | |
| 79 | 79 | .unsign(value, namespace="default") | signed - any serializable type The signed string that was created using .sign(value, namespace="default") . namespace - string, optional The alternative namespace, if one was used. 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. | 220 | |
| 80 | 80 | .add_message(request, message, type=datasette.INFO) | request - Request The current Request object message - string The message string type - constant, optional The message type - datasette.INFO , datasette.WARNING or datasette.ERROR 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. You can try out these messages (including the different visual styling of the three message types) using the /-/messages debugging tool. | 220 | |
| 81 | 81 | .absolute_url(request, path) | request - Request The current Request object path - string A path, for example /dbname/table.json Returns the absolute URL for the given path, including the protocol and host. For example: absolute_url = datasette.absolute_url( request, "/dbname/table.json" ) # Would return "http://localhost:8001/dbname/table.json" 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. | 220 | |
| 82 | 82 | .setting(key) | key - string The name of the setting, e.g. base_url . Returns the configured value for the specified setting . This can be a string, boolean or integer depending on the requested setting. For example: downloads_are_allowed = datasette.setting("allow_download") | 220 | |
| 83 | 83 | .resolve_database(request) | request - Request object A request object If you are implementing your own custom views, you may need to resolve the database that the user is requesting based on a URL path. If the regular expression for your route declares a database named group, you can use this method to resolve the database object. This returns a Database instance. If the database cannot be found, it raises a datasette.utils.asgi.DatabaseNotFound exception - which is a subclass of datasette.utils.asgi.NotFound with a .database_name attribute set to the name of the database that was requested. | 220 | |
| 84 | 84 | .resolve_table(request) | request - Request object A request object This assumes that the regular expression for your route declares both a database and a table named group. It returns a ResolvedTable named tuple instance with the following fields: db - Database The database object table - string The name of the table (or view) is_view - boolean True if this is a view, False if it is a table If the database or table cannot be found it raises a datasette.utils.asgi.DatabaseNotFound exception. If the table does not exist it raises a datasette.utils.asgi.TableNotFound exception - a subclass of datasette.utils.asgi.NotFound with .database_name and .table attributes. | 220 | |
| 85 | 85 | .resolve_row(request) | request - Request object A request object This method assumes your route declares named groups for database , table and pks . It returns a ResolvedRow named tuple instance with the following fields: db - Database The database object table - string The name of the table sql - string SQL snippet that can be used in a WHERE clause to select the row params - dict Parameters that should be passed to the SQL query pks - list List of primary key column names pk_values - list List of primary key values decoded from the URL row - sqlite3.Row The row itself If the database or table cannot be found it raises a datasette.utils.asgi.DatabaseNotFound exception. If the table does not exist it raises a datasette.utils.asgi.TableNotFound … | 220 | |
| 86 | 86 | datasette.client | Plugins can make internal simulated HTTP requests to the Datasette instance within which they are running. This ensures that all of Datasette's external JSON APIs are also available to plugins, while avoiding the overhead of making an external HTTP call to access those APIs. The datasette.client object is a wrapper around the HTTPX Python library , providing an async-friendly API that is similar to the widely used Requests library . It offers the following methods: await datasette.client.get(path, **kwargs) - returns HTTPX Response Execute an internal GET request against that path. await datasette.client.post(path, **kwargs) - returns HTTPX Response Execute an internal POST request. Use data={"name": "value"} to pass form parameters. await datasette.client.options(path, **kwargs) - returns HTTPX Response Execute an internal OPTIONS request. await datasette.client.head(path, **kwargs) - returns HTTPX Response Execute an internal HEAD request. await datasette.client.put(path, **kwargs) - returns HTTPX Response Execute an internal PUT request. await datasette.client.patch(path, **kwargs) - returns HTTPX Response … | 220 | |
| 87 | 87 | Authenticating as an actor | All datasette.client methods accept an optional actor= parameter. When set to a dictionary describing an actor, the request is made with a signed ds_actor cookie identifying that actor — as if the request had been made by a user who is signed in as that actor. This is a convenient shorthand equivalent to signing the cookie manually using datasette.client.actor_cookie() . Example usage: response = await datasette.client.get( "/-/actor.json", actor={"id": "root"} ) assert response.json() == {"actor": {"id": "root"}} This parameter works with all HTTP methods ( get , post , put , patch , delete , options , head ) and the generic request method. Passing both actor= and a ds_actor cookie via cookies= raises a TypeError . Other unrelated cookies can be combined with actor= . | 220 | |
| 88 | 88 | Bypassing permission checks | All datasette.client methods accept an optional skip_permission_checks=True parameter. When set, all permission checks will be bypassed for that request, allowing access to any resource regardless of the configured permissions. This is useful for plugins and internal operations that need to access all resources without being subject to permission restrictions. Example usage: # Regular request - respects permissions response = await datasette.client.get( "/private-db/secret-table.json" ) # May return 403 Forbidden if access is denied # With skip_permission_checks - bypasses all permission checks response = await datasette.client.get( "/private-db/secret-table.json", skip_permission_checks=True, ) # Will return 200 OK and the data, regardless of permissions This parameter works with all HTTP methods ( get , post , put , patch , delete , options , head ) and the generic request method. Use skip_permission_checks=True with caution. It completely bypasses Datasette's permission system and should only be used in trusted plugin code or internal operations where you need guaranteed access to resources. | 220 | |
| 89 | 89 | Detecting internal client requests | datasette.in_client() - returns bool Returns True if the current code is executing within a datasette.client request, False otherwise. This method is useful for plugins that need to behave differently when called through datasette.client versus when handling external HTTP requests. Example usage: async def fetch_documents(datasette): if not datasette.in_client(): return Response.text( "Only available via internal client requests", status=403, ) ... Note that datasette.in_client() is independent of skip_permission_checks . A request made through datasette.client will always have in_client() return True , regardless of whether skip_permission_checks is set. | 220 | |
| 90 | 90 | datasette.urls | The datasette.urls object contains methods for building URLs to pages within Datasette. Plugins should use this to link to pages, since these methods take into account any base_url configuration setting that might be in effect. datasette.urls.instance(format=None) Returns the URL to the Datasette instance root page. This is usually "/" . datasette.urls.path(path, format=None) Takes a path and returns the full path, taking base_url into account. For example, datasette.urls.path("-/logout") will return the path to the logout page, which will be "/-/logout" by default or /prefix-path/-/logout if base_url is set to /prefix-path/ datasette.urls.logout() Returns the URL to the logout page, usually "/-/logout" datasette.urls.static(path) Returns the URL of one of Datasette's default static assets, for example "/-/static/app.css" datasette.urls.static_plugins(plugin_name, path) Returns the URL of one of the static assets belonging to a plugin. datasette.urls.static_plugins("datasette_cluster_map", "datasette-cluster-map.js") would return "/-/static-plugins/datasette_cluster_map/datasette-cluster-map.js" datasette.urls.static(path) … | 220 | |
| 91 | 91 | Permission classes and utilities | 220 | ||
| 92 | 92 | PermissionSQL class | The PermissionSQL class is used by plugins to contribute SQL-based permission rules through the permission_resources_sql(datasette, actor, action) hook. This enables efficient permission checking across multiple resources by leveraging SQLite's query engine. from datasette.permissions import PermissionSQL @dataclass class PermissionSQL: source: str # Plugin name for auditing sql: str # SQL query returning permission rules params: Dict[str, Any] # Parameters for the SQL query Attributes: source - string An identifier for the source of these permission rules, typically the plugin name. This is used for debugging and auditing. sql - string A SQL query that returns permission rules. The query must return rows with the following columns: parent (TEXT or NULL) - The parent resource identifier (e.g., database name) child (TEXT or NULL) - The child resource identifier (e.g., table name) allow (INTEGER) - 1 for allow, 0 for deny reason (TEXT) - A human-readable explanation of why this permission was granted or denied params - dictionary A dictionary of parameters to … | 220 | |
| 93 | 93 | Available SQL parameters | When writing SQL for PermissionSQL , the following parameters are automatically available: :actor - JSON string or NULL The full actor dictionary serialized as JSON. Use SQLite's json_extract() function to access fields: json_extract(:actor, '$.role') = 'admin' json_extract(:actor, '$.team') = 'engineering' :actor_id - string or NULL The actor's id field, for simple equality comparisons: :actor_id = 'alice' :action - string The action being checked (e.g., "view-table" , "insert-row" , "execute-sql" ). Example usage: Here's an example plugin that grants view-table permissions to users with an "analyst" role for tables in the "analytics" database: from datasette import hookimpl from datasette.permissions import PermissionSQL @hookimpl def permission_resources_sql(datasette, actor, action): if action != "view-table": return None return PermissionSQL( source="my_analytics_plugin", sql=""" SELECT 'analytics' AS parent, NULL AS child, 1 AS allow, 'Analysts can view analytics database' AS reason WHERE json_extract(:actor, '$.role') = 'analyst' AND :action = 'view-table' """, params={}, ) A more complex example that uses custom parameters: … | 220 | |
| 94 | 94 | Database class | Instances of the Database class can be used to execute queries against attached SQLite databases, and to run introspection against their schemas. | 220 | |
| 95 | 95 | Database(ds, path=None, is_mutable=True, is_memory=False, memory_name=None, is_temp_disk=False) | The Database() constructor can be used by plugins, in conjunction with .add_database(db, name=None, route=None) , to create and register new databases. The arguments are as follows: ds - Datasette class (required) The Datasette instance you are attaching this database to. path - string Path to a SQLite database file on disk. is_mutable - boolean Set this to False to cause Datasette to open the file in immutable mode. is_memory - boolean Use this to create non-shared memory connections. memory_name - string or None Use this to create a named in-memory database. Unlike regular memory databases these can be accessed by multiple threads and will persist an changes made to them for the lifetime of the Datasette server process. is_temp_disk - boolean Set this to True to create a temporary file-backed database. This creates a SQLite database in a temporary file on disk (using Python's tempfile.mkstemp() ) with WAL mode enabled for better concurrent read/write performance. The temporary file is automatically cleaned up when the database is closed or when the process exits. Unlike named in-mem… | 220 | |
| 96 | 96 | db.hash | If the database was opened in immutable mode, this property returns the 64 character SHA-256 hash of the database contents as a string. Otherwise it returns None . | 220 | |
| 97 | 97 | await db.execute(sql, ...) | Executes a SQL query against the database and returns the resulting rows (see Results ). sql - string (required) The SQL query to execute. This can include ? or :named parameters. params - list or dict A list or dictionary of values to use for the parameters. List for ? , dictionary for :named . truncate - boolean Should the rows returned by the query be truncated at the maximum page size? Defaults to True , set this to False to disable truncation. custom_time_limit - integer ms A custom time limit for this query. This can be set to a lower value than the Datasette configured default. If a query takes longer than this it will be terminated early and raise a dataette.database.QueryInterrupted exception. page_size - integer Set a custom page size for truncation, over-riding the configured Datasette default. log_sql_errors - boolean Should any SQL errors be logged to the console in addition to being raised as an error? Defaults to True . | 220 | |
| 98 | 98 | Results | The db.execute() method returns a single Results object. This can be used to access the rows returned by the query. Iterating over a Results object will yield SQLite Row objects . Each of these can be treated as a tuple or can be accessed using row["column"] syntax: info = [] results = await db.execute("select name from sqlite_master") for row in results: info.append(row["name"]) The Results object also has the following properties and methods: .truncated - boolean Indicates if this query was truncated - if it returned more results than the specified page_size . If this is true then the results object will only provide access to the first page_size rows in the query result. You can disable truncation by passing truncate=False to the db.query() method. .columns - list of strings A list of column names returned by the query. .rows - list of sqlite3.Row This property provides direct access to the list of rows returned by the database. You can access specific rows by index using results.rows[0] . .dicts() - list of dict This method returns a list of Python dictionaries, one for each row. .first() - row or None Returns the first row in the results, or None if no rows were returned. … | 220 | |
| 99 | 99 | await db.execute_fn(fn) | Executes a given callback function against a read-only database connection running in a thread. The function will be passed a SQLite connection, and the return value from the function will be returned by the await . Example usage: def get_version(conn): return conn.execute( "select sqlite_version()" ).fetchall()[0][0] version = await db.execute_fn(get_version) | 220 | |
| 100 | 100 | await db.execute_write(sql, params=None, block=True, request=None, return_all=False, returning_limit=10) | SQLite only allows one database connection to write at a time. Datasette handles this for you by maintaining a queue of writes to be executed against a given database. Plugins can submit write operations to this queue and they will be executed in the order in which they are received. This method can be used to queue up a non-SELECT SQL query to be executed against a single write connection to the database. You can pass additional SQL parameters as a tuple or dictionary. The optional request= argument is used internally by Datasette to pass request context to write_wrapper plugin hooks . The method will block until the operation is completed, and the return value will be an ExecuteWriteResult object. This imitates a subset of the sqlite3.Cursor object: .rowcount The number of rows modified by the statement, or -1 if that number is unavailable. .lastrowid The row ID of the last modified row, as returned by sqlite3.Cursor.lastrowid . .description The same column metadata exposed by Python's sqlite3.Cursor.description : one tuple per returned column, or None if the statement does not return rows. .truncated True if the statement returned more rows than returning_limit . .fetchall() Returns any rows buffered by Datas… | 220 |
Advanced export
JSON shape: default, array, newline-delimited
CREATE VIRTUAL TABLE [sections_fts] USING FTS5 (
[title], [content],
tokenize='porter',
content=[sections]
);