{"rowid": 201, "title": "Per-database and per-table metadata", "content": "Metadata at the top level of the file will be shown on the index page and in the\n footer on every page of the site. The license and source is expected to apply to\n all of your data. \n You can also provide metadata at the per-database or per-table level, like this: \n [[[cog\nmetadata_example(cog, {\n \"databases\": {\n \"database1\": {\n \"source\": \"Alternative source\",\n \"source_url\": \"http://example.com/\",\n \"tables\": {\n \"example_table\": {\n \"description_html\": \"Custom table description\",\n \"license\": \"CC BY 3.0 US\",\n \"license_url\": \"https://creativecommons.org/licenses/by/3.0/us/\"\n }\n }\n }\n }\n}) \n ]]] \n [[[end]]] \n Each of the top-level metadata fields can be used at the database and table level.", "sections_fts": 437, "rank": null} {"rowid": 202, "title": "Source, license and about", "content": "The three visible metadata fields you can apply to everything, specific databases or specific tables are source, license and about. All three are optional. \n source and source_url should be used to indicate where the underlying data came from. \n license and license_url should be used to indicate the license under which the data can be used. \n about and about_url can be used to link to further information about the project - an accompanying blog entry for example. \n For each of these you can provide just the *_url field and Datasette will treat that as the default link label text and display the URL directly on the page.", "sections_fts": 437, "rank": null} {"rowid": 203, "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 [[[cog\nmetadata_example(cog, {\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 ]]] \n [[[end]]] \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 .", "sections_fts": 437, "rank": null} {"rowid": 204, "title": "Specifying units for a column", "content": "Datasette supports attaching units to a column, which will be used when displaying\n values from that column. SI prefixes will be used where appropriate. \n Column units are configured in the metadata like so: \n [[[cog\nmetadata_example(cog, {\n \"databases\": {\n \"database1\": {\n \"tables\": {\n \"example_table\": {\n \"units\": {\n \"column1\": \"metres\",\n \"column2\": \"Hz\"\n }\n }\n }\n }\n }\n}) \n ]]] \n [[[end]]] \n Units are interpreted using Pint , and you can see the full list of available units in\n Pint's unit registry . You can also add custom units to the metadata, which will be\n registered with Pint: \n [[[cog\nmetadata_example(cog, {\n \"custom_units\": [\n \"decibel = [] = dB\"\n ]\n}) \n ]]] \n [[[end]]]", "sections_fts": 437, "rank": null} {"rowid": 205, "title": "Setting a default sort order", "content": "By default Datasette tables are sorted by primary key. You can over-ride this default for a specific table using the \"sort\" or \"sort_desc\" metadata properties: \n [[[cog\nmetadata_example(cog, {\n \"databases\": {\n \"mydatabase\": {\n \"tables\": {\n \"example_table\": {\n \"sort\": \"created\"\n }\n }\n }\n }\n}) \n ]]] \n [[[end]]] \n Or use \"sort_desc\" to sort in descending order: \n [[[cog\nmetadata_example(cog, {\n \"databases\": {\n \"mydatabase\": {\n \"tables\": {\n \"example_table\": {\n \"sort_desc\": \"created\"\n }\n }\n }\n }\n}) \n ]]] \n [[[end]]]", "sections_fts": 437, "rank": null} {"rowid": 206, "title": "Setting a custom page size", "content": "Datasette defaults to displaying 100 rows per page, for both tables and views. You can change this default page size on a per-table or per-view basis using the \"size\" key in metadata.json : \n [[[cog\nmetadata_example(cog, {\n \"databases\": {\n \"mydatabase\": {\n \"tables\": {\n \"example_table\": {\n \"size\": 10\n }\n }\n }\n }\n}) \n ]]] \n [[[end]]] \n This size can still be over-ridden by passing e.g. ?_size=50 in the query string.", "sections_fts": 437, "rank": null} {"rowid": 207, "title": "Setting which columns can be used for sorting", "content": "Datasette allows any column to be used for sorting by default. If you need to\n control which columns are available for sorting you can do so using the optional\n sortable_columns key: \n [[[cog\nmetadata_example(cog, {\n \"databases\": {\n \"database1\": {\n \"tables\": {\n \"example_table\": {\n \"sortable_columns\": [\n \"height\",\n \"weight\"\n ]\n }\n }\n }\n }\n}) \n ]]] \n [[[end]]] \n This will restrict sorting of example_table to just the height and\n weight columns. \n You can also disable sorting entirely by setting \"sortable_columns\": [] \n You can use sortable_columns to enable specific sort orders for a view called name_of_view in the database my_database like so: \n [[[cog\nmetadata_example(cog, {\n \"databases\": {\n \"my_database\": {\n \"tables\": {\n \"name_of_view\": {\n \"sortable_columns\": [\n \"clicks\",\n \"impressions\"\n ]\n }\n }\n }\n }\n}) \n ]]] \n [[[end]]]", "sections_fts": 437, "rank": null} {"rowid": 208, "title": "Specifying the label column for a table", "content": "Datasette's HTML interface attempts to display foreign key references as\n labelled hyperlinks. By default, it looks for referenced tables that only have\n two columns: a primary key column and one other. It assumes that the second\n column should be used as the link label. \n If your table has more than two columns you can specify which column should be\n used for the link label with the label_column property: \n [[[cog\nmetadata_example(cog, {\n \"databases\": {\n \"database1\": {\n \"tables\": {\n \"example_table\": {\n \"label_column\": \"title\"\n }\n }\n }\n }\n}) \n ]]] \n [[[end]]]", "sections_fts": 437, "rank": null} {"rowid": 209, "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 [[[cog\nmetadata_example(cog, {\n \"databases\": {\n \"database1\": {\n \"tables\": {\n \"example_table\": {\n \"hidden\": True\n }\n }\n }\n }\n}) \n ]]] \n [[[end]]]", "sections_fts": 437, "rank": null} {"rowid": 210, "title": "Metadata reference", "content": "A full reference of every supported option in a metadata.json or metadata.yaml file.", "sections_fts": 437, "rank": null} {"rowid": 211, "title": "Top-level metadata", "content": "\"Top-level\" metadata refers to fields that can be specified at the root level of a metadata file. These attributes are meant to describe the entire Datasette instance. \n The following are the full list of allowed top-level metadata fields: \n \n \n title \n \n \n description \n \n \n description_html \n \n \n license \n \n \n license_url \n \n \n source \n \n \n source_url", "sections_fts": 437, "rank": null} {"rowid": 212, "title": "Database-level metadata", "content": "\"Database-level\" metadata refers to fields that can be specified for each database in a Datasette instance. These attributes should be listed under a database inside the \"databases\" field. \n The following are the full list of allowed database-level metadata fields: \n \n \n source \n \n \n source_url \n \n \n license \n \n \n license_url \n \n \n about \n \n \n about_url", "sections_fts": 437, "rank": null} {"rowid": 213, "title": "Table-level metadata", "content": "\"Table-level\" metadata refers to fields that can be specified for each table in a Datasette instance. These attributes should be listed under a specific table using the \"tables\" field. \n The following are the full list of allowed table-level metadata fields: \n \n \n source \n \n \n source_url \n \n \n license \n \n \n license_url \n \n \n about \n \n \n about_url \n \n \n hidden \n \n \n sort/sort_desc \n \n \n size \n \n \n sortable_columns \n \n \n label_column \n \n \n facets \n \n \n fts_table \n \n \n fts_pk \n \n \n searchmode \n \n \n columns", "sections_fts": 437, "rank": null} {"rowid": 214, "title": "Pages and API endpoints", "content": "The Datasette web application offers a number of different pages that can be accessed to explore the data in question, each of which is accompanied by an equivalent JSON API.", "sections_fts": 437, "rank": null} {"rowid": 215, "title": "Top-level index", "content": "The root page of any Datasette installation is an index page that lists all of the currently attached databases. Some examples: \n \n \n fivethirtyeight.datasettes.com \n \n \n global-power-plants.datasettes.com \n \n \n register-of-members-interests.datasettes.com \n \n \n Add /.json to the end of the URL for the JSON version of the underlying data: \n \n \n fivethirtyeight.datasettes.com/.json \n \n \n global-power-plants.datasettes.com/.json \n \n \n register-of-members-interests.datasettes.com/.json", "sections_fts": 437, "rank": null} {"rowid": 216, "title": "Database", "content": "Each database has a page listing the tables, views and canned queries available for that database. If the execute-sql permission is enabled (it's on by default) there will also be an interface for executing arbitrary SQL select queries against the data. \n Examples: \n \n \n fivethirtyeight.datasettes.com/fivethirtyeight \n \n \n global-power-plants.datasettes.com/global-power-plants \n \n \n The JSON version of this page provides programmatic access to the underlying data: \n \n \n fivethirtyeight.datasettes.com/fivethirtyeight.json \n \n \n global-power-plants.datasettes.com/global-power-plants.json", "sections_fts": 437, "rank": null} {"rowid": 217, "title": "Hidden tables", "content": "Some tables listed on the database page are treated as hidden. Hidden tables are not completely invisible - they can be accessed through the \"hidden tables\" link at the bottom of the page. They are hidden because they represent low-level implementation details which are generally not useful to end-users of Datasette. \n The following tables are hidden by default: \n \n \n Any table with a name that starts with an underscore - this is a Datasette convention to help plugins easily hide their own internal tables. \n \n \n Tables that have been configured as \"hidden\": true using Hiding tables . \n \n \n *_fts tables that implement SQLite full-text search indexes. \n \n \n Tables relating to the inner workings of the SpatiaLite SQLite extension. \n \n \n sqlite_stat tables used to store statistics used by the query optimizer.", "sections_fts": 437, "rank": null} {"rowid": 218, "title": "Table", "content": "The table page is the heart of Datasette: it allows users to interactively explore the contents of a database table, including sorting, filtering, Full-text search and applying Facets . \n The HTML interface is worth spending some time exploring. As with other pages, you can return the JSON data by appending .json to the URL path, before any ? query string arguments. \n The query string arguments are described in more detail here: Table arguments \n You can also use the table page to interactively construct a SQL query - by applying different filters and a sort order for example - and then click the \"View and edit SQL\" link to see the SQL query that was used for the page and edit and re-submit it. \n Some examples: \n \n \n ../items lists all of the line-items registered by UK MPs as potential conflicts of interest. It demonstrates Datasette's support for Full-text search . \n \n \n ../antiquities-act%2Factions_under_antiquities_act is an interface for exploring the \"actions under the antiquities act\" data table published by FiveThirtyEight. \n \n \n ../global-power-plants?country_long=United+Kingdom&primary_fuel=Gas is a filtered table page showing every Gas power plant in the United Kingdom. It includes some default facets (configured using its metadata.json ) and uses the datasette-cluster-map plugin to show a map of the results.", "sections_fts": 437, "rank": null} {"rowid": 219, "title": "Row", "content": "Every row in every Datasette table has its own URL. This means individual records can be linked to directly. \n Table cells with extremely long text contents are truncated on the table view according to the truncate_cells_html setting. If a cell has been truncated the full length version of that cell will be available on the row page. \n Rows which are the targets of foreign key references from other tables will show a link to a filtered search for all records that reference that row. Here's an example from the Registers of Members Interests database: \n ../people/uk~2Eorg~2Epublicwhip~2Fperson~2F10001 \n Note that this URL includes the encoded primary key of the record. \n Here's that same page as JSON: \n ../people/uk~2Eorg~2Epublicwhip~2Fperson~2F10001.json", "sections_fts": 437, "rank": null} {"rowid": 220, "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", "sections_fts": 437, "rank": null} {"rowid": 221, "title": "Setting up a Datasette test instance", "content": "The above example shows the easiest way to start writing tests against a Datasette instance: \n from datasette.app import Datasette\nimport pytest\n\n\n@pytest.mark.asyncio\nasync def test_plugin_is_installed():\n datasette = Datasette(memory=True)\n response = await datasette.client.get(\"/-/plugins.json\")\n assert response.status_code == 200 \n Creating a Datasette() instance like this as useful shortcut in tests, but there is one detail you need to be aware of. It's important to ensure that the async method .invoke_startup() is called on that instance. You can do that like this: \n datasette = Datasette(memory=True)\nawait datasette.invoke_startup() \n This method registers any startup(datasette) or prepare_jinja2_environment(env, datasette) plugins that might themselves need to make async calls. \n If you are using await datasette.client.get() and similar methods then you don't need to worry about this - Datasette automatically calls invoke_startup() the first time it handles a request.", "sections_fts": 437, "rank": null} {"rowid": 222, "title": "Using datasette.client in tests", "content": "The datasette.client mechanism is designed for use in tests. It provides access to a pre-configured HTTPX async client instance that can make GET, POST and other HTTP requests against a Datasette instance from inside a test. \n A simple test looks like this: \n @pytest.mark.asyncio\nasync def test_homepage():\n ds = Datasette(memory=True)\n response = await ds.client.get(\"/\")\n html = response.text\n assert \"

\" in html\n \n Or for a JSON API: \n @pytest.mark.asyncio\nasync def test_actor_is_null():\n ds = Datasette(memory=True)\n response = await ds.client.get(\"/-/actor.json\")\n assert response.json() == {\"actor\": None}\n \n To make requests as an authenticated actor, create a signed ds_cookie using the datasette.client.actor_cookie() helper function and pass it in cookies= like this: \n @pytest.mark.asyncio\nasync def test_signed_cookie_actor():\n ds = Datasette(memory=True)\n cookies = {\"ds_actor\": ds.client.actor_cookie({\"id\": \"root\"})}\n response = await ds.client.get(\"/-/actor.json\", cookies=cookies)\n assert response.json() == {\"actor\": {\"id\": \"root\"}}", "sections_fts": 437, "rank": null} {"rowid": 223, "title": "Using pdb for errors thrown inside Datasette", "content": "If an exception occurs within Datasette itself during a test, the response returned to your plugin will have a response.status_code value of 500. \n You can add pdb=True to the Datasette constructor to drop into a Python debugger session inside your test run instead of getting back a 500 response code. This is equivalent to running the datasette command-line tool with the --pdb option. \n Here's what that looks like in a test function: \n def test_that_opens_the_debugger_or_errors():\n ds = Datasette([db_path], pdb=True)\n response = await ds.client.get(\"/\") \n If you use this pattern you will need to run pytest with the -s option to avoid capturing stdin/stdout in order to interact with the debugger prompt.", "sections_fts": 437, "rank": null} {"rowid": 224, "title": "Using pytest fixtures", "content": "Pytest fixtures can be used to create initial testable objects which can then be used by multiple tests. \n A common pattern for Datasette plugins is to create a fixture which sets up a temporary test database and wraps it in a Datasette instance. \n Here's an example that uses the sqlite-utils library to populate a temporary test database. It also sets the title of that table using a simulated metadata.json configuration: \n from datasette.app import Datasette\nimport pytest\nimport sqlite_utils\n\n\n@pytest.fixture(scope=\"session\")\ndef datasette(tmp_path_factory):\n db_directory = tmp_path_factory.mktemp(\"dbs\")\n db_path = db_directory / \"test.db\"\n db = sqlite_utils.Database(db_path)\n db[\"dogs\"].insert_all(\n [\n {\"id\": 1, \"name\": \"Cleo\", \"age\": 5},\n {\"id\": 2, \"name\": \"Pancakes\", \"age\": 4},\n ],\n pk=\"id\",\n )\n datasette = Datasette(\n [db_path],\n metadata={\n \"databases\": {\n \"test\": {\n \"tables\": {\n \"dogs\": {\"title\": \"Some dogs\"}\n }\n }\n }\n },\n )\n return datasette\n\n\n@pytest.mark.asyncio\nasync def test_example_table_json(datasette):\n response = await datasette.client.get(\n \"/test/dogs.json?_shape=array\"\n )\n assert response.status_code == 200\n assert response.json() == [\n {\"id\": 1, \"name\": \"Cleo\", \"age\": 5},\n {\"id\": 2, \"name\": \"Pancakes\", \"age\": 4},\n ]\n\n\n@pytest.mark.asyncio\nasync def test_example_table_html(datasette):\n response = await datasette.client.get(\"/test/dogs\")\n assert \">Some dogs

\" in response.text \n Here the datasette() function defines the fixture, which is than automatically passed to the two test functions based on pytest automatically matching their datasette function parameters. \n The @pytest.fixture(scope=\"session\") line here ensures the fixture is reused for the full pytest execution session. This means that the temporary database file will be created once and reused for each test. \n If you want to create that test database repeatedly for every individual test function, write the fixture function like this instead. You may want to do this if your plugin modifies the database contents in some way: \n @pytest.fixture\ndef datasette(tmp_path_factory):\n # This fixture will be executed repeatedly for every test\n ...", "sections_fts": 437, "rank": null} {"rowid": 225, "title": "Testing outbound HTTP calls with pytest-httpx", "content": "If your plugin makes outbound HTTP calls - for example datasette-auth-github or datasette-import-table - you may need to mock those HTTP requests in your tests. \n The pytest-httpx package is a useful library for mocking calls. It can be tricky to use with Datasette though since it mocks all HTTPX requests, and Datasette's own testing mechanism uses HTTPX internally. \n To avoid breaking your tests, you can return [\"localhost\"] from the non_mocked_hosts() fixture. \n As an example, here's a very simple plugin which executes an HTTP response and returns the resulting content: \n from datasette import hookimpl\nfrom datasette.utils.asgi import Response\nimport httpx\n\n\n@hookimpl\ndef register_routes():\n return [\n (r\"^/-/fetch-url$\", fetch_url),\n ]\n\n\nasync def fetch_url(datasette, request):\n if request.method == \"GET\":\n return Response.html(\n \"\"\"\n
\n \n \n
\"\"\".format(\n request.scope[\"csrftoken\"]()\n )\n )\n vars = await request.post_vars()\n url = vars[\"url\"]\n return Response.text(httpx.get(url).text) \n Here's a test for that plugin that mocks the HTTPX outbound request: \n from datasette.app import Datasette\nimport pytest\n\n\n@pytest.fixture\ndef non_mocked_hosts():\n # This ensures httpx-mock will not affect Datasette's own\n # httpx calls made in the tests by datasette.client:\n return [\"localhost\"]\n\n\nasync def test_outbound_http_call(httpx_mock):\n httpx_mock.add_response(\n url=\"https://www.example.com/\",\n text=\"Hello world\",\n )\n datasette = Datasette([], memory=True)\n response = await datasette.client.post(\n \"/-/fetch-url\",\n data={\"url\": \"https://www.example.com/\"},\n )\n assert response.text == \"Hello world\"\n\n outbound_request = httpx_mock.get_request()\n assert (\n outbound_request.url == \"https://www.example.com/\"\n )", "sections_fts": 437, "rank": null} {"rowid": 226, "title": "Registering a plugin for the duration of a test", "content": "When writing tests for plugins you may find it useful to register a test plugin just for the duration of a single test. You can do this using pm.register() and pm.unregister() like this: \n from datasette import hookimpl\nfrom datasette.app import Datasette\nfrom datasette.plugins import pm\nimport pytest\n\n\n@pytest.mark.asyncio\nasync def test_using_test_plugin():\n class TestPlugin:\n __name__ = \"TestPlugin\"\n\n # Use hookimpl and method names to register hooks\n @hookimpl\n def register_routes(self):\n return [\n (r\"^/error$\", lambda: 1 / 0),\n ]\n\n pm.register(TestPlugin(), name=\"undo\")\n try:\n # The test implementation goes here\n datasette = Datasette()\n response = await datasette.client.get(\"/error\")\n assert response.status_code == 500\n finally:\n pm.unregister(name=\"undo\") \n To reuse the same temporary plugin in multiple tests, you can register it inside a fixture in your conftest.py file like this: \n from datasette import hookimpl\nfrom datasette.app import Datasette\nfrom datasette.plugins import pm\nimport pytest\nimport pytest_asyncio\n\n\n@pytest_asyncio.fixture\nasync def datasette_with_plugin():\n class TestPlugin:\n __name__ = \"TestPlugin\"\n\n @hookimpl\n def register_routes(self):\n return [\n (r\"^/error$\", lambda: 1 / 0),\n ]\n\n pm.register(TestPlugin(), name=\"undo\")\n try:\n yield Datasette()\n finally:\n pm.unregister(name=\"undo\")\n \n Note the yield statement here - this ensures that the finally: block that unregisters the plugin is executed only after the test function itself has completed. \n Then in a test: \n @pytest.mark.asyncio\nasync def test_error(datasette_with_plugin):\n response = await datasette_with_plugin.client.get(\"/error\")\n assert response.status_code == 500", "sections_fts": 437, "rank": null} {"rowid": 227, "title": "Internals for plugins", "content": "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.", "sections_fts": 437, "rank": null} {"rowid": 228, "title": "Request object", "content": "The request object is passed to various plugin hooks. It represents an incoming HTTP request. It has the following properties: \n \n \n .scope - dictionary \n \n The ASGI scope that was used to construct this request, described in the ASGI HTTP connection scope specification. \n \n \n \n .method - string \n \n The HTTP method for this request, usually GET or POST . \n \n \n \n .url - string \n \n The full URL for this request, e.g. https://latest.datasette.io/fixtures . \n \n \n \n .scheme - string \n \n The request scheme - usually https or http . \n \n \n \n .headers - dictionary (str -> str) \n \n A dictionary of incoming HTTP request headers. Header names have been converted to lowercase. \n \n \n \n .cookies - dictionary (str -> str) \n \n A dictionary of incoming cookies \n \n \n \n .host - string \n \n The host header from the incoming request, e.g. latest.datasette.io or localhost . \n \n \n \n .path - string \n \n The path of the request excluding the query string, e.g. /fixtures . \n \n \n \n .full_path - string \n \n The path of the request including the query string if one is present, e.g. /fixtures?sql=select+sqlite_version() . \n \n \n \n .query_string - string \n \n The query string component of the request, without the ? - e.g. name__contains=sam&age__gt=10 . \n \n \n \n .args - MultiParams \n \n An object representing the parsed query string parameters, see below. \n \n \n \n .url_vars - dictionary (str -> str) \n \n Variables extracted from the URL path, if that path was defined using a regular expression. See register_routes(datasette) . \n \n \n \n .actor - dictionary (str -> Any) or None \n \n The currently authenticated actor (see actors ), or None if the request is unauthenticated. \n \n \n \n The object also has two awaitable methods: \n \n \n await request.post_vars() - dictionary \n \n Returns a dictionary of form variables that were submitted in the request body via POST . Don't forget to read about CSRF protection ! \n \n \n \n await request.post_body() - bytes \n \n Returns the un-parsed body of a request submitted by POST - useful for things like incoming JSON data. \n \n \n \n And a class method that can be used to create fake request objects for use in tests: \n \n \n fake(path_with_query_string, method=\"GET\", scheme=\"http\", url_vars=None) \n \n Returns a Request instance for the specified path and method. For example: \n from datasette import Request\nfrom pprint import pprint\n\nrequest = Request.fake(\n \"/fixtures/facetable/\",\n url_vars={\"database\": \"fixtures\", \"table\": \"facetable\"},\n)\npprint(request.scope) \n This outputs: \n {'http_version': '1.1',\n 'method': 'GET',\n 'path': '/fixtures/facetable/',\n 'query_string': b'',\n 'raw_path': b'/fixtures/facetable/',\n 'scheme': 'http',\n 'type': 'http',\n 'url_route': {'kwargs': {'database': 'fixtures', 'table': 'facetable'}}}", "sections_fts": 437, "rank": null} {"rowid": 229, "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.", "sections_fts": 437, "rank": null} {"rowid": 230, "title": "Response class", "content": "The Response class can be returned from view functions that have been registered using the register_routes(datasette) hook. \n The Response() constructor takes the following arguments: \n \n \n body - string \n \n The body of the response. \n \n \n \n status - integer (optional) \n \n The HTTP status - defaults to 200. \n \n \n \n headers - dictionary (optional) \n \n A dictionary of extra HTTP headers, e.g. {\"x-hello\": \"world\"} . \n \n \n \n content_type - string (optional) \n \n The content-type for the response. Defaults to text/plain . \n \n \n \n For example: \n from datasette.utils.asgi import Response\n\nresponse = Response(\n \"This is XML\",\n content_type=\"application/xml; charset=utf-8\",\n) \n The quickest way to create responses is using the Response.text(...) , Response.html(...) , Response.json(...) or Response.redirect(...) helper methods: \n from datasette.utils.asgi import Response\n\nhtml_response = Response.html(\"This is HTML\")\njson_response = Response.json({\"this_is\": \"json\"})\ntext_response = Response.text(\n \"This will become utf-8 encoded text\"\n)\n# Redirects are served as 302, unless you pass status=301:\nredirect_response = Response.redirect(\n \"https://latest.datasette.io/\"\n) \n 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. \n Each of the helper methods take optional status= and headers= arguments, documented above.", "sections_fts": 437, "rank": null} {"rowid": 231, "title": "Returning a response with .asgi_send(send)", "content": "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. \n Create a Response object and then use await response.asgi_send(send) , passing the ASGI send function. For example: \n async def require_authorization(scope, receive, send):\n response = Response.text(\n \"401 Authorization Required\",\n headers={\n \"www-authenticate\": 'Basic realm=\"Datasette\", charset=\"UTF-8\"'\n },\n status=401,\n )\n await response.asgi_send(send)", "sections_fts": 437, "rank": null} {"rowid": 232, "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 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", "sections_fts": 437, "rank": null} {"rowid": 233, "title": "Datasette class", "content": "This object is an instance of the Datasette class, passed to many plugin hooks as an argument called datasette . \n You can create your own instance of this - for example to help write tests for a plugin - like so: \n from datasette.app import Datasette\n\n# With no arguments a single in-memory database will be attached\ndatasette = Datasette()\n\n# The files= argument can load files from disk\ndatasette = Datasette(files=[\"/path/to/my-database.db\"])\n\n# Pass metadata as a JSON dictionary like this\ndatasette = Datasette(\n files=[\"/path/to/my-database.db\"],\n metadata={\n \"databases\": {\n \"my-database\": {\n \"description\": \"This is my database\"\n }\n }\n },\n) \n Constructor parameters include: \n \n \n files=[...] - a list of database files to open \n \n \n immutables=[...] - a list of database files to open in immutable mode \n \n \n metadata={...} - a dictionary of Metadata \n \n \n config_dir=... - the configuration directory to use, stored in datasette.config_dir", "sections_fts": 437, "rank": null} {"rowid": 234, "title": ".databases", "content": "Property exposing a collections.OrderedDict of databases currently connected to Datasette. \n 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. \n All databases are listed, irrespective of user permissions.", "sections_fts": 437, "rank": null} {"rowid": 235, "title": ".permissions", "content": "Property exposing a dictionary of permissions that have been registered using the register_permissions(datasette) plugin hook. \n The dictionary keys are the permission names - e.g. view-instance - and the values are Permission() objects describing the permission. Here is a description of that object .", "sections_fts": 437, "rank": null} {"rowid": 236, "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 datasette.yaml . 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 .", "sections_fts": 437, "rank": null} {"rowid": 237, "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.", "sections_fts": 437, "rank": null} {"rowid": 238, "title": "await .actors_from_ids(actor_ids)", "content": "actor_ids - list of strings or integers \n \n A list of actor IDs to look up. \n \n \n \n Returns a dictionary, where the keys are the IDs passed to it and the values are the corresponding actor dictionaries. \n This method is mainly designed to be used with plugins. See the actors_from_ids(datasette, actor_ids) documentation for details. \n If no plugins that implement that hook are installed, the default return value looks like this: \n {\n \"1\": {\"id\": \"1\"},\n \"2\": {\"id\": \"2\"}\n}", "sections_fts": 437, "rank": null} {"rowid": 239, "title": "await .permission_allowed(actor, action, resource=None, default=...)", "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, False or None \n \n What value should be returned by default if nothing provides an opinion on this permission check.\n Set to True for default allow or False for default deny.\n If not specified the default from the Permission() tuple that was registered using register_permissions(datasette) will be used. \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 datasette.yaml , 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.", "sections_fts": 437, "rank": null} {"rowid": 240, "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 datasette.ensure_permissions(\n request.actor,\n [\n (\"view-table\", (database, table)),\n (\"view-database\", database),\n \"view-instance\",\n ],\n)", "sections_fts": 437, "rank": null} {"rowid": 241, "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 datasette.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 datasette.check_visibility(\n request.actor,\n permissions=[\n (\"view-table\", (database, table)),\n (\"view-database\", database),\n \"view-instance\",\n ],\n)", "sections_fts": 437, "rank": null} {"rowid": 242, "title": ".create_token(actor_id, expires_after=None, restrict_all=None, restrict_database=None, restrict_resource=None)", "content": "actor_id - string \n \n The ID of the actor to create a token for. \n \n \n \n expires_after - int, optional \n \n The number of seconds after which the token should expire. \n \n \n \n restrict_all - iterable, optional \n \n A list of actions that this token should be restricted to across all databases and resources. \n \n \n \n restrict_database - dict, optional \n \n For restricting actions within specific databases, e.g. {\"mydb\": [\"view-table\", \"view-query\"]} . \n \n \n \n restrict_resource - dict, optional \n \n For restricting actions to specific resources (tables, SQL views and Canned queries ) within a database. For example: {\"mydb\": {\"mytable\": [\"insert-row\", \"update-row\"]}} . \n \n \n \n This method returns a signed API token of the format dstok_... which can be used to authenticate requests to the Datasette API. \n All tokens must have an actor_id string indicating the ID of the actor which the token will act on behalf of. \n 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: \n token = datasette.create_token(\n actor_id=\"user1\",\n expires_after=3600,\n) \n The three restrict_* arguments can be used to create a token that has additional restrictions beyond what the associated actor is allowed to do. \n The following example creates a token that can access view-instance and view-table across everything, can additionally use view-query for anything in the docs database and is allowed to execute insert-row and update-row in the attachments table in that database: \n token = datasette.create_token(\n actor_id=\"user1\",\n restrict_all=(\"view-instance\", \"view-table\"),\n restrict_database={\"docs\": (\"view-query\",)},\n restrict_resource={\n \"docs\": {\n \"attachments\": (\"insert-row\", \"update-row\")\n }\n },\n)", "sections_fts": 437, "rank": null} {"rowid": 243, "title": ".get_permission(name_or_abbr)", "content": "name_or_abbr - string \n \n The name or abbreviation of the permission to look up, e.g. view-table or vt . \n \n \n \n Returns a Permission object representing the permission, or raises a KeyError if one is not found.", "sections_fts": 437, "rank": null} {"rowid": 244, "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.", "sections_fts": 437, "rank": null} {"rowid": 245, "title": ".get_internal_database()", "content": "Returns a database object for reading and writing to the private internal database .", "sections_fts": 437, "rank": null} {"rowid": 246, "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)", "sections_fts": 437, "rank": null} {"rowid": 247, "title": ".add_memory_database(name)", "content": "Adds a shared in-memory database with the specified name: \n datasette.add_memory_database(\"statistics\") \n This is a shortcut for the following: \n from datasette.database import Database\n\ndatasette.add_database(\n Database(datasette, memory_name=\"statistics\")\n) \n Using either of these pattern will result in the in-memory database being served at /statistics .", "sections_fts": 437, "rank": null} {"rowid": 248, "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.", "sections_fts": 437, "rank": null} {"rowid": 249, "title": "await .track_event(event)", "content": "event - Event \n \n An instance of a subclass of datasette.events.Event . \n \n \n \n Plugins can call this to track events, using classes they have previously registered. See Event tracking for details. \n The event will then be passed to all plugins that have registered to receive events using the track_event(datasette, event) hook. \n Example usage, assuming the plugin has previously registered the BanUserEvent class: \n await datasette.track_event(\n BanUserEvent(user={\"id\": 1, \"username\": \"cleverbot\"})\n)", "sections_fts": 437, "rank": null} {"rowid": 250, "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\") .", "sections_fts": 437, "rank": null} {"rowid": 251, "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.", "sections_fts": 437, "rank": null} {"rowid": 252, "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.", "sections_fts": 437, "rank": null} {"rowid": 253, "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.", "sections_fts": 437, "rank": null} {"rowid": 254, "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\")", "sections_fts": 437, "rank": null} {"rowid": 255, "title": ".resolve_database(request)", "content": "request - Request object \n \n A request object \n \n \n \n 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. \n This returns a Database instance. \n 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.", "sections_fts": 437, "rank": null} {"rowid": 256, "title": ".resolve_table(request)", "content": "request - Request object \n \n A request object \n \n \n \n This assumes that the regular expression for your route declares both a database and a table named group. \n It returns a ResolvedTable named tuple instance with the following fields: \n \n \n db - Database \n \n The database object \n \n \n \n table - string \n \n The name of the table (or view) \n \n \n \n is_view - boolean \n \n True if this is a view, False if it is a table \n \n \n \n If the database or table cannot be found it raises a datasette.utils.asgi.DatabaseNotFound exception. \n 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.", "sections_fts": 437, "rank": null} {"rowid": 257, "title": ".resolve_row(request)", "content": "request - Request object \n \n A request object \n \n \n \n This method assumes your route declares named groups for database , table and pks . \n It returns a ResolvedRow named tuple instance with the following fields: \n \n \n db - Database \n \n The database object \n \n \n \n table - string \n \n The name of the table \n \n \n \n sql - string \n \n SQL snippet that can be used in a WHERE clause to select the row \n \n \n \n params - dict \n \n Parameters that should be passed to the SQL query \n \n \n \n pks - list \n \n List of primary key column names \n \n \n \n pk_values - list \n \n List of primary key values decoded from the URL \n \n \n \n row - sqlite3.Row \n \n The row itself \n \n \n \n If the database or table cannot be found it raises a datasette.utils.asgi.DatabaseNotFound exception. \n If the table does not exist it raises a datasette.utils.asgi.TableNotFound exception. \n If the row cannot be found it raises a datasette.utils.asgi.RowNotFound exception. This has .database_name , .table and .pk_values attributes, extracted from the request path.", "sections_fts": 437, "rank": null} {"rowid": 258, "title": "datasette.client", "content": "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. \n 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 . \n It offers the following methods: \n \n \n await datasette.client.get(path, **kwargs) - returns HTTPX Response \n \n Execute an internal GET request against that path. \n \n \n \n await datasette.client.post(path, **kwargs) - returns HTTPX Response \n \n Execute an internal POST request. Use data={\"name\": \"value\"} to pass form parameters. \n \n \n \n await datasette.client.options(path, **kwargs) - returns HTTPX Response \n \n Execute an internal OPTIONS request. \n \n \n \n await datasette.client.head(path, **kwargs) - returns HTTPX Response \n \n Execute an internal HEAD request. \n \n \n \n await datasette.client.put(path, **kwargs) - returns HTTPX Response \n \n Execute an internal PUT request. \n \n \n \n await datasette.client.patch(path, **kwargs) - returns HTTPX Response \n \n Execute an internal PATCH request. \n \n \n \n await datasette.client.delete(path, **kwargs) - returns HTTPX Response \n \n Execute an internal DELETE request. \n \n \n \n await datasette.client.request(method, path, **kwargs) - returns HTTPX Response \n \n Execute an internal request with the given HTTP method against that path. \n \n \n \n These methods can be used with datasette.urls - for example: \n table_json = (\n await datasette.client.get(\n datasette.urls.table(\n \"fixtures\", \"facetable\", format=\"json\"\n )\n )\n).json() \n datasette.client methods automatically take the current base_url setting into account, whether or not you use the datasette.urls family of methods to construct the path. \n For documentation on available **kwargs options and the shape of the HTTPX Response object refer to the HTTPX Async documentation .", "sections_fts": 437, "rank": null} {"rowid": 259, "title": "datasette.urls", "content": "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. \n \n \n datasette.urls.instance(format=None) \n \n Returns the URL to the Datasette instance root page. This is usually \"/\" . \n \n \n \n datasette.urls.path(path, format=None) \n \n Takes a path and returns the full path, taking base_url into account. \n 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/ \n \n \n \n datasette.urls.logout() \n \n Returns the URL to the logout page, usually \"/-/logout\" \n \n \n \n datasette.urls.static(path) \n \n Returns the URL of one of Datasette's default static assets, for example \"/-/static/app.css\" \n \n \n \n datasette.urls.static_plugins(plugin_name, path) \n \n Returns the URL of one of the static assets belonging to a plugin. \n datasette.urls.static_plugins(\"datasette_cluster_map\", \"datasette-cluster-map.js\") would return \"/-/static-plugins/datasette_cluster_map/datasette-cluster-map.js\" \n \n \n \n datasette.urls.static(path) \n \n Returns the URL of one of Datasette's default static assets, for example \"/-/static/app.css\" \n \n \n \n datasette.urls.database(database_name, format=None) \n \n Returns the URL to a database page, for example \"/fixtures\" \n \n \n \n datasette.urls.table(database_name, table_name, format=None) \n \n Returns the URL to a table page, for example \"/fixtures/facetable\" \n \n \n \n datasette.urls.query(database_name, query_name, format=None) \n \n Returns the URL to a query page, for example \"/fixtures/pragma_cache_size\" \n \n \n \n These functions can be accessed via the {{ urls }} object in Datasette templates, for example: \n Homepage\nFixtures database\nfacetable table\npragma_cache_size query \n Use the format=\"json\" (or \"csv\" or other formats supported by plugins) arguments to get back URLs to the JSON representation. This is the path with .json added on the end. \n These methods each return a datasette.utils.PrefixedUrlString object, which is a subclass of the Python str type. This allows the logic that considers the base_url setting to detect if that prefix has already been applied to the path.", "sections_fts": 437, "rank": null} {"rowid": 260, "title": "Database class", "content": "Instances of the Database class can be used to execute queries against attached SQLite databases, and to run introspection against their schemas.", "sections_fts": 437, "rank": null} {"rowid": 261, "title": "Database(ds, path=None, is_mutable=True, is_memory=False, memory_name=None)", "content": "The Database() constructor can be used by plugins, in conjunction with .add_database(db, name=None, route=None) , to create and register new databases. \n The arguments are as follows: \n \n \n ds - Datasette class (required) \n \n The Datasette instance you are attaching this database to. \n \n \n \n path - string \n \n Path to a SQLite database file on disk. \n \n \n \n is_mutable - boolean \n \n Set this to False to cause Datasette to open the file in immutable mode. \n \n \n \n is_memory - boolean \n \n Use this to create non-shared memory connections. \n \n \n \n memory_name - string or None \n \n 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. \n \n \n \n The first argument is the datasette instance you are attaching to, the second is a path= , then is_mutable and is_memory are both optional arguments.", "sections_fts": 437, "rank": null} {"rowid": 262, "title": "db.hash", "content": "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 .", "sections_fts": 437, "rank": null} {"rowid": 263, "title": "await db.execute(sql, ...)", "content": "Executes a SQL query against the database and returns the resulting rows (see Results ). \n \n \n sql - string (required) \n \n The SQL query to execute. This can include ? or :named parameters. \n \n \n \n params - list or dict \n \n A list or dictionary of values to use for the parameters. List for ? , dictionary for :named . \n \n \n \n truncate - boolean \n \n Should the rows returned by the query be truncated at the maximum page size? Defaults to True , set this to False to disable truncation. \n \n \n \n custom_time_limit - integer ms \n \n 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. \n \n \n \n page_size - integer \n \n Set a custom page size for truncation, over-riding the configured Datasette default. \n \n \n \n log_sql_errors - boolean \n \n Should any SQL errors be logged to the console in addition to being raised as an error? Defaults to True .", "sections_fts": 437, "rank": null} {"rowid": 264, "title": "Results", "content": "The db.execute() method returns a single Results object. This can be used to access the rows returned by the query. \n 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: \n info = []\nresults = await db.execute(\"select name from sqlite_master\")\nfor row in results:\n info.append(row[\"name\"]) \n The Results object also has the following properties and methods: \n \n \n .truncated - boolean \n \n 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. \n \n \n \n .columns - list of strings \n \n A list of column names returned by the query. \n \n \n \n .rows - list of sqlite3.Row \n \n 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] . \n \n \n \n .first() - row or None \n \n Returns the first row in the results, or None if no rows were returned. \n \n \n \n .single_value() \n \n Returns the value of the first column of the first row of results - but only if the query returned a single row with a single column. Raises a datasette.database.MultipleValues exception otherwise. \n \n \n \n .__len__() \n \n Calling len(results) returns the (truncated) number of returned results.", "sections_fts": 437, "rank": null} {"rowid": 265, "title": "await db.execute_fn(fn)", "content": "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 . \n Example usage: \n def get_version(conn):\n return conn.execute(\n \"select sqlite_version()\"\n ).fetchall()[0][0]\n\n\nversion = await db.execute_fn(get_version)", "sections_fts": 437, "rank": null} {"rowid": 266, "title": "await db.execute_write(sql, params=None, block=True)", "content": "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. \n This method can be used to queue up a non-SELECT SQL query to be executed against a single write connection to the database. \n You can pass additional SQL parameters as a tuple or dictionary. \n The method will block until the operation is completed, and the return value will be the return from calling conn.execute(...) using the underlying sqlite3 Python library. \n If you pass block=False this behavior changes to \"fire and forget\" - queries will be added to the write queue and executed in a separate thread while your code can continue to do other things. The method will return a UUID representing the queued task. \n Each call to execute_write() will be executed inside a transaction.", "sections_fts": 437, "rank": null} {"rowid": 267, "title": "await db.execute_write_script(sql, block=True)", "content": "Like execute_write() but can be used to send multiple SQL statements in a single string separated by semicolons, using the sqlite3 conn.executescript() method. \n Each call to execute_write_script() will be executed inside a transaction.", "sections_fts": 437, "rank": null} {"rowid": 268, "title": "await db.execute_write_many(sql, params_seq, block=True)", "content": "Like execute_write() but uses the sqlite3 conn.executemany() method. This will efficiently execute the same SQL statement against each of the parameters in the params_seq iterator, for example: \n await db.execute_write_many(\n \"insert into characters (id, name) values (?, ?)\",\n [(1, \"Melanie\"), (2, \"Selma\"), (2, \"Viktor\")],\n) \n Each call to execute_write_many() will be executed inside a transaction.", "sections_fts": 437, "rank": null} {"rowid": 269, "title": "await db.execute_write_fn(fn, block=True, transaction=True)", "content": "This method works like .execute_write() , but instead of a SQL statement you give it a callable Python function. Your function will be queued up and then called when the write connection is available, passing that connection as the argument to the function. \n The function can then perform multiple actions, safe in the knowledge that it has exclusive access to the single writable connection for as long as it is executing. \n \n fn needs to be a regular function, not an async def function. \n \n For example: \n def delete_and_return_count(conn):\n conn.execute(\"delete from some_table where id > 5\")\n return conn.execute(\n \"select count(*) from some_table\"\n ).fetchone()[0]\n\n\ntry:\n num_rows_left = await database.execute_write_fn(\n delete_and_return_count\n )\nexcept Exception as e:\n print(\"An error occurred:\", e) \n The value returned from await database.execute_write_fn(...) will be the return value from your function. \n If your function raises an exception that exception will be propagated up to the await line. \n By default your function will be executed inside a transaction. You can pass transaction=False to disable this behavior, though if you do that you should be careful to manually apply transactions - ideally using the with conn: pattern, or you may see OperationalError: database table is locked errors. \n If you specify block=False the method becomes fire-and-forget, queueing your function to be executed and then allowing your code after the call to .execute_write_fn() to continue running while the underlying thread waits for an opportunity to run your function. A UUID representing the queued task will be returned. Any exceptions in your code will be silently swallowed.", "sections_fts": 437, "rank": null} {"rowid": 270, "title": "await db.execute_isolated_fn(fn)", "content": "This method works is similar to execute_write_fn() but executes the provided function in an entirely isolated SQLite connection, which is opened, used and then closed again in a single call to this method. \n The prepare_connection() plugin hook is not executed against this connection. \n This allows plugins to execute database operations that might conflict with how database connections are usually configured. For example, running a VACUUM operation while bypassing any restrictions placed by the datasette-sqlite-authorizer plugin. \n Plugins can also use this method to load potentially dangerous SQLite extensions, use them to perform an operation and then have them safely unloaded at the end of the call, without risk of exposing them to other connections. \n Functions run using execute_isolated_fn() share the same queue as execute_write_fn() , which guarantees that no writes can be executed at the same time as the isolated function is executing. \n The return value of the function will be returned by this method. Any exceptions raised by the function will be raised out of the await line as well.", "sections_fts": 437, "rank": null} {"rowid": 271, "title": "db.close()", "content": "Closes all of the open connections to file-backed databases. This is mainly intended to be used by large test suites, to avoid hitting limits on the number of open files.", "sections_fts": 437, "rank": null} {"rowid": 272, "title": "Database introspection", "content": "The Database class also provides properties and methods for introspecting the database. \n \n \n db.name - string \n \n The name of the database - usually the filename without the .db prefix. \n \n \n \n db.size - integer \n \n The size of the database file in bytes. 0 for :memory: databases. \n \n \n \n db.mtime_ns - integer or None \n \n The last modification time of the database file in nanoseconds since the epoch. None for :memory: databases. \n \n \n \n db.is_mutable - boolean \n \n Is this database mutable, and allowed to accept writes? \n \n \n \n db.is_memory - boolean \n \n Is this database an in-memory database? \n \n \n \n await db.attached_databases() - list of named tuples \n \n Returns a list of additional databases that have been connected to this database using the SQLite ATTACH command. Each named tuple has fields seq , name and file . \n \n \n \n await db.table_exists(table) - boolean \n \n Check if a table called table exists. \n \n \n \n await db.view_exists(view) - boolean \n \n Check if a view called view exists. \n \n \n \n await db.table_names() - list of strings \n \n List of names of tables in the database. \n \n \n \n await db.view_names() - list of strings \n \n List of names of views in the database. \n \n \n \n await db.table_columns(table) - list of strings \n \n Names of columns in a specific table. \n \n \n \n await db.table_column_details(table) - list of named tuples \n \n Full details of the columns in a specific table. Each column is represented by a Column named tuple with fields cid (integer representing the column position), name (string), type (string, e.g. REAL or VARCHAR(30) ), notnull (integer 1 or 0), default_value (string or None), is_pk (integer 1 or 0). \n \n \n \n await db.primary_keys(table) - list of strings \n \n Names of the columns that are part of the primary key for this table. \n \n \n \n await db.fts_table(table) - string or None \n \n The name of the FTS table associated with this table, if one exists. \n \n \n \n await db.label_column_for_table(table) - string or None \n \n The label column that is associated with this table - either automatically detected or using the \"label_column\" key from Metadata , see Specifying the label column for a table . \n \n \n \n await db.foreign_keys_for_table(table) - list of dictionaries \n \n Details of columns in this table which are foreign keys to other tables. A list of dictionaries where each dictionary is shaped like this: {\"column\": string, \"other_table\": string, \"other_column\": string} . \n \n \n \n await db.hidden_table_names() - list of strings \n \n List of tables which Datasette \"hides\" by default - usually these are tables associated with SQLite's full-text search feature, the SpatiaLite extension or tables hidden using the Hiding tables feature. \n \n \n \n await db.get_table_definition(table) - string \n \n Returns the SQL definition for the table - the CREATE TABLE statement and any associated CREATE INDEX statements. \n \n \n \n await db.get_view_definition(view) - string \n \n Returns the SQL definition of the named view. \n \n \n \n await db.get_all_foreign_keys() - dictionary \n \n Dictionary representing both incoming and outgoing foreign keys for this table. It has two keys, \"incoming\" and \"outgoing\" , each of which is a list of dictionaries with keys \"column\" , \"other_table\" and \"other_column\" . For example: \n {\n \"incoming\": [],\n \"outgoing\": [\n {\n \"other_table\": \"attraction_characteristic\",\n \"column\": \"characteristic_id\",\n \"other_column\": \"pk\",\n },\n {\n \"other_table\": \"roadside_attractions\",\n \"column\": \"attraction_id\",\n \"other_column\": \"pk\",\n }\n ]\n}", "sections_fts": 437, "rank": null} {"rowid": 273, "title": "CSRF protection", "content": "Datasette uses asgi-csrf to guard against CSRF attacks on form POST submissions. Users receive a ds_csrftoken cookie which is compared against the csrftoken form field (or x-csrftoken HTTP header) for every incoming request. \n If your plugin implements a
anywhere you will need to include that token. You can do so with the following template snippet: \n \n If you are rendering templates using the await .render_template(template, context=None, request=None) method the csrftoken() helper will only work if you provide the request= argument to that method. If you forget to do this you will see the following error: \n form-urlencoded POST field did not match cookie \n You can selectively disable CSRF protection using the skip_csrf(datasette, scope) hook.", "sections_fts": 437, "rank": null} {"rowid": 274, "title": "Datasette's internal database", "content": "Datasette maintains an \"internal\" SQLite database used for configuration, caching, and storage. Plugins can store configuration, settings, and other data inside this database. By default, Datasette will use a temporary in-memory SQLite database as the internal database, which is created at startup and destroyed at shutdown. Users of Datasette can optionally pass in a --internal flag to specify the path to a SQLite database to use as the internal database, which will persist internal data across Datasette instances. \n Datasette maintains tables called catalog_databases , catalog_tables , catalog_columns , catalog_indexes , catalog_foreign_keys with details of the attached databases and their schemas. These tables should not be considered a stable API - they may change between Datasette releases. \n The internal database is not exposed in the Datasette application by default, which means private data can safely be stored without worry of accidentally leaking information through the default Datasette interface and API. However, other plugins do have full read and write access to the internal database. \n Plugins can access this database by calling internal_db = datasette.get_internal_database() and then executing queries using the Database API . \n Plugin authors are asked to practice good etiquette when using the internal database, as all plugins use the same database to store data. For example: \n \n \n Use a unique prefix when creating tables, indices, and triggers in the internal database. If your plugin is called datasette-xyz , then prefix names with datasette_xyz_* . \n \n \n Avoid long-running write statements that may stall or block other plugins that are trying to write at the same time. \n \n \n Use temporary tables or shared in-memory attached databases when possible. \n \n \n Avoid implementing features that could expose private data stored in the internal database by other plugins.", "sections_fts": 437, "rank": null} {"rowid": 275, "title": "The datasette.utils module", "content": "The datasette.utils module contains various utility functions used by Datasette. As a general rule you should consider anything in this module to be unstable - functions and classes here could change without warning or be removed entirely between Datasette releases, without being mentioned in the release notes. \n The exception to this rule is anything that is documented here. If you find a need for an undocumented utility function in your own work, consider opening an issue requesting that the function you are using be upgraded to documented and supported status.", "sections_fts": 437, "rank": null} {"rowid": 276, "title": "parse_metadata(content)", "content": "This function accepts a string containing either JSON or YAML, expected to be of the format described in Metadata . It returns a nested Python dictionary representing the parsed data from that string. \n If the metadata cannot be parsed as either JSON or YAML the function will raise a utils.BadMetadataError exception. \n \n \n datasette.utils. parse_metadata content : str dict \n \n Detects if content is JSON or YAML and parses it appropriately.", "sections_fts": 437, "rank": null} {"rowid": 277, "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.", "sections_fts": 437, "rank": null} {"rowid": 278, "title": "derive_named_parameters(db, sql)", "content": "Derive the list of named parameters referenced in a SQL query, using an explain query executed against the provided database. \n \n \n async datasette.utils. derive_named_parameters db : Database sql : str List [ str ] \n \n Given a SQL statement, return a list of named parameters that are used in the statement \n e.g. for select * from foo where id=:id this would return [\"id\"]", "sections_fts": 437, "rank": null} {"rowid": 279, "title": "Tilde encoding", "content": "Datasette uses a custom encoding scheme in some places, called tilde encoding . This is primarily used for table names and row primary keys, to avoid any confusion between / characters in those values and the Datasette URLs that reference them. \n Tilde encoding uses the same algorithm as URL percent-encoding , but with the ~ tilde character used in place of % . \n Any character other than ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz0123456789_- will be replaced by the numeric equivalent preceded by a tilde. For example: \n \n \n / becomes ~2F \n \n \n . becomes ~2E \n \n \n % becomes ~25 \n \n \n ~ becomes ~7E \n \n \n Space becomes + \n \n \n polls/2022.primary becomes polls~2F2022~2Eprimary \n \n \n Note that the space character is a special case: it will be replaced with a + symbol. \n \n \n \n datasette.utils. tilde_encode s : str str \n \n Returns tilde-encoded string - for example /foo/bar -> ~2Ffoo~2Fbar \n \n \n \n \n \n datasette.utils. tilde_decode s : str str \n \n Decodes a tilde-encoded string, so ~2Ffoo~2Fbar -> /foo/bar", "sections_fts": 437, "rank": null} {"rowid": 280, "title": "datasette.tracer", "content": "Running Datasette with --setting trace_debug 1 enables trace debug output, which can then be viewed by adding ?_trace=1 to the query string for any page. \n You can see an example of this at the bottom of latest.datasette.io/fixtures/facetable?_trace=1 . The JSON output shows full details of every SQL query that was executed to generate the page. \n The datasette-pretty-traces plugin can be installed to provide a more readable display of this information. You can see a demo of that here . \n You can add your own custom traces to the JSON output using the trace() context manager. This takes a string that identifies the type of trace being recorded, and records any keyword arguments as additional JSON keys on the resulting trace object. \n The start and end time, duration and a traceback of where the trace was executed will be automatically attached to the JSON object. \n This example uses trace to record the start, end and duration of any HTTP GET requests made using the function: \n from datasette.tracer import trace\nimport httpx\n\n\nasync def fetch_url(url):\n with trace(\"fetch-url\", url=url):\n async with httpx.AsyncClient() as client:\n return await client.get(url)", "sections_fts": 437, "rank": null} {"rowid": 281, "title": "Tracing child tasks", "content": "If your code uses a mechanism such as asyncio.gather() to execute code in additional tasks you may find that some of the traces are missing from the display. \n You can use the trace_child_tasks() context manager to ensure these child tasks are correctly handled. \n from datasette import tracer\n\nwith tracer.trace_child_tasks():\n results = await asyncio.gather(\n # ... async tasks here\n ) \n This example uses the register_routes() plugin hook to add a page at /parallel-queries which executes two SQL queries in parallel using asyncio.gather() and returns their results. \n from datasette import hookimpl\nfrom datasette import tracer\n\n\n@hookimpl\ndef register_routes():\n async def parallel_queries(datasette):\n db = datasette.get_database()\n with tracer.trace_child_tasks():\n one, two = await asyncio.gather(\n db.execute(\"select 1\"),\n db.execute(\"select 2\"),\n )\n return Response.json(\n {\n \"one\": one.single_value(),\n \"two\": two.single_value(),\n }\n )\n\n return [\n (r\"/parallel-queries$\", parallel_queries),\n ] \n Note that running parallel SQL queries in this way has been known to cause problems in the past , so treat this example with caution. \n Adding ?_trace=1 will show that the trace covers both of those child tasks.", "sections_fts": 437, "rank": null} {"rowid": 282, "title": "Import shortcuts", "content": "The following commonly used symbols can be imported directly from the datasette module: \n from datasette import Response\nfrom datasette import Forbidden\nfrom datasette import NotFound\nfrom datasette import hookimpl\nfrom datasette import actor_matches_allow", "sections_fts": 437, "rank": null} {"rowid": 283, "title": "Plugins", "content": "Datasette's plugin system allows additional features to be implemented as Python\n code (or front-end JavaScript) which can be wrapped up in a separate Python\n package. The underlying mechanism uses pluggy . \n See the Datasette plugins directory for a list of existing plugins, or take a look at the\n datasette-plugin topic on GitHub. \n Things you can do with plugins include: \n \n \n Add visualizations to Datasette, for example\n datasette-cluster-map and\n datasette-vega . \n \n \n Make new custom SQL functions available for use within Datasette, for example\n datasette-haversine and\n datasette-jellyfish . \n \n \n Define custom output formats with custom extensions, for example datasette-atom and\n datasette-ics . \n \n \n Add template functions that can be called within your Jinja custom templates,\n for example datasette-render-markdown . \n \n \n Customize how database values are rendered in the Datasette interface, for example\n datasette-render-binary and\n datasette-pretty-json . \n \n \n Customize how Datasette's authentication and permissions systems work, for example datasette-auth-passwords and\n datasette-permissions-sql .", "sections_fts": 437, "rank": null} {"rowid": 284, "title": "Installing plugins", "content": "If a plugin has been packaged for distribution using setuptools you can use the plugin by installing it alongside Datasette in the same virtual environment or Docker container. \n You can install plugins using the datasette install command: \n datasette install datasette-vega \n You can uninstall plugins with datasette uninstall : \n datasette uninstall datasette-vega \n You can upgrade plugins with datasette install --upgrade or datasette install -U : \n datasette install -U datasette-vega \n This command can also be used to upgrade Datasette itself to the latest released version: \n datasette install -U datasette \n You can install multiple plugins at once by listing them as lines in a requirements.txt file like this: \n datasette-vega\ndatasette-cluster-map \n Then pass that file to datasette install -r : \n datasette install -r requirements.txt \n The install and uninstall commands are thin wrappers around pip install and pip uninstall , which ensure that they run pip in the same virtual environment as Datasette itself.", "sections_fts": 437, "rank": null} {"rowid": 285, "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/", "sections_fts": 437, "rank": null} {"rowid": 286, "title": "Deploying plugins using datasette publish", "content": "The datasette publish and datasette package commands both take an optional --install argument. You can use this one or more times to tell Datasette to pip install specific plugins as part of the process: \n datasette publish cloudrun mydb.db --install=datasette-vega \n You can use the name of a package on PyPI or any of the other valid arguments to pip install such as a URL to a .zip file: \n datasette publish cloudrun mydb.db \\\n --install=https://url-to-my-package.zip", "sections_fts": 437, "rank": null} {"rowid": 287, "title": "Controlling which plugins are loaded", "content": "Datasette defaults to loading every plugin that is installed in the same virtual environment as Datasette itself. \n You can set the DATASETTE_LOAD_PLUGINS environment variable to a comma-separated list of plugin names to load a controlled subset of plugins instead. \n For example, to load just the datasette-vega and datasette-cluster-map plugins, set DATASETTE_LOAD_PLUGINS to datasette-vega,datasette-cluster-map : \n export DATASETTE_LOAD_PLUGINS='datasette-vega,datasette-cluster-map'\ndatasette mydb.db \n Or: \n DATASETTE_LOAD_PLUGINS='datasette-vega,datasette-cluster-map' \\\n datasette mydb.db \n To disable the loading of all additional plugins, set DATASETTE_LOAD_PLUGINS to an empty string: \n export DATASETTE_LOAD_PLUGINS=''\ndatasette mydb.db \n A quick way to test this setting is to use it with the datasette plugins command: \n DATASETTE_LOAD_PLUGINS='datasette-vega' datasette plugins \n This should output the following: \n [\n {\n \"name\": \"datasette-vega\",\n \"static\": true,\n \"templates\": false,\n \"version\": \"0.6.2\",\n \"hooks\": [\n \"extra_css_urls\",\n \"extra_js_urls\"\n ]\n }\n]", "sections_fts": 437, "rank": null} {"rowid": 288, "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 Which outputs: \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\")\ncog.outl(\".. code-block:: json\\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 \"actor_from_request\",\n \"permission_allowed\",\n \"register_permissions\",\n \"skip_csrf\"\n ]\n },\n {\n \"name\": \"datasette.events\",\n \"static\": false,\n \"templates\": false,\n \"version\": null,\n \"hooks\": [\n \"register_events\"\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. \n Add --requirements to output a list of installed plugins that can then be installed in another Datasette instance using datasette install -r requirements.txt : \n datasette plugins --requirements \n The output will look something like this: \n datasette-codespaces==0.1.1\ndatasette-graphql==2.2\ndatasette-json-html==1.0.1\ndatasette-pretty-json==0.2.2\ndatasette-x-forwarded-host==0.1 \n To write that to a requirements.txt file, run this: \n datasette plugins --requirements > requirements.txt", "sections_fts": 437, "rank": null} {"rowid": 289, "title": "Plugin configuration", "content": "Plugins can have their own configuration, embedded in a configuration file . Configuration options for plugins live within a \"plugins\" key in that file, which can be included at the root, database or table level. \n Here is an example of some plugin configuration for a specific table: \n [[[cog\nfrom metadata_doc import config_example\nconfig_example(cog, {\n \"databases\": {\n \"sf-trees\": {\n \"tables\": {\n \"Street_Tree_List\": {\n \"plugins\": {\n \"datasette-cluster-map\": {\n \"latitude_column\": \"lat\",\n \"longitude_column\": \"lng\"\n }\n }\n }\n }\n }\n }\n}) \n ]]] \n [[[end]]] \n This tells the datasette-cluster-map column which latitude and longitude columns should be used for a table called Street_Tree_List inside a database file called sf-trees.db .", "sections_fts": 437, "rank": null} {"rowid": 290, "title": "Secret configuration values", "content": "Some plugins may need configuration that should stay secret - API keys for example. There are two ways in which you can store secret configuration values. \n As environment variables . If your secret lives in an environment variable that is available to the Datasette process, you can indicate that the configuration value should be read from that environment variable like so: \n [[[cog\nconfig_example(cog, {\n \"plugins\": {\n \"datasette-auth-github\": {\n \"client_secret\": {\n \"$env\": \"GITHUB_CLIENT_SECRET\"\n }\n }\n }\n}) \n ]]] \n [[[end]]] \n As values in separate files . Your secrets can also live in files on disk. To specify a secret should be read from a file, provide the full file path like this: \n [[[cog\nconfig_example(cog, {\n \"plugins\": {\n \"datasette-auth-github\": {\n \"client_secret\": {\n \"$file\": \"/secrets/client-secret\"\n }\n }\n }\n}) \n ]]] \n [[[end]]] \n If you are publishing your data using the datasette publish family of commands, you can use the --plugin-secret option to set these secrets at publish time. For example, using Heroku you might run the following command: \n datasette publish heroku my_database.db \\\n --name my-heroku-app-demo \\\n --install=datasette-auth-github \\\n --plugin-secret datasette-auth-github client_id your_client_id \\\n --plugin-secret datasette-auth-github client_secret your_client_secret \n This will set the necessary environment variables and add the following to the deployed metadata.yaml : \n [[[cog\nconfig_example(cog, {\n \"plugins\": {\n \"datasette-auth-github\": {\n \"client_id\": {\n \"$env\": \"DATASETTE_AUTH_GITHUB_CLIENT_ID\"\n },\n \"client_secret\": {\n \"$env\": \"DATASETTE_AUTH_GITHUB_CLIENT_SECRET\"\n }\n }\n }\n}) \n ]]] \n [[[end]]]", "sections_fts": 437, "rank": null} {"rowid": 291, "title": "The Datasette Ecosystem", "content": "Datasette sits at the center of a growing ecosystem of open source tools aimed at making it as easy as possible to gather, analyze and publish interesting data. \n These tools are divided into two main groups: tools for building SQLite databases (for use with Datasette) and plugins that extend Datasette's functionality. \n The Datasette project website includes a directory of plugins and a directory of tools: \n \n \n Plugins directory on datasette.io \n \n \n Tools directory on datasette.io", "sections_fts": 437, "rank": null} {"rowid": 292, "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.", "sections_fts": 437, "rank": null} {"rowid": 293, "title": "Dogsheep", "content": "Dogsheep is a collection of tools for personal analytics using SQLite and Datasette. The project provides tools like github-to-sqlite and twitter-to-sqlite that can import data from different sources in order to create a personal data warehouse. Personal Data Warehouses: Reclaiming Your Data is a talk that explains Dogsheep and demonstrates it in action.", "sections_fts": 437, "rank": null} {"rowid": 294, "title": "Changelog", "content": "", "sections_fts": 437, "rank": null} {"rowid": 295, "title": "1.0a13 (2024-03-12)", "content": "Each of the key concepts in Datasette now has an actions menu , which plugins can use to add additional functionality targeting that entity. \n \n \n Plugin hook: view_actions() for actions that can be applied to a SQL view. ( #2297 ) \n \n \n Plugin hook: homepage_actions() for actions that apply to the instance homepage. ( #2298 ) \n \n \n Plugin hook: row_actions() for actions that apply to the row page. ( #2299 ) \n \n \n Action menu items for all of the *_actions() plugin hooks can now return an optional \"description\" key, which will be displayed in the menu below the action label. ( #2294 ) \n \n \n Plugin hooks documentation page is now organized with additional headings. ( #2300 ) \n \n \n Improved the display of action buttons on pages that also display metadata. ( #2286 ) \n \n \n The header and footer of the page now uses a subtle gradient effect, and options in the navigation menu are better visually defined. ( #2302 ) \n \n \n Table names that start with an underscore now default to hidden. ( #2104 ) \n \n \n pragma_table_list has been added to the allow-list of SQLite pragma functions supported by Datasette. select * from pragma_table_list() is no longer blocked. ( #2104 )", "sections_fts": 437, "rank": null} {"rowid": 296, "title": "1.0a12 (2024-02-29)", "content": "New query_actions() plugin hook, similar to table_actions() and database_actions() . Can be used to add a menu of actions to the canned query or arbitrary SQL query page. ( #2283 ) \n \n \n New design for the button that opens the query, table and database actions menu. ( #2281 ) \n \n \n \"does not contain\" table filter for finding rows that do not contain a string. ( #2287 ) \n \n \n Fixed a bug in the makeColumnActions(columnDetails) JavaScript plugin mechanism where the column action menu was not fully reset in between each interaction. ( #2289 )", "sections_fts": 437, "rank": null} {"rowid": 297, "title": "1.0a11 (2024-02-19)", "content": "The \"replace\": true argument to the /db/table/-/insert API now requires the actor to have the update-row permission. ( #2279 ) \n \n \n Fixed some UI bugs in the interactive permissions debugging tool. ( #2278 ) \n \n \n The column action menu now aligns better with the cog icon, and positions itself taking into account the width of the browser window. ( #2263 )", "sections_fts": 437, "rank": null} {"rowid": 298, "title": "1.0a10 (2024-02-17)", "content": "The only changes in this alpha correspond to the way Datasette handles database transactions. ( #2277 ) \n \n \n The database.execute_write_fn() method has a new transaction=True parameter. This defaults to True which means all functions executed using this method are now automatically wrapped in a transaction - previously the functions needed to roll transaction handling on their own, and many did not. \n \n \n Pass transaction=False to execute_write_fn() if you want to manually handle transactions in your function. \n \n \n Several internal Datasette features, including parts of the JSON write API , had been failing to wrap their operations in a transaction. This has been fixed by the new transaction=True default.", "sections_fts": 437, "rank": null} {"rowid": 299, "title": "1.0a9 (2024-02-16)", "content": "This alpha release adds basic alter table support to the Datasette Write API and fixes a permissions bug relating to the /upsert API endpoint.", "sections_fts": 437, "rank": null} {"rowid": 300, "title": "Alter table support for create, insert, upsert and update", "content": "The JSON write API can now be used to apply simple alter table schema changes, provided the acting actor has the new alter-table permission. ( #2101 ) \n The only alter operation supported so far is adding new columns to an existing table. \n \n \n The /db/-/create API now adds new columns during large operations to create a table based on incoming example \"rows\" , in the case where one of the later rows includes columns that were not present in the earlier batches. This requires the create-table but not the alter-table permission. \n \n \n When /db/-/create is called with rows in a situation where the table may have been already created, an \"alter\": true key can be included to indicate that any missing columns from the new rows should be added to the table. This requires the alter-table permission. \n \n \n /db/table/-/insert and /db/table/-/upsert and /db/table/row-pks/-/update all now also accept \"alter\": true , depending on the alter-table permission. \n \n \n Operations that alter a table now fire the new alter-table event .", "sections_fts": 437, "rank": null}