# repos

node_id: MDEwOlJlcG9zaXRvcnkzMDg5MzAxMTg=
name: datasette-edit-templates
full_name: simonw/datasette-edit-templates
html_url: https://github.com/simonw/datasette-edit-templates
description: Plugin allowing Datasette templates to be edited within Datasette
created_at: 2020-10-31T16:58:29Z
updated_at: 2025-11-10T19:31:10Z
pushed_at: 2025-11-10T19:31:57Z
language: Python
topics: []
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": false}
pull_request_creation_policy: all
readme:
  # datasette-edit-templates

  [![PyPI](https://img.shields.io/pypi/v/datasette-edit-templates.svg)](https://pypi.org/project/datasette-edit-templates/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-edit-templates?include_prereleases&label=changelog)](https://github.com/simonw/datasette-edit-templates/releases)
  [![Tests](https://github.com/simonw/datasette-edit-templates/workflows/Test/badge.svg)](https://github.com/simonw/datasette-edit-templates/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-edit-templates/blob/main/LICENSE)

  Plugin allowing Datasette templates to be edited within Datasette.

  ## Installation

  Install this plugin in the same environment as Datasette.
  ```bash
  datasette install datasette-edit-templates
  ```
  ## Usage

  On startup. a `_templates_` table will be created in the database you are running Datasette against.

  Use the app menu to navigate to the `/-/edit-templates` page, and edit templates there.

  Changes should become visible instantly, and will be persisted to your database.

  The interface is only available to users with the `edit-templates` permission.

  The `root` user is granted this permission by default. You can sign in as the root user using `datasette mydb.db --root`.

  ## Configuration

  To put the `_templates_` table in a specific database, set the `datasette-edit-templates: database` plugin configuration option:

  ```json
  {
      "plugins": {
          "datasette-edit-templates": {
              "database": "some_database"
          }
      }
  }
  ```
  On Datasette [1.0a5](https://docs.datasette.io/en/latest/changelog.html#a5-2023-08-29) or higher you can use the [internal database](https://docs.datasette.io/en/latest/internals.html#internals-internal) with `"internal_db: true":`

  ```json
  {
      "plugins": {
          "datasette-edit-templates": {
              "internal_db": true
          }
      }
  }
  ```

  By default the [prepare_jinja2_environment()](https://docs.datasette.io/en/stable/plugin_hooks.html#prepare-jinja2-environment-env-datasette) hook will be used to load the custom templates.

  You can disable this behavior using the `skip_prepare_jinja2_environment` plugin configuration option:

  ```json
  {
      "plugins": {
          "datasette-edit-templates": {
              "skip_prepare_jinja2_environment": true
          }
      }
  }
  ```
  Set this option if you want to further customize how the templates are loaded using another plugin.

  The menu item used to access this plugin is labeled "Edit templates" by default. You can customize this using the `menu_label` plugin configuration option:

  ```json
  {
      "plugins": {
          "datasette-edit-templates": {
              "menu_label": "Custom templates"
          }
      }
  }
  ```
  Set that to `null` to hide the menu option entirely.

  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:
  ```bash
  cd datasette-edit-templates
  python3 -mvenv venv
  source venv/bin/activate
  ```
  Or if you are using `pipenv`:
  ```bash
  pipenv shell
  ```
  Now install the dependencies and tests:
  ```bash
  pip install -e '.[test]'
  ```
  To run the tests:
  ```bash
  pytest
  ```

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-edit-templates</h1><a id="user-content-datasette-edit-templates" class="anchor" aria-label="Permalink: datasette-edit-templates" href="#datasette-edit-templates"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-edit-templates/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-edit-templates.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/af838dfacdf936640e67a04dbb772a45d25ed88acd533eac94af192387f32d2a/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d656469742d74656d706c617465732e737667"></a>
  <a href="https://github.com/simonw/datasette-edit-templates/releases"><img src="https://img.shields.io/github/v/release/simonw/datasette-edit-templates?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/13d34693875fd9063b20c8ecec3c782a430b2c09443b3cc6f96a74abc5105300/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f6461746173657474652d656469742d74656d706c617465733f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/datasette-edit-templates/actions?query=workflow%3ATest"><img src="https://github.com/simonw/datasette-edit-templates/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/datasette-edit-templates/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Plugin allowing Datasette templates to be edited within Datasette.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="datasette install datasette-edit-templates"><pre>datasette install datasette-edit-templates</pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">On startup. a <code>_templates_</code> table will be created in the database you are running Datasette against.</p>
  <p dir="auto">Use the app menu to navigate to the <code>/-/edit-templates</code> page, and edit templates there.</p>
  <p dir="auto">Changes should become visible instantly, and will be persisted to your database.</p>
  <p dir="auto">The interface is only available to users with the <code>edit-templates</code> permission.</p>
  <p dir="auto">The <code>root</code> user is granted this permission by default. You can sign in as the root user using <code>datasette mydb.db --root</code>.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Configuration</h2><a id="user-content-configuration" class="anchor" aria-label="Permalink: Configuration" href="#configuration"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To put the <code>_templates_</code> table in a specific database, set the <code>datasette-edit-templates: database</code> plugin configuration option:</p>
  <div class="highlight highlight-source-json notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="{
      "plugins": {
          "datasette-edit-templates": {
              "database": "some_database"
          }
      }
  }"><pre>{
      <span class="pl-ent">"plugins"</span>: {
          <span class="pl-ent">"datasette-edit-templates"</span>: {
              <span class="pl-ent">"database"</span>: <span class="pl-s"><span class="pl-pds">"</span>some_database<span class="pl-pds">"</span></span>
          }
      }
  }</pre></div>
  <p dir="auto">On Datasette <a href="https://docs.datasette.io/en/latest/changelog.html#a5-2023-08-29" rel="nofollow">1.0a5</a> or higher you can use the <a href="https://docs.datasette.io/en/latest/internals.html#internals-internal" rel="nofollow">internal database</a> with <code>"internal_db: true":</code></p>
  <div class="highlight highlight-source-json notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="{
      "plugins": {
          "datasette-edit-templates": {
              "internal_db": true
          }
      }
  }"><pre>{
      <span class="pl-ent">"plugins"</span>: {
          <span class="pl-ent">"datasette-edit-templates"</span>: {
              <span class="pl-ent">"internal_db"</span>: <span class="pl-c1">true</span>
          }
      }
  }</pre></div>
  <p dir="auto">By default the <a href="https://docs.datasette.io/en/stable/plugin_hooks.html#prepare-jinja2-environment-env-datasette" rel="nofollow">prepare_jinja2_environment()</a> hook will be used to load the custom templates.</p>
  <p dir="auto">You can disable this behavior using the <code>skip_prepare_jinja2_environment</code> plugin configuration option:</p>
  <div class="highlight highlight-source-json notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="{
      "plugins": {
          "datasette-edit-templates": {
              "skip_prepare_jinja2_environment": true
          }
      }
  }"><pre>{
      <span class="pl-ent">"plugins"</span>: {
          <span class="pl-ent">"datasette-edit-templates"</span>: {
              <span class="pl-ent">"skip_prepare_jinja2_environment"</span>: <span class="pl-c1">true</span>
          }
      }
  }</pre></div>
  <p dir="auto">Set this option if you want to further customize how the templates are loaded using another plugin.</p>
  <p dir="auto">The menu item used to access this plugin is labeled "Edit templates" by default. You can customize this using the <code>menu_label</code> plugin configuration option:</p>
  <div class="highlight highlight-source-json notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="{
      "plugins": {
          "datasette-edit-templates": {
              "menu_label": "Custom templates"
          }
      }
  }"><pre>{
      <span class="pl-ent">"plugins"</span>: {
          <span class="pl-ent">"datasette-edit-templates"</span>: {
              <span class="pl-ent">"menu_label"</span>: <span class="pl-s"><span class="pl-pds">"</span>Custom templates<span class="pl-pds">"</span></span>
          }
      }
  }</pre></div>
  <p dir="auto">Set that to <code>null</code> to hide the menu option entirely.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="cd datasette-edit-templates
  python3 -mvenv venv
  source venv/bin/activate"><pre><span class="pl-c1">cd</span> datasette-edit-templates
  python3 -mvenv venv
  <span class="pl-c1">source</span> venv/bin/activate</pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="pipenv shell"><pre>pipenv shell</pre></div>
  <p dir="auto">Now install the dependencies and tests:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre>pip install -e <span class="pl-s"><span class="pl-pds">'</span>.[test]<span class="pl-pds">'</span></span></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="pytest"><pre>pytest</pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzMTI5MzQwMDE=
name: datasette-indieauth
full_name: simonw/datasette-indieauth
html_url: https://github.com/simonw/datasette-indieauth
description: Datasette authentication using IndieAuth and RelMeAuth
created_at: 2020-11-15T01:18:21Z
updated_at: 2022-10-25T01:00:43Z
pushed_at: 2022-11-18T06:48:02Z
language: Python
topics: ["datasette", "datasette-io", "datasette-plugin", "indieauth"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # datasette-indieauth

  [![PyPI](https://img.shields.io/pypi/v/datasette-indieauth.svg)](https://pypi.org/project/datasette-indieauth/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-indieauth?include_prereleases&label=changelog)](https://github.com/simonw/datasette-indieauth/releases)
  [![codecov](https://codecov.io/gh/simonw/datasette-indieauth/branch/main/graph/badge.svg)](https://codecov.io/gh/simonw/datasette-indieauth)
  [![Tests](https://github.com/simonw/datasette-indieauth/workflows/Test/badge.svg)](https://github.com/simonw/datasette-indieauth/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-indieauth/blob/main/LICENSE)

  Datasette authentication using [IndieAuth](https://indieauth.net/).

  ## Demo

  You can try out the latest version of this plugin at [datasette-indieauth-demo.datasette.io](https://datasette-indieauth-demo.datasette.io/-/indieauth)

  ## Installation

  Install this plugin in the same environment as Datasette.

      $ datasette install datasette-indieauth

  ## Usage

  Ensure you have a website with a domain that supports IndieAuth or RelMeAuth. The easiest way to do that is to add the following HTML to your homepage, linking to your personal GitHub profile:

  ```html
  <link href="https://github.com/simonw" rel="me">
  <link rel="authorization_endpoint" href="https://indieauth.com/auth">
  ```
  Your GitHub profile needs to link back to your website, to prove that your GitHub account should be a valid identifier for that page.

  Now visit `/-/indieauth` on your Datasette instance to begin the sign-in progress.

  ## Actor

  When a user signs in using IndieAuth they will be recieve a signed `ds_actor` cookie identifying them as an [actor](https://docs.datasette.io/en/stable/authentication.html#actors) that looks like this:

  ```json
  {
      "me": "https://simonwillison.net/",
      "display": "simonwillison.net"
  }
  ```

  If the IndieAuth server returned additional `"profile"` fields those will be merged into the actor. You can visit `/-/actor` on your Datasette instance to see the full actor you are currently signed in as.

  ## Restricting access with the restrict_access plugin configuration

  You can use [Datasette's permissions system](https://docs.datasette.io/en/stable/authentication.html#permissions) to control permissions of authenticated users - by default, an authenticated user will be able to perform the same actions as an unauthenticated user.

  As a shortcut if you want to lock down access to your instance entirely to just specific users, you can use the `restrict_access` plugin configuration option like this:

  ```json
  {
      "plugins": {
          "datasette-indieauth": {
              "restrict_access": "https://simonwillison.net/"
          }
      }
  }
  ```

  This can be a string or a list of user identifiers. It can also be a space separated list, which means you can use it with the [datasette publish](https://docs.datasette.io/en/stable/publish.html#datasette-publish) `--plugin-secret` configuration option to set permissions as part of a deployment, like this:
  ```
  datasette publish vercel mydb.db --project my-secret-db \
      --install datasette-indieauth \
      --plugin-secret datasette-indieauth restrict_access https://simonwillison.net/
  ```
  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:

      cd datasette-indieauth
      python3 -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and tests:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-indieauth</h1><a id="user-content-datasette-indieauth" class="anchor" aria-label="Permalink: datasette-indieauth" href="#datasette-indieauth"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-indieauth/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-indieauth.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/bb02be890161a54f4a5f262aef3283a3feca15973b0c4f5c73a7986f65c5cc13/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d696e646965617574682e737667"></a>
  <a href="https://github.com/simonw/datasette-indieauth/releases"><img src="https://img.shields.io/github/v/release/simonw/datasette-indieauth?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/aa831f2581273aab9471a54d7244a2badd9f866123ecc5d96cf23c8a76bbacc3/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f6461746173657474652d696e646965617574683f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://codecov.io/gh/simonw/datasette-indieauth" rel="nofollow"><img src="https://codecov.io/gh/simonw/datasette-indieauth/branch/main/graph/badge.svg" alt="codecov" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/52ea57bd6f9d317bf0c82ecf964e02cf6f637ed42222d02497ab7aadc048e672/68747470733a2f2f636f6465636f762e696f2f67682f73696d6f6e772f6461746173657474652d696e646965617574682f6272616e63682f6d61696e2f67726170682f62616467652e737667"></a>
  <a href="https://github.com/simonw/datasette-indieauth/actions?query=workflow%3ATest"><img src="https://github.com/simonw/datasette-indieauth/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/datasette-indieauth/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Datasette authentication using <a href="https://indieauth.net/" rel="nofollow">IndieAuth</a>.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Demo</h2><a id="user-content-demo" class="anchor" aria-label="Permalink: Demo" href="#demo"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">You can try out the latest version of this plugin at <a href="https://datasette-indieauth-demo.datasette.io/-/indieauth" rel="nofollow">datasette-indieauth-demo.datasette.io</a></p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ datasette install datasette-indieauth"><pre class="notranslate"><code>$ datasette install datasette-indieauth
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Ensure you have a website with a domain that supports IndieAuth or RelMeAuth. The easiest way to do that is to add the following HTML to your homepage, linking to your personal GitHub profile:</p>
  <div class="highlight highlight-text-html-basic notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="<link href="https://github.com/simonw" rel="me">
  <link rel="authorization_endpoint" href="https://indieauth.com/auth">"><pre><span class="pl-kos"><</span><span class="pl-ent">link</span> <span class="pl-c1">href</span>="<span class="pl-s">https://github.com/simonw</span>" <span class="pl-c1">rel</span>="<span class="pl-s">me</span>"<span class="pl-kos">></span>
  <span class="pl-kos"><</span><span class="pl-ent">link</span> <span class="pl-c1">rel</span>="<span class="pl-s">authorization_endpoint</span>" <span class="pl-c1">href</span>="<span class="pl-s">https://indieauth.com/auth</span>"<span class="pl-kos">></span></pre></div>
  <p dir="auto">Your GitHub profile needs to link back to your website, to prove that your GitHub account should be a valid identifier for that page.</p>
  <p dir="auto">Now visit <code>/-/indieauth</code> on your Datasette instance to begin the sign-in progress.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Actor</h2><a id="user-content-actor" class="anchor" aria-label="Permalink: Actor" href="#actor"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">When a user signs in using IndieAuth they will be recieve a signed <code>ds_actor</code> cookie identifying them as an <a href="https://docs.datasette.io/en/stable/authentication.html#actors" rel="nofollow">actor</a> that looks like this:</p>
  <div class="highlight highlight-source-json notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="{
      "me": "https://simonwillison.net/",
      "display": "simonwillison.net"
  }"><pre>{
      <span class="pl-ent">"me"</span>: <span class="pl-s"><span class="pl-pds">"</span>https://simonwillison.net/<span class="pl-pds">"</span></span>,
      <span class="pl-ent">"display"</span>: <span class="pl-s"><span class="pl-pds">"</span>simonwillison.net<span class="pl-pds">"</span></span>
  }</pre></div>
  <p dir="auto">If the IndieAuth server returned additional <code>"profile"</code> fields those will be merged into the actor. You can visit <code>/-/actor</code> on your Datasette instance to see the full actor you are currently signed in as.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Restricting access with the restrict_access plugin configuration</h2><a id="user-content-restricting-access-with-the-restrict_access-plugin-configuration" class="anchor" aria-label="Permalink: Restricting access with the restrict_access plugin configuration" href="#restricting-access-with-the-restrict_access-plugin-configuration"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">You can use <a href="https://docs.datasette.io/en/stable/authentication.html#permissions" rel="nofollow">Datasette's permissions system</a> to control permissions of authenticated users - by default, an authenticated user will be able to perform the same actions as an unauthenticated user.</p>
  <p dir="auto">As a shortcut if you want to lock down access to your instance entirely to just specific users, you can use the <code>restrict_access</code> plugin configuration option like this:</p>
  <div class="highlight highlight-source-json notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="{
      "plugins": {
          "datasette-indieauth": {
              "restrict_access": "https://simonwillison.net/"
          }
      }
  }"><pre>{
      <span class="pl-ent">"plugins"</span>: {
          <span class="pl-ent">"datasette-indieauth"</span>: {
              <span class="pl-ent">"restrict_access"</span>: <span class="pl-s"><span class="pl-pds">"</span>https://simonwillison.net/<span class="pl-pds">"</span></span>
          }
      }
  }</pre></div>
  <p dir="auto">This can be a string or a list of user identifiers. It can also be a space separated list, which means you can use it with the <a href="https://docs.datasette.io/en/stable/publish.html#datasette-publish" rel="nofollow">datasette publish</a> <code>--plugin-secret</code> configuration option to set permissions as part of a deployment, like this:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="datasette publish vercel mydb.db --project my-secret-db \
      --install datasette-indieauth \
      --plugin-secret datasette-indieauth restrict_access https://simonwillison.net/"><pre class="notranslate"><code>datasette publish vercel mydb.db --project my-secret-db \
      --install datasette-indieauth \
      --plugin-secret datasette-indieauth restrict_access https://simonwillison.net/
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd datasette-indieauth
  python3 -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd datasette-indieauth
  python3 -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzMTU3OTYwMTU=
name: datasette-ripgrep
full_name: simonw/datasette-ripgrep
html_url: https://github.com/simonw/datasette-ripgrep
description: Web interface for searching your code using ripgrep, built as a Datasette plugin
created_at: 2020-11-25T01:26:36Z
updated_at: 2025-12-25T20:08:33Z
pushed_at: 2025-11-11T15:26:54Z
homepage: https://ripgrep.datasette.io
language: Python
license: apache-2.0
topics: ["codesearch", "datasette", "datasette-io", "datasette-plugin", "ripgrep"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": false}
pull_request_creation_policy: all
readme:
  # datasette-ripgrep

  [![PyPI](https://img.shields.io/pypi/v/datasette-ripgrep.svg)](https://pypi.org/project/datasette-ripgrep/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-ripgrep?include_prereleases&label=changelog)](https://github.com/simonw/datasette-ripgrep/releases)
  [![Tests](https://github.com/simonw/datasette-ripgrep/workflows/Test/badge.svg)](https://github.com/simonw/datasette-ripgrep/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-ripgrep/blob/main/LICENSE)

  Web interface for searching your code using [ripgrep](https://github.com/BurntSushi/ripgrep), built as a [Datasette](https://datasette.io/) plugin

  For background on this project see [datasette-ripgrep: deploy a regular expression search engine for your source code](https://simonwillison.net/2020/Nov/28/datasette-ripgrep/).

  ## Demo

  Try this plugin out at https://ripgrep.datasette.io/-/ripgrep - where you can run regular expression searches across the source code of Datasette and all of the `datasette-*` plugins belonging to the [simonw GitHub user](https://github.com/simonw).

  Some example searches:

  - [with.\*AsyncClient](https://ripgrep.datasette.io/-/ripgrep?pattern=with.*AsyncClient) - regular expression search for `with.*AsyncClient`
  - [.plugin_config, literal=on](https://ripgrep.datasette.io/-/ripgrep?pattern=.plugin_config\(&literal=on) - a non-regular expression search for `.plugin_config(`
  - [with.\*AsyncClient glob=datasette/\*\*](https://ripgrep.datasette.io/-/ripgrep?pattern=with.*AsyncClient&glob=datasette%2F%2A%2A) - search for that pattern only within the `datasette/` top folder
  - ["sqlite-utils\[">\] glob=setup.py](https://ripgrep.datasette.io/-/ripgrep?pattern=%22sqlite-utils%5B%22%3E%5D&glob=setup.py) - a regular expression search for packages that depend on either `sqlite-utils` or `sqlite-utils>=some-version`
  - [test glob=!\*.html](https://ripgrep.datasette.io/-/ripgrep?pattern=test&glob=%21*.html) - search for the string `test` but exclude results in HTML files

  ## Installation

  Install this plugin in the same environment as Datasette.

      $ datasette install datasette-ripgrep

  The `rg` executable needs to be [installed](https://github.com/BurntSushi/ripgrep/blob/master/README.md#installation) such that it can be run by this tool.

  ## Usage

  This plugin requires configuration: it needs to a `path` setting so that it knows where to run searches.

  Create a `metadata.json` file that looks like this:

  ```json
  {
      "plugins": {
          "datasette-ripgrep": {
              "path": "/path/to/your/files"
          }
      }
  }
  ```

  Now run Datasette using `datasette -m metadata.json`. The plugin will add an interface at `/-/ripgrep` for running searches.

  ## Plugin configuration

  The `"path"` configuration is required. Optional extra configuration options are:

  - `time_limit` - floating point number. The `rg` process will be terminated if it takes longer than this limit. The default is one second, `1.0`.
  - `max_lines` - integer. The `rg` process will be terminated if it returns more than this number of lines. The default is `2000`.

  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:

      cd datasette-ripgrep
      python3 -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and tests:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-ripgrep</h1><a id="user-content-datasette-ripgrep" class="anchor" aria-label="Permalink: datasette-ripgrep" href="#datasette-ripgrep"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-ripgrep/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-ripgrep.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/2a2c6b27cb1240a30e6852be7cf29435acbd42de45f93e1c5e2473b926292e2b/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d726970677265702e737667"></a>
  <a href="https://github.com/simonw/datasette-ripgrep/releases"><img src="https://img.shields.io/github/v/release/simonw/datasette-ripgrep?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/b794f54e9a9cb3ef9a15b8278fb20b6e5dae386be4704ee3b79ca66af9139ab8/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f6461746173657474652d726970677265703f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/datasette-ripgrep/actions?query=workflow%3ATest"><img src="https://github.com/simonw/datasette-ripgrep/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/datasette-ripgrep/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Web interface for searching your code using <a href="https://github.com/BurntSushi/ripgrep">ripgrep</a>, built as a <a href="https://datasette.io/" rel="nofollow">Datasette</a> plugin</p>
  <p dir="auto">For background on this project see <a href="https://simonwillison.net/2020/Nov/28/datasette-ripgrep/" rel="nofollow">datasette-ripgrep: deploy a regular expression search engine for your source code</a>.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Demo</h2><a id="user-content-demo" class="anchor" aria-label="Permalink: Demo" href="#demo"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Try this plugin out at <a href="https://ripgrep.datasette.io/-/ripgrep" rel="nofollow">https://ripgrep.datasette.io/-/ripgrep</a> - where you can run regular expression searches across the source code of Datasette and all of the <code>datasette-*</code> plugins belonging to the <a href="https://github.com/simonw">simonw GitHub user</a>.</p>
  <p dir="auto">Some example searches:</p>
  <ul dir="auto">
  <li><a href="https://ripgrep.datasette.io/-/ripgrep?pattern=with.*AsyncClient" rel="nofollow">with.*AsyncClient</a> - regular expression search for <code>with.*AsyncClient</code></li>
  <li><a href="https://ripgrep.datasette.io/-/ripgrep?pattern=.plugin_config(&literal=on" rel="nofollow">.plugin_config, literal=on</a> - a non-regular expression search for <code>.plugin_config(</code></li>
  <li><a href="https://ripgrep.datasette.io/-/ripgrep?pattern=with.*AsyncClient&glob=datasette%2F%2A%2A" rel="nofollow">with.*AsyncClient glob=datasette/**</a> - search for that pattern only within the <code>datasette/</code> top folder</li>
  <li><a href="https://ripgrep.datasette.io/-/ripgrep?pattern=%22sqlite-utils%5B%22%3E%5D&glob=setup.py" rel="nofollow">"sqlite-utils[">] glob=setup.py</a> - a regular expression search for packages that depend on either <code>sqlite-utils</code> or <code>sqlite-utils>=some-version</code></li>
  <li><a href="https://ripgrep.datasette.io/-/ripgrep?pattern=test&glob=%21*.html" rel="nofollow">test glob=!*.html</a> - search for the string <code>test</code> but exclude results in HTML files</li>
  </ul>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ datasette install datasette-ripgrep"><pre class="notranslate"><code>$ datasette install datasette-ripgrep
  </code></pre></div>
  <p dir="auto">The <code>rg</code> executable needs to be <a href="https://github.com/BurntSushi/ripgrep/blob/master/README.md#installation">installed</a> such that it can be run by this tool.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">This plugin requires configuration: it needs to a <code>path</code> setting so that it knows where to run searches.</p>
  <p dir="auto">Create a <code>metadata.json</code> file that looks like this:</p>
  <div class="highlight highlight-source-json notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="{
      "plugins": {
          "datasette-ripgrep": {
              "path": "/path/to/your/files"
          }
      }
  }"><pre>{
      <span class="pl-ent">"plugins"</span>: {
          <span class="pl-ent">"datasette-ripgrep"</span>: {
              <span class="pl-ent">"path"</span>: <span class="pl-s"><span class="pl-pds">"</span>/path/to/your/files<span class="pl-pds">"</span></span>
          }
      }
  }</pre></div>
  <p dir="auto">Now run Datasette using <code>datasette -m metadata.json</code>. The plugin will add an interface at <code>/-/ripgrep</code> for running searches.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Plugin configuration</h2><a id="user-content-plugin-configuration" class="anchor" aria-label="Permalink: Plugin configuration" href="#plugin-configuration"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">The <code>"path"</code> configuration is required. Optional extra configuration options are:</p>
  <ul dir="auto">
  <li><code>time_limit</code> - floating point number. The <code>rg</code> process will be terminated if it takes longer than this limit. The default is one second, <code>1.0</code>.</li>
  <li><code>max_lines</code> - integer. The <code>rg</code> process will be terminated if it returns more than this number of lines. The default is <code>2000</code>.</li>
  </ul>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd datasette-ripgrep
  python3 -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd datasette-ripgrep
  python3 -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzMjcwODcyMDc=
name: datasette-css-properties
full_name: simonw/datasette-css-properties
html_url: https://github.com/simonw/datasette-css-properties
description: Experimental Datasette output plugin using CSS properties
created_at: 2021-01-05T18:38:07Z
updated_at: 2026-06-02T21:08:40Z
pushed_at: 2026-06-02T21:08:37Z
language: Python
topics: ["datasette-io", "datasette-plugin"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": false}
pull_request_creation_policy: all
readme:
  # datasette-css-properties

  [![PyPI](https://img.shields.io/pypi/v/datasette-css-properties.svg)](https://pypi.org/project/datasette-css-properties/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-css-properties?include_prereleases&label=changelog)](https://github.com/simonw/datasette-css-properties/releases)
  [![Tests](https://github.com/simonw/datasette-css-properties/workflows/Test/badge.svg)](https://github.com/simonw/datasette-css-properties/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-css-properties/blob/main/LICENSE)

  Extremely experimental Datasette output plugin using CSS properties, inspired by [Custom Properties as State](https://css-tricks.com/custom-properties-as-state/) by Chris Coyier.

  More about this project: [APIs from CSS without JavaScript: the datasette-css-properties plugin](https://simonwillison.net/2021/Jan/7/css-apis-no-javascript/)

  ## Installation

  Install this plugin in the same environment as Datasette.

      $ datasette install datasette-css-properties

  ## Usage

  Once installed, this plugin adds a `.css` output format to every query result. This will return the first row in the query as a valid CSS file, defining each column as a custom property:

  Example: https://latest-with-plugins.datasette.io/fixtures/roadside_attractions.css produces:

  ```css
  :root {
    --pk: '1';
    --name: 'The Mystery Spot';
    --address: '465 Mystery Spot Road, Santa Cruz, CA 95065';
    --latitude: '37.0167';
    --longitude: '-122.0024';
  }
  ```

  If you link this stylesheet to your page you can then do things like this;

  ```html
  <link rel="stylesheet" href="https://latest-with-plugins.datasette.io/fixtures/roadside_attractions.css">
  <style>
  .attraction-name:after { content: var(--name); }
  </style>
  <p class="attraction-name">Attraction name: </p>
  ```

  Values will be quoted as CSS strings by default. If you want to return a "raw" value without the quotes - for example to set a CSS property that is numeric or a color, you can specify that column name using the `?_raw=column-name` parameter. This can be passed multiple times.

  Consider [this example query](https://latest-with-plugins.datasette.io/github?sql=select%0D%0A++%27%23%27+||+substr(sha%2C+0%2C+6)+as+[custom-bg]%0D%0Afrom%0D%0A++commits%0D%0Aorder+by%0D%0A++author_date+desc%0D%0Alimit%0D%0A++1%3B):

  ```sql
  select
    '#' || substr(sha, 0, 6) as [custom-bg]
  from
    commits
  order by
    author_date desc
  limit
    1;
  ```

  This returns the first 6 characters of the most recently authored commit with a `#` prefix. The `.css` [output rendered version](https://latest-with-plugins.datasette.io/github.css?sql=select%0D%0A++%27%23%27+||+substr(sha%2C+0%2C+6)+as+[custom-bg]%0D%0Afrom%0D%0A++commits%0D%0Aorder+by%0D%0A++author_date+desc%0D%0Alimit%0D%0A++1%3B) looks like this:

  ```css
  :root {
    --custom-bg: '#97fb1';
  }
  ```

  Adding `?_raw=custom-bg` to the URL produces [this instead](https://latest-with-plugins.datasette.io/github.css?sql=select%0D%0A++%27%23%27+||+substr(sha%2C+0%2C+6)+as+[custom-bg]%0D%0Afrom%0D%0A++commits%0D%0Aorder+by%0D%0A++author_date+desc%0D%0Alimit%0D%0A++1%3B&_raw=custom-bg):

  ```css
  :root {
    --custom-bg: #97fb1;
  }
  ```

  This can then be used as a color value like so:

  ```css
  h1 {
      background-color: var(--custom-bg);
  }
  ```

  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:

      cd datasette-css-properties
      python3 -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and tests:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-css-properties</h1><a id="user-content-datasette-css-properties" class="anchor" aria-label="Permalink: datasette-css-properties" href="#datasette-css-properties"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-css-properties/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-css-properties.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/b42a03ffea99ea61a949ccacf8264e43b6e8fa1a6de7cbaa98c1278ea40778af/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d6373732d70726f706572746965732e737667"></a>
  <a href="https://github.com/simonw/datasette-css-properties/releases"><img src="https://img.shields.io/github/v/release/simonw/datasette-css-properties?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/794fbfc0b73c14c55aa8b1c4ad837d32436b724edbf97c6ba21103f256c8f19b/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f6461746173657474652d6373732d70726f706572746965733f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/datasette-css-properties/actions?query=workflow%3ATest"><img src="https://github.com/simonw/datasette-css-properties/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/datasette-css-properties/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Extremely experimental Datasette output plugin using CSS properties, inspired by <a href="https://css-tricks.com/custom-properties-as-state/" rel="nofollow">Custom Properties as State</a> by Chris Coyier.</p>
  <p dir="auto">More about this project: <a href="https://simonwillison.net/2021/Jan/7/css-apis-no-javascript/" rel="nofollow">APIs from CSS without JavaScript: the datasette-css-properties plugin</a></p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ datasette install datasette-css-properties"><pre class="notranslate"><code>$ datasette install datasette-css-properties
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Once installed, this plugin adds a <code>.css</code> output format to every query result. This will return the first row in the query as a valid CSS file, defining each column as a custom property:</p>
  <p dir="auto">Example: <a href="https://latest-with-plugins.datasette.io/fixtures/roadside_attractions.css" rel="nofollow">https://latest-with-plugins.datasette.io/fixtures/roadside_attractions.css</a> produces:</p>
  <div class="highlight highlight-source-css notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content=":root {
    --pk: '1';
    --name: 'The Mystery Spot';
    --address: '465 Mystery Spot Road, Santa Cruz, CA 95065';
    --latitude: '37.0167';
    --longitude: '-122.0024';
  }"><pre><span class="pl-kos">:</span><span class="pl-c1">root</span> {
    <span class="pl-s1">--pk</span><span class="pl-kos">:</span> <span class="pl-s">'1'</span>;
    <span class="pl-s1">--name</span><span class="pl-kos">:</span> <span class="pl-s">'The Mystery Spot'</span>;
    <span class="pl-s1">--address</span><span class="pl-kos">:</span> <span class="pl-s">'465 Mystery Spot Road, Santa Cruz, CA 95065'</span>;
    <span class="pl-s1">--latitude</span><span class="pl-kos">:</span> <span class="pl-s">'37.0167'</span>;
    <span class="pl-s1">--longitude</span><span class="pl-kos">:</span> <span class="pl-s">'-122.0024'</span>;
  }</pre></div>
  <p dir="auto">If you link this stylesheet to your page you can then do things like this;</p>
  <div class="highlight highlight-text-html-basic notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="<link rel="stylesheet" href="https://latest-with-plugins.datasette.io/fixtures/roadside_attractions.css">
  <style>
  .attraction-name:after { content: var(--name); }
  </style>
  <p class="attraction-name">Attraction name: </p>"><pre><span class="pl-kos"><</span><span class="pl-ent">link</span> <span class="pl-c1">rel</span>="<span class="pl-s">stylesheet</span>" <span class="pl-c1">href</span>="<span class="pl-s">https://latest-with-plugins.datasette.io/fixtures/roadside_attractions.css</span>"<span class="pl-kos">></span>
  <span class="pl-kos"><</span><span class="pl-ent">style</span><span class="pl-kos">></span>
  .<span class="pl-c1">attraction-name</span><span class="pl-kos">:</span><span class="pl-c1">after</span> { <span class="pl-c1">content</span><span class="pl-kos">:</span> <span class="pl-en">var</span>(<span class="pl-s1">--name</span>); }
  <span class="pl-kos"></</span><span class="pl-ent">style</span><span class="pl-kos">></span>
  <span class="pl-kos"><</span><span class="pl-ent">p</span> <span class="pl-c1">class</span>="<span class="pl-s">attraction-name</span>"<span class="pl-kos">></span>Attraction name: <span class="pl-kos"></</span><span class="pl-ent">p</span><span class="pl-kos">></span></pre></div>
  <p dir="auto">Values will be quoted as CSS strings by default. If you want to return a "raw" value without the quotes - for example to set a CSS property that is numeric or a color, you can specify that column name using the <code>?_raw=column-name</code> parameter. This can be passed multiple times.</p>
  <p dir="auto">Consider <a href="https://latest-with-plugins.datasette.io/github?sql=select%0D%0A++%27%23%27+%7C%7C+substr(sha%2C+0%2C+6)+as+%5Bcustom-bg%5D%0D%0Afrom%0D%0A++commits%0D%0Aorder+by%0D%0A++author_date+desc%0D%0Alimit%0D%0A++1%3B" rel="nofollow">this example query</a>:</p>
  <div class="highlight highlight-source-sql notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="select
    '#' || substr(sha, 0, 6) as [custom-bg]
  from
    commits
  order by
    author_date desc
  limit
    1;"><pre><span class="pl-k">select</span>
    <span class="pl-s"><span class="pl-pds">'</span>#<span class="pl-pds">'</span></span> <span class="pl-k">||</span> substr(sha, <span class="pl-c1">0</span>, <span class="pl-c1">6</span>) <span class="pl-k">as</span> [custom<span class="pl-k">-</span>bg]
  <span class="pl-k">from</span>
    commits
  <span class="pl-k">order by</span>
    author_date <span class="pl-k">desc</span>
  <span class="pl-k">limit</span>
    <span class="pl-c1">1</span>;</pre></div>
  <p dir="auto">This returns the first 6 characters of the most recently authored commit with a <code>#</code> prefix. The <code>.css</code> <a href="https://latest-with-plugins.datasette.io/github.css?sql=select%0D%0A++%27%23%27+%7C%7C+substr(sha%2C+0%2C+6)+as+%5Bcustom-bg%5D%0D%0Afrom%0D%0A++commits%0D%0Aorder+by%0D%0A++author_date+desc%0D%0Alimit%0D%0A++1%3B" rel="nofollow">output rendered version</a> looks like this:</p>
  <div class="highlight highlight-source-css notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content=":root {
    --custom-bg: '#97fb1';
  }"><pre><span class="pl-kos">:</span><span class="pl-c1">root</span> {
    <span class="pl-s1">--custom-bg</span><span class="pl-kos">:</span> <span class="pl-s">'#97fb1'</span>;
  }</pre></div>
  <p dir="auto">Adding <code>?_raw=custom-bg</code> to the URL produces <a href="https://latest-with-plugins.datasette.io/github.css?sql=select%0D%0A++%27%23%27+%7C%7C+substr(sha%2C+0%2C+6)+as+%5Bcustom-bg%5D%0D%0Afrom%0D%0A++commits%0D%0Aorder+by%0D%0A++author_date+desc%0D%0Alimit%0D%0A++1%3B&_raw=custom-bg" rel="nofollow">this instead</a>:</p>
  <div class="highlight highlight-source-css notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content=":root {
    --custom-bg: #97fb1;
  }"><pre><span class="pl-kos">:</span><span class="pl-c1">root</span> {
    <span class="pl-s1">--custom-bg</span><span class="pl-kos">:</span> <span class="pl-pds"><span class="pl-kos">#</span>97fb1</span>;
  }</pre></div>
  <p dir="auto">This can then be used as a color value like so:</p>
  <div class="highlight highlight-source-css notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="h1 {
      background-color: var(--custom-bg);
  }"><pre><span class="pl-ent">h1</span> {
      <span class="pl-c1">background-color</span><span class="pl-kos">:</span> <span class="pl-en">var</span>(<span class="pl-s1">--custom-bg</span>);
  }</pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd datasette-css-properties
  python3 -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd datasette-css-properties
  python3 -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzMjcyMzYxMTk=
name: datasette-export-notebook
full_name: simonw/datasette-export-notebook
html_url: https://github.com/simonw/datasette-export-notebook
description: Datasette plugin providing instructions for exporting data to Jupyter or Observable
created_at: 2021-01-06T07:37:00Z
updated_at: 2024-06-19T21:59:43Z
pushed_at: 2023-09-15T18:59:37Z
language: Python
topics: ["datasette-io", "datasette-plugin"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # datasette-export-notebook

  [![PyPI](https://img.shields.io/pypi/v/datasette-export-notebook.svg)](https://pypi.org/project/datasette-export-notebook/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-export-notebook?include_prereleases&label=changelog)](https://github.com/simonw/datasette-export-notebook/releases)
  [![Tests](https://github.com/simonw/datasette-export-notebook/workflows/Test/badge.svg)](https://github.com/simonw/datasette-export-notebook/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-export-notebook/blob/main/LICENSE)

  Datasette plugin providing instructions for exporting data to a [Jupyter](https://jupyter.org/) or [Observable](https://observablehq.com/) notebook.

  ## Installation

  Install this plugin in the same environment as Datasette.

      $ datasette install datasette-export-notebook

  ## Usage

  Once installed, the plugin will add a `.Notebook` export option to every table and query. Clicking on this link will show instructions for exporting the data to Jupyter or Observable.

  ## Demo

  You can see this plugin in action on the [latest-with-plugins.datasette.io](https://latest-with-plugins.datasette.io/) Datasette instance - for example on [/github/commits.Notebook](https://latest-with-plugins.datasette.io/github/commits.Notebook).

  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:

      cd datasette-export-notebook
      python3 -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and tests:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-export-notebook</h1><a id="user-content-datasette-export-notebook" class="anchor" aria-label="Permalink: datasette-export-notebook" href="#datasette-export-notebook"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-export-notebook/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-export-notebook.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/dddec091a2edfd224721ecc8f13a469e9a9eea4949bc9e458c7bfff32aa102d1/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d6578706f72742d6e6f7465626f6f6b2e737667"></a>
  <a href="https://github.com/simonw/datasette-export-notebook/releases"><img src="https://img.shields.io/github/v/release/simonw/datasette-export-notebook?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/2830a3bbf954e0d6d89d1da47a769d36e5f349f7faab2496606ad72cdd803bb5/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f6461746173657474652d6578706f72742d6e6f7465626f6f6b3f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/datasette-export-notebook/actions?query=workflow%3ATest"><img src="https://github.com/simonw/datasette-export-notebook/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/datasette-export-notebook/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Datasette plugin providing instructions for exporting data to a <a href="https://jupyter.org/" rel="nofollow">Jupyter</a> or <a href="https://observablehq.com/" rel="nofollow">Observable</a> notebook.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ datasette install datasette-export-notebook"><pre class="notranslate"><code>$ datasette install datasette-export-notebook
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Once installed, the plugin will add a <code>.Notebook</code> export option to every table and query. Clicking on this link will show instructions for exporting the data to Jupyter or Observable.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Demo</h2><a id="user-content-demo" class="anchor" aria-label="Permalink: Demo" href="#demo"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">You can see this plugin in action on the <a href="https://latest-with-plugins.datasette.io/" rel="nofollow">latest-with-plugins.datasette.io</a> Datasette instance - for example on <a href="https://latest-with-plugins.datasette.io/github/commits.Notebook" rel="nofollow">/github/commits.Notebook</a>.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd datasette-export-notebook
  python3 -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd datasette-export-notebook
  python3 -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzMzAzMDQ2Mjg=
name: datasette-query-history
full_name: bretwalker/datasette-query-history
html_url: https://github.com/bretwalker/datasette-query-history
created_at: 2021-01-17T03:13:34Z
updated_at: 2022-09-23T17:52:01Z
pushed_at: 2021-01-17T23:11:37Z
language: JavaScript
license: apache-2.0
topics: []
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # datasette-query-history

  [![PyPI](https://img.shields.io/pypi/v/datasette-query-history.svg)](https://pypi.org/project/datasette-query-history/)
  [![Changelog](https://img.shields.io/github/v/release/bretwalker/datasette-query-history?include_prereleases&label=changelog)](https://github.com/bretwalker/datasette-query-history/releases)
  [![Tests](https://github.com/bretwalker/datasette-query-history/workflows/Test/badge.svg)](https://github.com/bretwalker/datasette-query-history/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/bretwalker/datasette-query-history/blob/main/LICENSE)

  Datasette plugin that keeps a list of the queries you've run and lets you rerun them.

  ## Installation

  Install this plugin in the same environment as Datasette.

      $ datasette install datasette-query-history

  ## Usage

  Click the `Query History` button on the SQL editor page to see previous queries.  
  Click the ⬆︎ button to replace the current query with a previous query.  
  Click the `Clear Query History` button to clear the list previous queries.

  <img src="https://raw.githubusercontent.com/bretwalker/datasette-query-history/main/docs/datasette-query-history-example1.png" width="350px" alt="Screenshot of plugin">

  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:

      cd datasette-query-history
      python3 -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and tests:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-query-history</h1><a id="user-content-datasette-query-history" class="anchor" aria-label="Permalink: datasette-query-history" href="#datasette-query-history"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-query-history/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-query-history.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/9791a86450c46a4e8fd7e6a825d4905bfc83cbf3259799c023888c97b5bf3988/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d71756572792d686973746f72792e737667"></a>
  <a href="https://github.com/bretwalker/datasette-query-history/releases"><img src="https://img.shields.io/github/v/release/bretwalker/datasette-query-history?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/ffe1d3004f8876f5788830c151508d1f03e3c7066d9807b5ccb444c7a663f131/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f6272657477616c6b65722f6461746173657474652d71756572792d686973746f72793f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/bretwalker/datasette-query-history/actions?query=workflow%3ATest"><img src="https://github.com/bretwalker/datasette-query-history/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/bretwalker/datasette-query-history/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Datasette plugin that keeps a list of the queries you've run and lets you rerun them.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ datasette install datasette-query-history"><pre class="notranslate"><code>$ datasette install datasette-query-history
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Click the <code>Query History</code> button on the SQL editor page to see previous queries.<br>
  Click the ⬆︎ button to replace the current query with a previous query.<br>
  Click the <code>Clear Query History</code> button to clear the list previous queries.</p>
  <p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/bretwalker/datasette-query-history/main/docs/datasette-query-history-example1.png"><img src="https://raw.githubusercontent.com/bretwalker/datasette-query-history/main/docs/datasette-query-history-example1.png" width="350px" alt="Screenshot of plugin" style="max-width: 100%;"></a></p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd datasette-query-history
  python3 -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd datasette-query-history
  python3 -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzMzExNTE3MDg=
name: datasette-leaflet-freedraw
full_name: simonw/datasette-leaflet-freedraw
html_url: https://github.com/simonw/datasette-leaflet-freedraw
description: Draw polygons on maps in Datasette
created_at: 2021-01-20T00:55:03Z
updated_at: 2025-03-18T21:47:14Z
pushed_at: 2023-01-09T20:17:06Z
language: Python
topics: ["datasette", "datasette-io", "datasette-plugin", "leafletjs"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # datasette-leaflet-freedraw

  [![PyPI](https://img.shields.io/pypi/v/datasette-leaflet-freedraw.svg)](https://pypi.org/project/datasette-leaflet-freedraw/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-leaflet-freedraw?include_prereleases&label=changelog)](https://github.com/simonw/datasette-leaflet-freedraw/releases)
  [![Tests](https://github.com/simonw/datasette-leaflet-freedraw/workflows/Test/badge.svg)](https://github.com/simonw/datasette-leaflet-freedraw/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-leaflet-freedraw/blob/main/LICENSE)

  Draw polygons on maps in Datasette

  Project background: [Drawing shapes on a map to query a SpatiaLite database](https://simonwillison.net/2021/Jan/24/drawing-shapes-spatialite/).

  ## Installation

  Install this plugin in the same environment as Datasette.

      $ datasette install datasette-leaflet-freedraw

  ## Usage

  If a table has a SpatiaLite `geometry` column, the plugin will add a map interface to the table page allowing users to draw a shape on the map to find rows with a geometry that intersects that shape.

  The plugin can also work with arbitrary SQL queries. There it looks for input fields with a name of `freedraw` or that ends in `_freedraw` and replaces them with a map interface.

  The map interface uses the [FreeDraw](https://freedraw.herokuapp.com/) Leaflet plugin.

  ## Demo

  You can try out this plugin to run searches against the GreenInfo Network California Protected Areas Database. Here's [an example query](https://calands.datasettes.com/calands/draw_to_find_mini_parks?freedraw=%7B%22type%22%3A%22MultiPolygon%22%2C%22coordinates%22%3A%5B%5B%5B%5B-122.42202758789064%2C37.82280243352759%5D%2C%5B-122.39868164062501%2C37.823887203271454%5D%2C%5B-122.38220214843751%2C37.81846319511331%5D%2C%5B-122.35061645507814%2C37.77071473849611%5D%2C%5B-122.34924316406251%2C37.74465712069939%5D%2C%5B-122.37258911132814%2C37.703380457832374%5D%2C%5B-122.39044189453125%2C37.690340943717715%5D%2C%5B-122.41241455078126%2C37.680559803205135%5D%2C%5B-122.44262695312501%2C37.67295135774715%5D%2C%5B-122.47283935546876%2C37.67295135774715%5D%2C%5B-122.52502441406251%2C37.68382032669382%5D%2C%5B-122.53463745117189%2C37.6892542140253%5D%2C%5B-122.54699707031251%2C37.690340943717715%5D%2C%5B-122.55798339843751%2C37.72945260537781%5D%2C%5B-122.54287719726564%2C37.77831314799672%5D%2C%5B-122.49893188476564%2C37.81303878836991%5D%2C%5B-122.46185302734376%2C37.82822612280363%5D%2C%5B-122.42889404296876%2C37.82822612280363%5D%2C%5B-122.42202758789064%2C37.82280243352759%5D%5D%5D%5D%7D) showing mini parks in San Francisco:

  ```sql
  select
    AsGeoJSON(geometry), *
  from
    CPAD_2020a_SuperUnits
  where
    PARK_NAME like '%mini%' and
    Intersects(GeomFromGeoJSON(:freedraw), geometry) = 1
    and CPAD_2020a_SuperUnits.rowid in (
      select
        rowid
      from
        SpatialIndex
      where
        f_table_name = 'CPAD_2020a_SuperUnits'
        and search_frame = GeomFromGeoJSON(:freedraw)
    )
  ```

  ![Screenshot of the plugin in action](https://static.simonwillison.net/static/2021/datasette-leaflet-freedraw.png)

  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:

      cd datasette-leaflet-freedraw
      python3 -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and tests:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-leaflet-freedraw</h1><a id="user-content-datasette-leaflet-freedraw" class="anchor" aria-label="Permalink: datasette-leaflet-freedraw" href="#datasette-leaflet-freedraw"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-leaflet-freedraw/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-leaflet-freedraw.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/b9432a74185a9d91efebf077a6fe22382103de680ed8452428ca855d86f14f24/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d6c6561666c65742d66726565647261772e737667"></a>
  <a href="https://github.com/simonw/datasette-leaflet-freedraw/releases"><img src="https://img.shields.io/github/v/release/simonw/datasette-leaflet-freedraw?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/8fd3084f80ce32fd38a8ffd5630118d410d37ca05add2fe1b63626a41c7a0935/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f6461746173657474652d6c6561666c65742d66726565647261773f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/datasette-leaflet-freedraw/actions?query=workflow%3ATest"><img src="https://github.com/simonw/datasette-leaflet-freedraw/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/datasette-leaflet-freedraw/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Draw polygons on maps in Datasette</p>
  <p dir="auto">Project background: <a href="https://simonwillison.net/2021/Jan/24/drawing-shapes-spatialite/" rel="nofollow">Drawing shapes on a map to query a SpatiaLite database</a>.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ datasette install datasette-leaflet-freedraw"><pre class="notranslate"><code>$ datasette install datasette-leaflet-freedraw
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">If a table has a SpatiaLite <code>geometry</code> column, the plugin will add a map interface to the table page allowing users to draw a shape on the map to find rows with a geometry that intersects that shape.</p>
  <p dir="auto">The plugin can also work with arbitrary SQL queries. There it looks for input fields with a name of <code>freedraw</code> or that ends in <code>_freedraw</code> and replaces them with a map interface.</p>
  <p dir="auto">The map interface uses the <a href="https://freedraw.herokuapp.com/" rel="nofollow">FreeDraw</a> Leaflet plugin.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Demo</h2><a id="user-content-demo" class="anchor" aria-label="Permalink: Demo" href="#demo"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">You can try out this plugin to run searches against the GreenInfo Network California Protected Areas Database. Here's <a href="https://calands.datasettes.com/calands/draw_to_find_mini_parks?freedraw=%7B%22type%22%3A%22MultiPolygon%22%2C%22coordinates%22%3A%5B%5B%5B%5B-122.42202758789064%2C37.82280243352759%5D%2C%5B-122.39868164062501%2C37.823887203271454%5D%2C%5B-122.38220214843751%2C37.81846319511331%5D%2C%5B-122.35061645507814%2C37.77071473849611%5D%2C%5B-122.34924316406251%2C37.74465712069939%5D%2C%5B-122.37258911132814%2C37.703380457832374%5D%2C%5B-122.39044189453125%2C37.690340943717715%5D%2C%5B-122.41241455078126%2C37.680559803205135%5D%2C%5B-122.44262695312501%2C37.67295135774715%5D%2C%5B-122.47283935546876%2C37.67295135774715%5D%2C%5B-122.52502441406251%2C37.68382032669382%5D%2C%5B-122.53463745117189%2C37.6892542140253%5D%2C%5B-122.54699707031251%2C37.690340943717715%5D%2C%5B-122.55798339843751%2C37.72945260537781%5D%2C%5B-122.54287719726564%2C37.77831314799672%5D%2C%5B-122.49893188476564%2C37.81303878836991%5D%2C%5B-122.46185302734376%2C37.82822612280363%5D%2C%5B-122.42889404296876%2C37.82822612280363%5D%2C%5B-122.42202758789064%2C37.82280243352759%5D%5D%5D%5D%7D" rel="nofollow">an example query</a> showing mini parks in San Francisco:</p>
  <div class="highlight highlight-source-sql notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="select
    AsGeoJSON(geometry), *
  from
    CPAD_2020a_SuperUnits
  where
    PARK_NAME like '%mini%' and
    Intersects(GeomFromGeoJSON(:freedraw), geometry) = 1
    and CPAD_2020a_SuperUnits.rowid in (
      select
        rowid
      from
        SpatialIndex
      where
        f_table_name = 'CPAD_2020a_SuperUnits'
        and search_frame = GeomFromGeoJSON(:freedraw)
    )"><pre><span class="pl-k">select</span>
    AsGeoJSON(geometry), <span class="pl-k">*</span>
  <span class="pl-k">from</span>
    CPAD_2020a_SuperUnits
  <span class="pl-k">where</span>
    PARK_NAME <span class="pl-k">like</span> <span class="pl-s"><span class="pl-pds">'</span>%mini%<span class="pl-pds">'</span></span> <span class="pl-k">and</span>
    Intersects(GeomFromGeoJSON(:freedraw), geometry) <span class="pl-k">=</span> <span class="pl-c1">1</span>
    <span class="pl-k">and</span> <span class="pl-c1">CPAD_2020a_SuperUnits</span>.<span class="pl-c1">rowid</span> <span class="pl-k">in</span> (
      <span class="pl-k">select</span>
        rowid
      <span class="pl-k">from</span>
        SpatialIndex
      <span class="pl-k">where</span>
        f_table_name <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span>CPAD_2020a_SuperUnits<span class="pl-pds">'</span></span>
        <span class="pl-k">and</span> search_frame <span class="pl-k">=</span> GeomFromGeoJSON(:freedraw)
    )</pre></div>
  <p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/3753739ffb17e72045e61f631acab8707b10227015235e5cca3944c4a058eb64/68747470733a2f2f7374617469632e73696d6f6e77696c6c69736f6e2e6e65742f7374617469632f323032312f6461746173657474652d6c6561666c65742d66726565647261772e706e67"><img src="https://static.simonwillison.net/static/2021/datasette-leaflet-freedraw.png" alt="Screenshot of the plugin in action" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/3753739ffb17e72045e61f631acab8707b10227015235e5cca3944c4a058eb64/68747470733a2f2f7374617469632e73696d6f6e77696c6c69736f6e2e6e65742f7374617469632f323032312f6461746173657474652d6c6561666c65742d66726565647261772e706e67"></a></p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd datasette-leaflet-freedraw
  python3 -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd datasette-leaflet-freedraw
  python3 -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzMzE3MjA4MjQ=
name: datasette-leaflet
full_name: simonw/datasette-leaflet
html_url: https://github.com/simonw/datasette-leaflet
description: Datasette plugin adding the Leaflet JavaScript library
created_at: 2021-01-21T18:41:19Z
updated_at: 2025-04-15T13:07:02Z
pushed_at: 2024-02-17T23:58:16Z
language: JavaScript
topics: ["datasette", "datasette-io", "datasette-plugin"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # datasette-leaflet

  [![PyPI](https://img.shields.io/pypi/v/datasette-leaflet.svg)](https://pypi.org/project/datasette-leaflet/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-leaflet?include_prereleases&label=changelog)](https://github.com/simonw/datasette-leaflet/releases)
  [![Tests](https://github.com/simonw/datasette-leaflet/workflows/Test/badge.svg)](https://github.com/simonw/datasette-leaflet/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-leaflet/blob/main/LICENSE)

  Datasette plugin adding the [Leaflet](https://leafletjs.com/) JavaScript library.

  A growing number of Datasette plugins depend on the Leaflet JavaScript mapping library. They each have their own way of loading Leaflet, which could result in loading it multiple times (with multiple versions) if more than one plugin is installed.

  This library is intended to solve this problem, by providing a single plugin they can all depend on that loads Leaflet in a reusable way.

  Plugins that use this:

  - [datasette-leaflet-freedraw](https://datasette.io/plugins/datasette-leaflet-freedraw)
  - [datasette-leaflet-geojson](https://datasette.io/plugins/datasette-leaflet-geojson)
  - [datasette-cluster-map](https://datasette.io/plugins/datasette-cluster-map)

  ## Installation

  You can install this plugin like so:

      datasette install datasette-leaflet

  Usually this plugin will be a dependency of other plugins, so it should be installed automatically when you install them.

  ## Usage

  The plugin makes `leaflet.js` and `leaflet.css` available as static files. It provides two custom template variables with the URLs of those two files.

  - `{{ datasette_leaflet_url }}` is the URL to the JavaScript
  - `{{ datasette_leaflet_css_url }}` is the URL to the CSS

  These URLs are also made available as global JavaScript constants:

  - `datasette.leaflet.JAVASCRIPT_URL`
  - `datasette.leaflet.CSS_URL`

  The JavaScript is packaed as a [JavaScript module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). You can dynamically import the JavaScript from a custom template like this:

  ```html+jinja
  <script type="module">
  import('{{ datasette_leaflet_url }}')
    .then((leaflet) => {
      /* Use leaflet here */
    });
  </script>
  ```

  You can load the CSS like this:

  ```html+jinja
  <link rel="stylesheet" href="{{ datasette_leaflet_css_url }}">
  ```

  Or dynamically like this:

  ```html+jinja
  <script>
  let link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = '{{ datasette_leaflet_css_url }}';
  document.head.appendChild(link);
  </script>
  ```

  Here's a full example that loads the JavaScript and CSS and renders a map:

  ```html+jinja
  <script type="module">
  window.DATASETTE_CLUSTER_MAP_TILE_LAYER = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
  window.DATASETTE_CLUSTER_MAP_TILE_LAYER_OPTIONS = {"maxZoom": 19, "detectRetina": true, "attribution": "&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors"};
  let link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = '{{ datasette_leaflet_css_url }}';
  document.head.appendChild(link);
  import('{{ datasette_leaflet_url }}')
    .then((leaflet) => {
      let div = document.createElement('div');
      div.style.height = '400px';
      document.querySelector('.content').appendChild(div);
      let tiles = leaflet.tileLayer(
          window.DATASETTE_CLUSTER_MAP_TILE_LAYER,
          window.DATASETTE_CLUSTER_MAP_TILE_LAYER_OPTIONS
      );
      let map = leaflet.map(div, {
          center: leaflet.latLng(0, 0),
          zoom: 2,
          layers: [tiles]
      });
    });
  </script>
  ```

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-leaflet</h1><a id="user-content-datasette-leaflet" class="anchor" aria-label="Permalink: datasette-leaflet" href="#datasette-leaflet"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-leaflet/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-leaflet.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/43389f8553e59e588765d51ef86a818fdf2edab01db1f74addb2a343a9bf6fc1/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d6c6561666c65742e737667"></a>
  <a href="https://github.com/simonw/datasette-leaflet/releases"><img src="https://img.shields.io/github/v/release/simonw/datasette-leaflet?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c5a30425d5855cad1af824063608f092ac376ef897b6b0410cc2fc06579fd5e2/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f6461746173657474652d6c6561666c65743f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/datasette-leaflet/actions?query=workflow%3ATest"><img src="https://github.com/simonw/datasette-leaflet/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/datasette-leaflet/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Datasette plugin adding the <a href="https://leafletjs.com/" rel="nofollow">Leaflet</a> JavaScript library.</p>
  <p dir="auto">A growing number of Datasette plugins depend on the Leaflet JavaScript mapping library. They each have their own way of loading Leaflet, which could result in loading it multiple times (with multiple versions) if more than one plugin is installed.</p>
  <p dir="auto">This library is intended to solve this problem, by providing a single plugin they can all depend on that loads Leaflet in a reusable way.</p>
  <p dir="auto">Plugins that use this:</p>
  <ul dir="auto">
  <li><a href="https://datasette.io/plugins/datasette-leaflet-freedraw" rel="nofollow">datasette-leaflet-freedraw</a></li>
  <li><a href="https://datasette.io/plugins/datasette-leaflet-geojson" rel="nofollow">datasette-leaflet-geojson</a></li>
  <li><a href="https://datasette.io/plugins/datasette-cluster-map" rel="nofollow">datasette-cluster-map</a></li>
  </ul>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">You can install this plugin like so:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="datasette install datasette-leaflet"><pre class="notranslate"><code>datasette install datasette-leaflet
  </code></pre></div>
  <p dir="auto">Usually this plugin will be a dependency of other plugins, so it should be installed automatically when you install them.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">The plugin makes <code>leaflet.js</code> and <code>leaflet.css</code> available as static files. It provides two custom template variables with the URLs of those two files.</p>
  <ul dir="auto">
  <li><code>{{ datasette_leaflet_url }}</code> is the URL to the JavaScript</li>
  <li><code>{{ datasette_leaflet_css_url }}</code> is the URL to the CSS</li>
  </ul>
  <p dir="auto">These URLs are also made available as global JavaScript constants:</p>
  <ul dir="auto">
  <li><code>datasette.leaflet.JAVASCRIPT_URL</code></li>
  <li><code>datasette.leaflet.CSS_URL</code></li>
  </ul>
  <p dir="auto">The JavaScript is packaed as a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules" rel="nofollow">JavaScript module</a>. You can dynamically import the JavaScript from a custom template like this:</p>
  <div class="highlight highlight-text-html-django notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="<script type="module">
  import('{{ datasette_leaflet_url }}')
    .then((leaflet) => {
      /* Use leaflet here */
    });
  </script>"><pre><<span class="pl-ent">script</span> <span class="pl-e">type</span>=<span class="pl-s"><span class="pl-pds">"</span>module<span class="pl-pds">"</span></span>><span class="pl-s1"></span>
  <span class="pl-s1"><span class="pl-k">import</span>(<span class="pl-s"><span class="pl-pds">'</span>{{ datasette_leaflet_url }}<span class="pl-pds">'</span></span>)</span>
  <span class="pl-s1">  .<span class="pl-c1">then</span>((<span class="pl-smi">leaflet</span>) <span class="pl-k">=></span> {</span>
  <span class="pl-s1">    <span class="pl-c"><span class="pl-c">/*</span> Use leaflet here <span class="pl-c">*/</span></span></span>
  <span class="pl-s1">  });</span>
  <span class="pl-s1"></span></<span class="pl-ent">script</span>></pre></div>
  <p dir="auto">You can load the CSS like this:</p>
  <div class="highlight highlight-text-html-django notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="<link rel="stylesheet" href="{{ datasette_leaflet_css_url }}">"><pre><<span class="pl-ent">link</span> <span class="pl-e">rel</span>=<span class="pl-s"><span class="pl-pds">"</span>stylesheet<span class="pl-pds">"</span></span> <span class="pl-e">href</span>=<span class="pl-s"><span class="pl-pds">"</span>{{ datasette_leaflet_css_url }}<span class="pl-pds">"</span></span>></pre></div>
  <p dir="auto">Or dynamically like this:</p>
  <div class="highlight highlight-text-html-django notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="<script>
  let link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = '{{ datasette_leaflet_css_url }}';
  document.head.appendChild(link);
  </script>"><pre><<span class="pl-ent">script</span>><span class="pl-s1"></span>
  <span class="pl-s1"><span class="pl-k">let</span> link <span class="pl-k">=</span> <span class="pl-c1">document</span>.<span class="pl-c1">createElement</span>(<span class="pl-s"><span class="pl-pds">'</span>link<span class="pl-pds">'</span></span>);</span>
  <span class="pl-s1"><span class="pl-smi">link</span>.<span class="pl-c1">rel</span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span>stylesheet<span class="pl-pds">'</span></span>;</span>
  <span class="pl-s1"><span class="pl-smi">link</span>.<span class="pl-c1">href</span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span>{{ datasette_leaflet_css_url }}<span class="pl-pds">'</span></span>;</span>
  <span class="pl-s1"><span class="pl-c1">document</span>.<span class="pl-smi">head</span>.<span class="pl-c1">appendChild</span>(link);</span>
  <span class="pl-s1"></span></<span class="pl-ent">script</span>></pre></div>
  <p dir="auto">Here's a full example that loads the JavaScript and CSS and renders a map:</p>
  <div class="highlight highlight-text-html-django notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="<script type="module">
  window.DATASETTE_CLUSTER_MAP_TILE_LAYER = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
  window.DATASETTE_CLUSTER_MAP_TILE_LAYER_OPTIONS = {"maxZoom": 19, "detectRetina": true, "attribution": "&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors"};
  let link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = '{{ datasette_leaflet_css_url }}';
  document.head.appendChild(link);
  import('{{ datasette_leaflet_url }}')
    .then((leaflet) => {
      let div = document.createElement('div');
      div.style.height = '400px';
      document.querySelector('.content').appendChild(div);
      let tiles = leaflet.tileLayer(
          window.DATASETTE_CLUSTER_MAP_TILE_LAYER,
          window.DATASETTE_CLUSTER_MAP_TILE_LAYER_OPTIONS
      );
      let map = leaflet.map(div, {
          center: leaflet.latLng(0, 0),
          zoom: 2,
          layers: [tiles]
      });
    });
  </script>"><pre><<span class="pl-ent">script</span> <span class="pl-e">type</span>=<span class="pl-s"><span class="pl-pds">"</span>module<span class="pl-pds">"</span></span>><span class="pl-s1"></span>
  <span class="pl-s1"><span class="pl-c1">window</span>.<span class="pl-c1">DATASETTE_CLUSTER_MAP_TILE_LAYER</span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">"</span>https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png<span class="pl-pds">"</span></span>;</span>
  <span class="pl-s1"><span class="pl-c1">window</span>.<span class="pl-c1">DATASETTE_CLUSTER_MAP_TILE_LAYER_OPTIONS</span> <span class="pl-k">=</span> {<span class="pl-s"><span class="pl-pds">"</span>maxZoom<span class="pl-pds">"</span></span><span class="pl-k">:</span> <span class="pl-c1">19</span>, <span class="pl-s"><span class="pl-pds">"</span>detectRetina<span class="pl-pds">"</span></span><span class="pl-k">:</span> <span class="pl-c1">true</span>, <span class="pl-s"><span class="pl-pds">"</span>attribution<span class="pl-pds">"</span></span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>&copy; <a href=<span class="pl-cce">\"</span>https://www.openstreetmap.org/copyright<span class="pl-cce">\"</span>>OpenStreetMap</a> contributors<span class="pl-pds">"</span></span>};</span>
  <span class="pl-s1"><span class="pl-k">let</span> link <span class="pl-k">=</span> <span class="pl-c1">document</span>.<span class="pl-c1">createElement</span>(<span class="pl-s"><span class="pl-pds">'</span>link<span class="pl-pds">'</span></span>);</span>
  <span class="pl-s1"><span class="pl-smi">link</span>.<span class="pl-c1">rel</span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span>stylesheet<span class="pl-pds">'</span></span>;</span>
  <span class="pl-s1"><span class="pl-smi">link</span>.<span class="pl-c1">href</span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span>{{ datasette_leaflet_css_url }}<span class="pl-pds">'</span></span>;</span>
  <span class="pl-s1"><span class="pl-c1">document</span>.<span class="pl-smi">head</span>.<span class="pl-c1">appendChild</span>(link);</span>
  <span class="pl-s1"><span class="pl-k">import</span>(<span class="pl-s"><span class="pl-pds">'</span>{{ datasette_leaflet_url }}<span class="pl-pds">'</span></span>)</span>
  <span class="pl-s1">  .<span class="pl-c1">then</span>((<span class="pl-smi">leaflet</span>) <span class="pl-k">=></span> {</span>
  <span class="pl-s1">    <span class="pl-k">let</span> div <span class="pl-k">=</span> <span class="pl-c1">document</span>.<span class="pl-c1">createElement</span>(<span class="pl-s"><span class="pl-pds">'</span>div<span class="pl-pds">'</span></span>);</span>
  <span class="pl-s1">    <span class="pl-smi">div</span>.<span class="pl-c1">style</span>.<span class="pl-c1">height</span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span>400px<span class="pl-pds">'</span></span>;</span>
  <span class="pl-s1">    <span class="pl-c1">document</span>.<span class="pl-c1">querySelector</span>(<span class="pl-s"><span class="pl-pds">'</span>.content<span class="pl-pds">'</span></span>).<span class="pl-c1">appendChild</span>(div);</span>
  <span class="pl-s1">    <span class="pl-k">let</span> tiles <span class="pl-k">=</span> <span class="pl-smi">leaflet</span>.<span class="pl-en">tileLayer</span>(</span>
  <span class="pl-s1">        <span class="pl-c1">window</span>.<span class="pl-c1">DATASETTE_CLUSTER_MAP_TILE_LAYER</span>,</span>
  <span class="pl-s1">        <span class="pl-c1">window</span>.<span class="pl-c1">DATASETTE_CLUSTER_MAP_TILE_LAYER_OPTIONS</span></span>
  <span class="pl-s1">    );</span>
  <span class="pl-s1">    <span class="pl-k">let</span> map <span class="pl-k">=</span> <span class="pl-smi">leaflet</span>.<span class="pl-en">map</span>(div, {</span>
  <span class="pl-s1">        center<span class="pl-k">:</span> <span class="pl-smi">leaflet</span>.<span class="pl-en">latLng</span>(<span class="pl-c1">0</span>, <span class="pl-c1">0</span>),</span>
  <span class="pl-s1">        zoom<span class="pl-k">:</span> <span class="pl-c1">2</span>,</span>
  <span class="pl-s1">        layers<span class="pl-k">:</span> [tiles]</span>
  <span class="pl-s1">    });</span>
  <span class="pl-s1">  });</span>
  <span class="pl-s1"></span></<span class="pl-ent">script</span>></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzMzUxMzcxMDg=
name: datasette-basemap
full_name: simonw/datasette-basemap
html_url: https://github.com/simonw/datasette-basemap
description: A basemap for Datasette and datasette-leaflet
created_at: 2021-02-02T01:49:09Z
updated_at: 2025-04-16T01:08:24Z
pushed_at: 2024-03-11T16:44:43Z
homepage: https://datasette.io/plugins/datasette-basemap
language: Python
topics: ["datasette", "datasette-io", "datasette-plugin", "mbtiles", "openstreetmap"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # datasette-basemap

  [![PyPI](https://img.shields.io/pypi/v/datasette-basemap.svg)](https://pypi.org/project/datasette-basemap/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-basemap?include_prereleases&label=changelog)](https://github.com/simonw/datasette-basemap/releases)
  [![Test](https://github.com/simonw/datasette-basemap/actions/workflows/test.yml/badge.svg)](https://github.com/simonw/datasette-basemap/actions/workflows/test.yml)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-basemap/blob/main/LICENSE)

  A basemap for Datasette and datasette-leaflet

  ## Installation

  Install this plugin in the same environment as Datasette.

      $ datasette install datasette-basemap

  ## Usage

  This plugin will make a `basemap` database available containing OpenStreetMap tiles for zoom levels 0-6 in the [mbtiles](https://github.com/mapbox/mbtiles-spec) format. It is designed for use with the [datasette-tiles](https://datasette.io/plugins/datasette-tiles) tile server plugin.

  ## Demo

  You can preview this map at https://datasette-tiles-demo.datasette.io/-/tiles/basemap and browse the database directly at https://datasette-tiles-demo.datasette.io/basemap

  ## License

  The data bundled with this package is © OpenStreetMap contributors, licensed under the [Open Data Commons Open Database License](https://opendatacommons.org/licenses/odbl/). See [this page](https://www.openstreetmap.org/copyright) for more details.

  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:

      cd datasette-basemap
      python3 -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and tests:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-basemap</h1><a id="user-content-datasette-basemap" class="anchor" aria-label="Permalink: datasette-basemap" href="#datasette-basemap"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-basemap/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-basemap.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/e350e19d5cc4a57982b5729a8ee33de294aa0135e35c612b816465a8b67d786b/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d626173656d61702e737667"></a>
  <a href="https://github.com/simonw/datasette-basemap/releases"><img src="https://img.shields.io/github/v/release/simonw/datasette-basemap?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/10c9728494eacfee60995745b668bdf5498cd46285f107760d758a74d3ce5464/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f6461746173657474652d626173656d61703f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/datasette-basemap/actions/workflows/test.yml"><img src="https://github.com/simonw/datasette-basemap/actions/workflows/test.yml/badge.svg" alt="Test" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/datasette-basemap/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">A basemap for Datasette and datasette-leaflet</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ datasette install datasette-basemap"><pre class="notranslate"><code>$ datasette install datasette-basemap
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">This plugin will make a <code>basemap</code> database available containing OpenStreetMap tiles for zoom levels 0-6 in the <a href="https://github.com/mapbox/mbtiles-spec">mbtiles</a> format. It is designed for use with the <a href="https://datasette.io/plugins/datasette-tiles" rel="nofollow">datasette-tiles</a> tile server plugin.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Demo</h2><a id="user-content-demo" class="anchor" aria-label="Permalink: Demo" href="#demo"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">You can preview this map at <a href="https://datasette-tiles-demo.datasette.io/-/tiles/basemap" rel="nofollow">https://datasette-tiles-demo.datasette.io/-/tiles/basemap</a> and browse the database directly at <a href="https://datasette-tiles-demo.datasette.io/basemap" rel="nofollow">https://datasette-tiles-demo.datasette.io/basemap</a></p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">License</h2><a id="user-content-license" class="anchor" aria-label="Permalink: License" href="#license"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">The data bundled with this package is © OpenStreetMap contributors, licensed under the <a href="https://opendatacommons.org/licenses/odbl/" rel="nofollow">Open Data Commons Open Database License</a>. See <a href="https://www.openstreetmap.org/copyright" rel="nofollow">this page</a> for more details.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd datasette-basemap
  python3 -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd datasette-basemap
  python3 -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzMzUxNzU2Mzc=
name: datasette-tiles
full_name: simonw/datasette-tiles
html_url: https://github.com/simonw/datasette-tiles
description: Mapping tile server for Datasette, serving tiles from MBTiles packages
created_at: 2021-02-02T05:11:12Z
updated_at: 2025-04-30T07:09:26Z
pushed_at: 2022-06-11T05:15:02Z
homepage: https://datasette.io/plugins/datasette-tiles
language: Python
topics: ["datasette", "datasette-io", "datasette-plugin", "mbtiles"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # datasette-tiles

  [![PyPI](https://img.shields.io/pypi/v/datasette-tiles.svg)](https://pypi.org/project/datasette-tiles/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-tiles?include_prereleases&label=changelog)](https://github.com/simonw/datasette-tiles/releases)
  [![Tests](https://github.com/simonw/datasette-tiles/workflows/Test/badge.svg)](https://github.com/simonw/datasette-tiles/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-tiles/blob/main/LICENSE)

  Datasette plugin for serving MBTiles map tiles

  ## Installation

  Install this plugin in the same environment as Datasette.

      $ datasette install datasette-tiles

  ## Demo

  You can try this plugin out at https://datasette-tiles-demo.datasette.io/-/tiles

  ## Usage

  This plugin scans all database files connected to Datasette to see if any of them are valid MBTiles databases.

  It can then serve tiles from those databases at the following URL:

      /-/tiles/db-name/zoom/x/y.png

  An example map for each database demonstrating the configured minimum and maximum zoom for that database can be found at `/-/tiles/db-name` - this can also be accessed via the table and database action menus for that database.

  Visit `/-/tiles` for an index page of attached valid databases.

  You can install the [datasette-basemap](https://datasette.io/plugins/datasette-basemap) plugin to get a `basemap` default set of tiles, handling zoom levels 0 to 6 using OpenStreetMap.

  ### Tile coordinate systems

  There are two tile coordinate systems in common use for online maps. The first is used by OpenStreetMap and Google Maps, the second is from a specification called [Tile Map Service](https://en.wikipedia.org/wiki/Tile_Map_Service), or TMS.

  Both systems use three components: `z/x/y` - where `z` is the zoom level, `x` is the column and `y` is the row.

  The difference is in the way the `y` value is counted. OpenStreetMap has y=0 at the top. TMS has y=0 at the bottom.

  An illustrative example: at zoom level 2 the map is divided into 16 total tiles. The OpenStreetMap scheme numbers them like so:

      0/0  1/0  2/0  3/0
      0/1  1/1  2/1  3/1
      0/2  1/2  2/2  3/2
      0/3  1/3  2/3  3/3

  The TMS scheme looks like this:

      0/3  1/3  2/3  3/3
      0/2  1/2  2/2  3/2
      0/1  1/1  2/1  3/1
      0/0  1/0  2/0  3/0

  `datasette-tiles` can serve tiles using either of these standards. For the OpenStreetMap / Google Maps 0-at-the-top system, use the following URL:

      /-/tiles/database-name/{z}/{x}/{y}.png

  For the TMS 0-at-the-bottom system, use this:

      /-/tiles-tms/database-name/{z}/{x}/{y}.png

  ### Configuring a Leaflet tile layer

  The following JavaScript will configure a [Leaflet TileLayer](https://leafletjs.com/reference-1.7.1.html#tilelayer) for use with this plugin:

  ```javascript
  var tiles = leaflet.tileLayer("/-/tiles/basemap/{z}/{x}/{y}.png", {
    minZoom: 0,
    maxZoom: 6,
    attribution: "\u00a9 OpenStreetMap contributors"
  });
  ```

  ### Tile stacks

  `datasette-tiles` can be configured to serve tiles from multiple attached MBTiles files, searching each database in order for a tile and falling back to the next in line if that tile is not found.

  For a demo of this in action, visit https://datasette-tiles-demo.datasette.io/-/tiles-stack and zoom in on Japan. It should start showing [Stamen's Toner map](maps.stamen.com) of Japan once you get to zoom level 6 and 7.

  The `/-/tiles-stack/{z}/{x}/{y}.png` endpoint provides this feature.

  If you start Datasette like this:

      datasette world.mbtiles country.mbtiles city1.mbtiles city2.mbtiles

  Any requests for a tile from the `/-/tiles-stack` path will first check the `city2` database, than `city1`, then `country`, then `world`.

  If you have the [datasette-basemap](https://datasette.io/plugins/datasette-basemap) plugin installed it will be given special treatment: the `basemap` database will always be the last database checked for a tile.

  Rather than rely on the order in which databases were attached, you can instead configure an explicit order using the `tiles-stack-order` plugin setting. Add the following to your `metadata.json` file:

  ```json
  {
      "plugins": {
          "datasette-tiles": {
              "tiles-stack-order": ["world", "country"]
          }
      }
  }
  ```

  You can then run Datasette like this:

      datasette -m metadata.json country.mbtiles world.mbtiles

  This endpoint serves tiles using the OpenStreetMap / Google Maps coordinate system. To load tiles using the TMS coordinate system use this endpoint instead:

      /-/tiles-stack-tms/{z}/{x}/{y}.png

  ### Retina tiles

  Retina (double resolution) tiles are supported by `datasette-tiles` if the MBTiles database file contains 512x512 tile images as opposed to the default of 256x256. JavaScript libraries such as Leaflet will serve these tiles with a fixed 256x256 size, which will cause them to be displayed correctly by capable operating systems.

  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:

      cd datasette-tiles
      python3 -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and tests:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-tiles</h1><a id="user-content-datasette-tiles" class="anchor" aria-label="Permalink: datasette-tiles" href="#datasette-tiles"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-tiles/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-tiles.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/9938600eb2b33665122dca9e13659edc7dcb87b1e7d4457c65310ca6c2f73937/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d74696c65732e737667"></a>
  <a href="https://github.com/simonw/datasette-tiles/releases"><img src="https://img.shields.io/github/v/release/simonw/datasette-tiles?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/e7a67a949978919dbd62d91416c4dd8c5121da26436f38e593fe7c1528619085/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f6461746173657474652d74696c65733f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/datasette-tiles/actions?query=workflow%3ATest"><img src="https://github.com/simonw/datasette-tiles/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/datasette-tiles/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Datasette plugin for serving MBTiles map tiles</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ datasette install datasette-tiles"><pre class="notranslate"><code>$ datasette install datasette-tiles
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Demo</h2><a id="user-content-demo" class="anchor" aria-label="Permalink: Demo" href="#demo"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">You can try this plugin out at <a href="https://datasette-tiles-demo.datasette.io/-/tiles" rel="nofollow">https://datasette-tiles-demo.datasette.io/-/tiles</a></p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">This plugin scans all database files connected to Datasette to see if any of them are valid MBTiles databases.</p>
  <p dir="auto">It can then serve tiles from those databases at the following URL:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="/-/tiles/db-name/zoom/x/y.png"><pre class="notranslate"><code>/-/tiles/db-name/zoom/x/y.png
  </code></pre></div>
  <p dir="auto">An example map for each database demonstrating the configured minimum and maximum zoom for that database can be found at <code>/-/tiles/db-name</code> - this can also be accessed via the table and database action menus for that database.</p>
  <p dir="auto">Visit <code>/-/tiles</code> for an index page of attached valid databases.</p>
  <p dir="auto">You can install the <a href="https://datasette.io/plugins/datasette-basemap" rel="nofollow">datasette-basemap</a> plugin to get a <code>basemap</code> default set of tiles, handling zoom levels 0 to 6 using OpenStreetMap.</p>
  <div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">Tile coordinate systems</h3><a id="user-content-tile-coordinate-systems" class="anchor" aria-label="Permalink: Tile coordinate systems" href="#tile-coordinate-systems"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">There are two tile coordinate systems in common use for online maps. The first is used by OpenStreetMap and Google Maps, the second is from a specification called <a href="https://en.wikipedia.org/wiki/Tile_Map_Service" rel="nofollow">Tile Map Service</a>, or TMS.</p>
  <p dir="auto">Both systems use three components: <code>z/x/y</code> - where <code>z</code> is the zoom level, <code>x</code> is the column and <code>y</code> is the row.</p>
  <p dir="auto">The difference is in the way the <code>y</code> value is counted. OpenStreetMap has y=0 at the top. TMS has y=0 at the bottom.</p>
  <p dir="auto">An illustrative example: at zoom level 2 the map is divided into 16 total tiles. The OpenStreetMap scheme numbers them like so:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="0/0  1/0  2/0  3/0
  0/1  1/1  2/1  3/1
  0/2  1/2  2/2  3/2
  0/3  1/3  2/3  3/3"><pre class="notranslate"><code>0/0  1/0  2/0  3/0
  0/1  1/1  2/1  3/1
  0/2  1/2  2/2  3/2
  0/3  1/3  2/3  3/3
  </code></pre></div>
  <p dir="auto">The TMS scheme looks like this:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="0/3  1/3  2/3  3/3
  0/2  1/2  2/2  3/2
  0/1  1/1  2/1  3/1
  0/0  1/0  2/0  3/0"><pre class="notranslate"><code>0/3  1/3  2/3  3/3
  0/2  1/2  2/2  3/2
  0/1  1/1  2/1  3/1
  0/0  1/0  2/0  3/0
  </code></pre></div>
  <p dir="auto"><code>datasette-tiles</code> can serve tiles using either of these standards. For the OpenStreetMap / Google Maps 0-at-the-top system, use the following URL:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="/-/tiles/database-name/{z}/{x}/{y}.png"><pre class="notranslate"><code>/-/tiles/database-name/{z}/{x}/{y}.png
  </code></pre></div>
  <p dir="auto">For the TMS 0-at-the-bottom system, use this:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="/-/tiles-tms/database-name/{z}/{x}/{y}.png"><pre class="notranslate"><code>/-/tiles-tms/database-name/{z}/{x}/{y}.png
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">Configuring a Leaflet tile layer</h3><a id="user-content-configuring-a-leaflet-tile-layer" class="anchor" aria-label="Permalink: Configuring a Leaflet tile layer" href="#configuring-a-leaflet-tile-layer"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">The following JavaScript will configure a <a href="https://leafletjs.com/reference-1.7.1.html#tilelayer" rel="nofollow">Leaflet TileLayer</a> for use with this plugin:</p>
  <div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="var tiles = leaflet.tileLayer("/-/tiles/basemap/{z}/{x}/{y}.png", {
    minZoom: 0,
    maxZoom: 6,
    attribution: "\u00a9 OpenStreetMap contributors"
  });"><pre><span class="pl-k">var</span> <span class="pl-s1">tiles</span> <span class="pl-c1">=</span> <span class="pl-s1">leaflet</span><span class="pl-kos">.</span><span class="pl-en">tileLayer</span><span class="pl-kos">(</span><span class="pl-s">"/-/tiles/basemap/{z}/{x}/{y}.png"</span><span class="pl-kos">,</span> <span class="pl-kos">{</span>
    <span class="pl-c1">minZoom</span>: <span class="pl-c1">0</span><span class="pl-kos">,</span>
    <span class="pl-c1">maxZoom</span>: <span class="pl-c1">6</span><span class="pl-kos">,</span>
    <span class="pl-c1">attribution</span>: <span class="pl-s">"\u00a9 OpenStreetMap contributors"</span>
  <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
  <div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">Tile stacks</h3><a id="user-content-tile-stacks" class="anchor" aria-label="Permalink: Tile stacks" href="#tile-stacks"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><code>datasette-tiles</code> can be configured to serve tiles from multiple attached MBTiles files, searching each database in order for a tile and falling back to the next in line if that tile is not found.</p>
  <p dir="auto">For a demo of this in action, visit <a href="https://datasette-tiles-demo.datasette.io/-/tiles-stack" rel="nofollow">https://datasette-tiles-demo.datasette.io/-/tiles-stack</a> and zoom in on Japan. It should start showing <a href="maps.stamen.com">Stamen's Toner map</a> of Japan once you get to zoom level 6 and 7.</p>
  <p dir="auto">The <code>/-/tiles-stack/{z}/{x}/{y}.png</code> endpoint provides this feature.</p>
  <p dir="auto">If you start Datasette like this:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="datasette world.mbtiles country.mbtiles city1.mbtiles city2.mbtiles"><pre class="notranslate"><code>datasette world.mbtiles country.mbtiles city1.mbtiles city2.mbtiles
  </code></pre></div>
  <p dir="auto">Any requests for a tile from the <code>/-/tiles-stack</code> path will first check the <code>city2</code> database, than <code>city1</code>, then <code>country</code>, then <code>world</code>.</p>
  <p dir="auto">If you have the <a href="https://datasette.io/plugins/datasette-basemap" rel="nofollow">datasette-basemap</a> plugin installed it will be given special treatment: the <code>basemap</code> database will always be the last database checked for a tile.</p>
  <p dir="auto">Rather than rely on the order in which databases were attached, you can instead configure an explicit order using the <code>tiles-stack-order</code> plugin setting. Add the following to your <code>metadata.json</code> file:</p>
  <div class="highlight highlight-source-json notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="{
      "plugins": {
          "datasette-tiles": {
              "tiles-stack-order": ["world", "country"]
          }
      }
  }"><pre>{
      <span class="pl-ent">"plugins"</span>: {
          <span class="pl-ent">"datasette-tiles"</span>: {
              <span class="pl-ent">"tiles-stack-order"</span>: [<span class="pl-s"><span class="pl-pds">"</span>world<span class="pl-pds">"</span></span>, <span class="pl-s"><span class="pl-pds">"</span>country<span class="pl-pds">"</span></span>]
          }
      }
  }</pre></div>
  <p dir="auto">You can then run Datasette like this:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="datasette -m metadata.json country.mbtiles world.mbtiles"><pre class="notranslate"><code>datasette -m metadata.json country.mbtiles world.mbtiles
  </code></pre></div>
  <p dir="auto">This endpoint serves tiles using the OpenStreetMap / Google Maps coordinate system. To load tiles using the TMS coordinate system use this endpoint instead:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="/-/tiles-stack-tms/{z}/{x}/{y}.png"><pre class="notranslate"><code>/-/tiles-stack-tms/{z}/{x}/{y}.png
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">Retina tiles</h3><a id="user-content-retina-tiles" class="anchor" aria-label="Permalink: Retina tiles" href="#retina-tiles"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Retina (double resolution) tiles are supported by <code>datasette-tiles</code> if the MBTiles database file contains 512x512 tile images as opposed to the default of 256x256. JavaScript libraries such as Leaflet will serve these tiles with a fixed 256x256 size, which will cause them to be displayed correctly by capable operating systems.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd datasette-tiles
  python3 -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd datasette-tiles
  python3 -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzMzUzNzIwNTA=
name: download-tiles
full_name: simonw/download-tiles
html_url: https://github.com/simonw/download-tiles
description: Download map tiles and store them in an MBTiles database
created_at: 2021-02-02T17:37:49Z
updated_at: 2025-05-10T12:10:35Z
pushed_at: 2023-08-24T17:45:26Z
homepage: https://datasette.io/tools/download-tiles
language: Python
license: apache-2.0
topics: ["datasette-io", "datasette-tool", "mbtiles", "openstreetmap"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # download-tiles

  [![PyPI](https://img.shields.io/pypi/v/download-tiles.svg)](https://pypi.org/project/download-tiles/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/download-tiles?include_prereleases&label=changelog)](https://github.com/simonw/download-tiles/releases)
  [![Tests](https://github.com/simonw/download-tiles/workflows/Test/badge.svg)](https://github.com/simonw/download-tiles/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/download-tiles/blob/master/LICENSE)

  Download map tiles and store them in an MBTiles database

  ## Installation

  Install this tool using `pip`:
  ```bash
  pip install download-tiles
  ```
  ## Usage

  This tool downloads tiles from a specified [TMS (Tile Map Server)](https://wiki.openstreetmap.org/wiki/TMS) server for a specified bounding box and range of zoom levels and stores those tiles in a MBTiles SQLite database. It is a command-line wrapper around the [Landez](https://github.com/makinacorpus/landez) Python libary.

  **Please use this tool responsibly**. Consult the usage policies of the tile servers you are interacting with, for example the [OpenStreetMap Tile Usage Policy](https://operations.osmfoundation.org/policies/tiles/).

  Running the following will download zoom levels 0-3 of OpenStreetMap, 85 tiles total, and store them in a SQLite database called `world.mbtiles`:
  ```bash
  download-tiles world.mbtiles
  ```
  You can customize which tile and zoom levels are downloaded using command options:
  ```
  --zoom-levels=0-3
  ```
  The different zoom levels to download. Specify a single number, e.g. `15`, or a range of numbers e.g. `0-4`. Be careful with this setting as you can easily go over the limits requested by the underlying tile server.
  ```
  --bbox=3.9,-6.3,14.5,10.2
  ```
  The bounding box to fetch. Should be specified as `min-lon,min-lat,max-lon,max-lat`. You can use [bboxfinder.com](http://bboxfinder.com/) to find these for different areas.
  ```
  --city=london
  ```
  Or:
  ```
  --country=madagascar
  ```

  These options can be used instead of `--bbox`. The city or country specified will be looked up using the [Nominatum API](https://nominatim.org/release-docs/latest/api/Search/) and used to derive a bounding box.
  ```
  --show-bbox
  ```
  Use this option to output the bounding box that was retrieved for the `--city` or `--country` without downloading any tiles.
  ```
  --name=Name
  ```
  A name for this tile collection, used for the `name` field in the `metadata` table. If not specified a UUID will be used, or if you used `--city` or `--country` the name will be set to the full name of that place.
  ```
  --attribution="Attribution string"
  ```

  Attribution string to bake into the `metadata` table. This will default to `© OpenStreetMap contributors` unless you use `--tiles-url` to specify an alternative tile server, in which case you should specify a custom attribution string.

  You can use the `--attribution=osm` shortcut to specify the `© OpenStreetMap contributors` value without having to type it out in full.
  ```
  --tiles-url=https://...
  ```
  The tile server URL to use. This should include `{z}` and `{x}` and `{y}` specifiers, and can optionally include `{s}` for subdomains.

  The default URL used here is for OpenStreetMap, `http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`
  ```
  --tiles-subdomains=a,b,c
  ```
  A comma-separated list of subdomains to use for the `{s}` parameter.
  ```
  --verbose
  ```
  Use this option to turn on verbose logging.
  ```
  --cache-dir=/tmp/tiles
  ```
  Provide a directory to cache downloaded tiles between runs. This can be useful if you are worried you might not have used the correct options for the bounding box or zoom levels.

  Databases created with this tool will have their SQLite `application_id` set to `0x4d504258`, as described in the SQLite [magic.txt file](https://www.sqlite.org/src/artifact?ci=trunk&filename=magic.txt).

  ## Development

  To contribute to this tool, first checkout the code. Then create a new virtual environment:
  ```bash
  cd download-tiles
  python -mvenv venv
  source venv/bin/activate
  ```
  Or if you are using `pipenv`:
  ```bash
  pipenv shell
  ```
  Now install the dependencies and tests:
  ```bash
  pip install -e '.[test]'
  ```
  To run the tests:
  ```bash
  pytest
  ```

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">download-tiles</h1><a id="user-content-download-tiles" class="anchor" aria-label="Permalink: download-tiles" href="#download-tiles"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/download-tiles/" rel="nofollow"><img src="https://img.shields.io/pypi/v/download-tiles.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/f03091744b581b6fbf7fc658f7268eb3a4bba9149a0f4f024b44e152a8a32020/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f646f776e6c6f61642d74696c65732e737667"></a>
  <a href="https://github.com/simonw/download-tiles/releases"><img src="https://img.shields.io/github/v/release/simonw/download-tiles?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c63966b255e30f7127f78036dadca892ed7feef7ea885b85bdf0613545d8fe7c/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f646f776e6c6f61642d74696c65733f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/download-tiles/actions?query=workflow%3ATest"><img src="https://github.com/simonw/download-tiles/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/download-tiles/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Download map tiles and store them in an MBTiles database</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this tool using <code>pip</code>:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="pip install download-tiles"><pre>pip install download-tiles</pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">This tool downloads tiles from a specified <a href="https://wiki.openstreetmap.org/wiki/TMS" rel="nofollow">TMS (Tile Map Server)</a> server for a specified bounding box and range of zoom levels and stores those tiles in a MBTiles SQLite database. It is a command-line wrapper around the <a href="https://github.com/makinacorpus/landez">Landez</a> Python libary.</p>
  <p dir="auto"><strong>Please use this tool responsibly</strong>. Consult the usage policies of the tile servers you are interacting with, for example the <a href="https://operations.osmfoundation.org/policies/tiles/" rel="nofollow">OpenStreetMap Tile Usage Policy</a>.</p>
  <p dir="auto">Running the following will download zoom levels 0-3 of OpenStreetMap, 85 tiles total, and store them in a SQLite database called <code>world.mbtiles</code>:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="download-tiles world.mbtiles"><pre>download-tiles world.mbtiles</pre></div>
  <p dir="auto">You can customize which tile and zoom levels are downloaded using command options:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="--zoom-levels=0-3"><pre class="notranslate"><code>--zoom-levels=0-3
  </code></pre></div>
  <p dir="auto">The different zoom levels to download. Specify a single number, e.g. <code>15</code>, or a range of numbers e.g. <code>0-4</code>. Be careful with this setting as you can easily go over the limits requested by the underlying tile server.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="--bbox=3.9,-6.3,14.5,10.2"><pre class="notranslate"><code>--bbox=3.9,-6.3,14.5,10.2
  </code></pre></div>
  <p dir="auto">The bounding box to fetch. Should be specified as <code>min-lon,min-lat,max-lon,max-lat</code>. You can use <a href="http://bboxfinder.com/" rel="nofollow">bboxfinder.com</a> to find these for different areas.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="--city=london"><pre class="notranslate"><code>--city=london
  </code></pre></div>
  <p dir="auto">Or:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="--country=madagascar"><pre class="notranslate"><code>--country=madagascar
  </code></pre></div>
  <p dir="auto">These options can be used instead of <code>--bbox</code>. The city or country specified will be looked up using the <a href="https://nominatim.org/release-docs/latest/api/Search/" rel="nofollow">Nominatum API</a> and used to derive a bounding box.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="--show-bbox"><pre class="notranslate"><code>--show-bbox
  </code></pre></div>
  <p dir="auto">Use this option to output the bounding box that was retrieved for the <code>--city</code> or <code>--country</code> without downloading any tiles.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="--name=Name"><pre class="notranslate"><code>--name=Name
  </code></pre></div>
  <p dir="auto">A name for this tile collection, used for the <code>name</code> field in the <code>metadata</code> table. If not specified a UUID will be used, or if you used <code>--city</code> or <code>--country</code> the name will be set to the full name of that place.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="--attribution="Attribution string""><pre class="notranslate"><code>--attribution="Attribution string"
  </code></pre></div>
  <p dir="auto">Attribution string to bake into the <code>metadata</code> table. This will default to <code>© OpenStreetMap contributors</code> unless you use <code>--tiles-url</code> to specify an alternative tile server, in which case you should specify a custom attribution string.</p>
  <p dir="auto">You can use the <code>--attribution=osm</code> shortcut to specify the <code>© OpenStreetMap contributors</code> value without having to type it out in full.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="--tiles-url=https://..."><pre class="notranslate"><code>--tiles-url=https://...
  </code></pre></div>
  <p dir="auto">The tile server URL to use. This should include <code>{z}</code> and <code>{x}</code> and <code>{y}</code> specifiers, and can optionally include <code>{s}</code> for subdomains.</p>
  <p dir="auto">The default URL used here is for OpenStreetMap, <code>http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png</code></p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="--tiles-subdomains=a,b,c"><pre class="notranslate"><code>--tiles-subdomains=a,b,c
  </code></pre></div>
  <p dir="auto">A comma-separated list of subdomains to use for the <code>{s}</code> parameter.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="--verbose"><pre class="notranslate"><code>--verbose
  </code></pre></div>
  <p dir="auto">Use this option to turn on verbose logging.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="--cache-dir=/tmp/tiles"><pre class="notranslate"><code>--cache-dir=/tmp/tiles
  </code></pre></div>
  <p dir="auto">Provide a directory to cache downloaded tiles between runs. This can be useful if you are worried you might not have used the correct options for the bounding box or zoom levels.</p>
  <p dir="auto">Databases created with this tool will have their SQLite <code>application_id</code> set to <code>0x4d504258</code>, as described in the SQLite <a href="https://www.sqlite.org/src/artifact?ci=trunk&filename=magic.txt" rel="nofollow">magic.txt file</a>.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To contribute to this tool, first checkout the code. Then create a new virtual environment:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="cd download-tiles
  python -mvenv venv
  source venv/bin/activate"><pre><span class="pl-c1">cd</span> download-tiles
  python -mvenv venv
  <span class="pl-c1">source</span> venv/bin/activate</pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="pipenv shell"><pre>pipenv shell</pre></div>
  <p dir="auto">Now install the dependencies and tests:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre>pip install -e <span class="pl-s"><span class="pl-pds">'</span>.[test]<span class="pl-pds">'</span></span></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="pytest"><pre>pytest</pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzNDIxMjY2MTA=
name: datasette-block
full_name: simonw/datasette-block
html_url: https://github.com/simonw/datasette-block
description: Block all access to specific path prefixes
created_at: 2021-02-25T04:51:08Z
updated_at: 2021-02-25T08:18:28Z
pushed_at: 2021-02-25T05:03:45Z
homepage: https://datasette.io/plugins/datasette-block
language: Python
topics: ["datasette", "datasette-io", "datasette-plugin"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # datasette-block

  [![PyPI](https://img.shields.io/pypi/v/datasette-block.svg)](https://pypi.org/project/datasette-block/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-block?include_prereleases&label=changelog)](https://github.com/simonw/datasette-block/releases)
  [![Tests](https://github.com/simonw/datasette-block/workflows/Test/badge.svg)](https://github.com/simonw/datasette-block/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-block/blob/main/LICENSE)

  Block all access to specific path prefixes

  ## Installation

  Install this plugin in the same environment as Datasette.

      $ datasette install datasette-block

  ## Configuration

  Add the following to `metadata.json` to block specific path prefixes:

  ```json
  {
      "plugins": {
          "datasette-block": {
              "prefixes": ["/all/"]
          }
      }
  }
  ```

  This will cause a 403 error to be returned for any path beginning with `/all/`.

  This blocking happens as an ASGI wrapper around Datasette.

  ## Why would you need this?

  You almost always would not. I use it with `datasette-ripgrep` to block access to static assets for unauthenticated users.

  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:

      cd datasette-block
      python3 -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and tests:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-block</h1><a id="user-content-datasette-block" class="anchor" aria-label="Permalink: datasette-block" href="#datasette-block"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-block/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-block.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/8991bad869b8a88cf259aec1435ff535d23f134e7d9f3ae20942eb2594f8c8c4/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d626c6f636b2e737667"></a>
  <a href="https://github.com/simonw/datasette-block/releases"><img src="https://img.shields.io/github/v/release/simonw/datasette-block?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/e52b9cc3f3268b938747d48b6d6db6a2acddb038f4c03c8dadabaa7f186e5b85/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f6461746173657474652d626c6f636b3f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/datasette-block/actions?query=workflow%3ATest"><img src="https://github.com/simonw/datasette-block/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/datasette-block/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Block all access to specific path prefixes</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ datasette install datasette-block"><pre class="notranslate"><code>$ datasette install datasette-block
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Configuration</h2><a id="user-content-configuration" class="anchor" aria-label="Permalink: Configuration" href="#configuration"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Add the following to <code>metadata.json</code> to block specific path prefixes:</p>
  <div class="highlight highlight-source-json notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="{
      "plugins": {
          "datasette-block": {
              "prefixes": ["/all/"]
          }
      }
  }"><pre>{
      <span class="pl-ent">"plugins"</span>: {
          <span class="pl-ent">"datasette-block"</span>: {
              <span class="pl-ent">"prefixes"</span>: [<span class="pl-s"><span class="pl-pds">"</span>/all/<span class="pl-pds">"</span></span>]
          }
      }
  }</pre></div>
  <p dir="auto">This will cause a 403 error to be returned for any path beginning with <code>/all/</code>.</p>
  <p dir="auto">This blocking happens as an ASGI wrapper around Datasette.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Why would you need this?</h2><a id="user-content-why-would-you-need-this" class="anchor" aria-label="Permalink: Why would you need this?" href="#why-would-you-need-this"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">You almost always would not. I use it with <code>datasette-ripgrep</code> to block access to static assets for unauthenticated users.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd datasette-block
  python3 -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd datasette-block
  python3 -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzNDY1OTc1NTc=
name: tableau-to-sqlite
full_name: simonw/tableau-to-sqlite
html_url: https://github.com/simonw/tableau-to-sqlite
description: Fetch data from Tableau into a SQLite database
created_at: 2021-03-11T06:12:02Z
updated_at: 2025-02-28T21:22:14Z
pushed_at: 2021-07-29T13:56:42Z
language: Python
license: apache-2.0
topics: ["datasette-io", "datasette-tool"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # tableau-to-sqlite

  [![PyPI](https://img.shields.io/pypi/v/tableau-to-sqlite.svg)](https://pypi.org/project/tableau-to-sqlite/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/tableau-to-sqlite?include_prereleases&label=changelog)](https://github.com/simonw/tableau-to-sqlite/releases)
  [![Tests](https://github.com/simonw/tableau-to-sqlite/workflows/Test/badge.svg)](https://github.com/simonw/tableau-to-sqlite/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/tableau-to-sqlite/blob/master/LICENSE)

  Fetch data from Tableau into a SQLite database. A wrapper around [TableauScraper](https://github.com/bertrandmartel/tableau-scraping/).

  ## Installation

  Install this tool using `pip`:

      $ pip install tableau-to-sqlite

  ## Usage

  If you have the URL to a Tableau dashboard like this:

  https://results.mo.gov/t/COVID19/views/VaccinationsDashboard/Vaccinations

  You can pass that directly to the tool:

      tableau-to-sqlite tableau.db \
        https://results.mo.gov/t/COVID19/views/VaccinationsDashboard/Vaccinations

  This will create a SQLite database called `tableau.db` containing one table for each of the worksheets in that dashboard.

  If the dashboard is hosted on https://public.tableau.com/ you can instead provide the view name. This will be two strings separated by a `/` symbol - something like this:

      OregonCOVID-19VaccineProviderEnrollment/COVID-19VaccineProviderEnrollment

  Now run the tool like this:

      tableau-to-sqlite tableau.db \
          OregonCOVID-19VaccineProviderEnrollment/COVID-19VaccineProviderEnrollment

  ## Get the data as JSON or CSV

  If you're building a [git scraper](https://simonwillison.net/2020/Oct/9/git-scraping/) you may want to convert the data gathered by this tool to CSV or JSON to check into your repository.

  You can do that using [sqlite-utils](https://sqlite-utils.datasette.io/). Install it using `pip`:

      pip install sqlite-utils

  You can dump out a table as JSON like so:

      sqlite-utils rows tableau.db \
         'Admin Site and County Map Site No Info' > tableau.json

  Or as CSV like this:

      sqlite-utils rows tableau.db --csv \
         'Admin Site and County Map Site No Info' > tableau.csv

  ## Development

  To contribute to this tool, first checkout the code. Then create a new virtual environment:

      cd tableau-to-sqlite
      python -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and tests:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">tableau-to-sqlite</h1><a id="user-content-tableau-to-sqlite" class="anchor" aria-label="Permalink: tableau-to-sqlite" href="#tableau-to-sqlite"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/tableau-to-sqlite/" rel="nofollow"><img src="https://img.shields.io/pypi/v/tableau-to-sqlite.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/b78cfd28f83ed7f457aa5f05f17f1562d812faba6178c9d1107ce0c35eb30f56/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f7461626c6561752d746f2d73716c6974652e737667"></a>
  <a href="https://github.com/simonw/tableau-to-sqlite/releases"><img src="https://img.shields.io/github/v/release/simonw/tableau-to-sqlite?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/ecc10d5bc3c42d1becafc89a84eb05a0a2bdd58bb4eb4a0fa8f5c078f8422540/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f7461626c6561752d746f2d73716c6974653f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/tableau-to-sqlite/actions?query=workflow%3ATest"><img src="https://github.com/simonw/tableau-to-sqlite/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/tableau-to-sqlite/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Fetch data from Tableau into a SQLite database. A wrapper around <a href="https://github.com/bertrandmartel/tableau-scraping/">TableauScraper</a>.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this tool using <code>pip</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ pip install tableau-to-sqlite"><pre class="notranslate"><code>$ pip install tableau-to-sqlite
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">If you have the URL to a Tableau dashboard like this:</p>
  <p dir="auto"><a href="https://results.mo.gov/t/COVID19/views/VaccinationsDashboard/Vaccinations" rel="nofollow">https://results.mo.gov/t/COVID19/views/VaccinationsDashboard/Vaccinations</a></p>
  <p dir="auto">You can pass that directly to the tool:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="tableau-to-sqlite tableau.db \
    https://results.mo.gov/t/COVID19/views/VaccinationsDashboard/Vaccinations"><pre class="notranslate"><code>tableau-to-sqlite tableau.db \
    https://results.mo.gov/t/COVID19/views/VaccinationsDashboard/Vaccinations
  </code></pre></div>
  <p dir="auto">This will create a SQLite database called <code>tableau.db</code> containing one table for each of the worksheets in that dashboard.</p>
  <p dir="auto">If the dashboard is hosted on <a href="https://public.tableau.com/" rel="nofollow">https://public.tableau.com/</a> you can instead provide the view name. This will be two strings separated by a <code>/</code> symbol - something like this:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="OregonCOVID-19VaccineProviderEnrollment/COVID-19VaccineProviderEnrollment"><pre class="notranslate"><code>OregonCOVID-19VaccineProviderEnrollment/COVID-19VaccineProviderEnrollment
  </code></pre></div>
  <p dir="auto">Now run the tool like this:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="tableau-to-sqlite tableau.db \
      OregonCOVID-19VaccineProviderEnrollment/COVID-19VaccineProviderEnrollment"><pre class="notranslate"><code>tableau-to-sqlite tableau.db \
      OregonCOVID-19VaccineProviderEnrollment/COVID-19VaccineProviderEnrollment
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Get the data as JSON or CSV</h2><a id="user-content-get-the-data-as-json-or-csv" class="anchor" aria-label="Permalink: Get the data as JSON or CSV" href="#get-the-data-as-json-or-csv"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">If you're building a <a href="https://simonwillison.net/2020/Oct/9/git-scraping/" rel="nofollow">git scraper</a> you may want to convert the data gathered by this tool to CSV or JSON to check into your repository.</p>
  <p dir="auto">You can do that using <a href="https://sqlite-utils.datasette.io/" rel="nofollow">sqlite-utils</a>. Install it using <code>pip</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install sqlite-utils"><pre class="notranslate"><code>pip install sqlite-utils
  </code></pre></div>
  <p dir="auto">You can dump out a table as JSON like so:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="sqlite-utils rows tableau.db \
     'Admin Site and County Map Site No Info' > tableau.json"><pre class="notranslate"><code>sqlite-utils rows tableau.db \
     'Admin Site and County Map Site No Info' > tableau.json
  </code></pre></div>
  <p dir="auto">Or as CSV like this:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="sqlite-utils rows tableau.db --csv \
     'Admin Site and County Map Site No Info' > tableau.csv"><pre class="notranslate"><code>sqlite-utils rows tableau.db --csv \
     'Admin Site and County Map Site No Info' > tableau.csv
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To contribute to this tool, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd tableau-to-sqlite
  python -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd tableau-to-sqlite
  python -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzNDcyNjM3MjI=
name: django-sql-dashboard
full_name: simonw/django-sql-dashboard
html_url: https://github.com/simonw/django-sql-dashboard
description: Django app for building dashboards using raw SQL queries
created_at: 2021-03-13T03:38:23Z
updated_at: 2026-04-07T02:57:04Z
pushed_at: 2025-12-30T00:10:35Z
homepage: https://django-sql-dashboard.datasette.io/
language: Python
license: apache-2.0
topics: ["dashboards", "datasette-io", "datasette-tool", "django", "sql"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": false}
pull_request_creation_policy: all
readme:
  # django-sql-dashboard

  [![PyPI](https://img.shields.io/pypi/v/django-sql-dashboard.svg)](https://pypi.org/project/django-sql-dashboard/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/django-sql-dashboard?include_prereleases&label=changelog)](https://github.com/simonw/django-sql-dashboard/releases)
  [![Tests](https://github.com/simonw/django-sql-dashboard/workflows/Test/badge.svg)](https://github.com/simonw/django-sql-dashboard/actions?query=workflow%3ATest)
  [![Documentation Status](https://readthedocs.org/projects/django-sql-dashboard/badge/?version=latest)](http://django-sql-dashboard.datasette.io/en/latest/?badge=latest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/django-sql-dashboard/blob/main/LICENSE)

  Django SQL Dashboard provides an authenticated interface for executing read-only SQL queries directly against your PostgreSQL database, bringing a useful subset of [Datasette](https://datasette.io/) to Django.

  Applications include ad-hoc analysis and debugging, plus the creation of reporting dashboards that can be shared with team members or published online.

  See my blog for [more about this project](https://simonwillison.net/2021/May/10/django-sql-dashboard/), including [a video demo](https://www.youtube.com/watch?v=ausrmMZkPEY).

  Features include:

  - Safely run read-only one or more SQL queries against your database and view the results in your browser
  - Bookmark queries and share those links with other members of your team
  - Create [saved dashboards](https://django-sql-dashboard.datasette.io/en/latest/saved-dashboards.html) from your queries, with full control over who can view and edit them
  - [Named parameters](https://django-sql-dashboard.datasette.io/en/latest/sql.html#sql-parameters) such as `select * from entries where id = %(id)s` will be turned into form fields, allowing quick creation of interactive dashboards
  - Produce [bar charts](https://django-sql-dashboard.datasette.io/en/latest/widgets.html#bar-label-bar-quantity), [progress bars](https://django-sql-dashboard.datasette.io/en/latest/widgets.html#total-count-completed-count) and more from SQL queries, with the ability to easily create new [custom dashboard widgets](https://django-sql-dashboard.datasette.io/en/latest/widgets.html#custom-widgets) using the Django template system
  - Write SQL queries that safely construct and render [markdown](https://django-sql-dashboard.datasette.io/en/latest/widgets.html#markdown) and [HTML](https://django-sql-dashboard.datasette.io/en/latest/widgets.html#html)
  - Export the full results of a SQL query as a downloadable CSV or TSV file, using a combination of Django's [streaming HTTP response](https://docs.djangoproject.com/en/3.2/ref/request-response/#django.http.StreamingHttpResponse) mechanism and PostgreSQL [server-side cursors](https://www.psycopg.org/docs/usage.html#server-side-cursors) to efficiently stream large amounts of data without running out of resources
  - Copy and paste the results of SQL queries directly into tools such as Google Sheets or Excel
  - Uses Django's authentication system, so dashboard accounts can be granted using Django's Admin tools

  ## Documentation

  Full documentation is at [django-sql-dashboard.datasette.io](https://django-sql-dashboard.datasette.io/)

  ## Screenshot

  <img width="1018" alt="Screenshot showing a SQL query that produces a table and one that produces a bar chart" src="https://user-images.githubusercontent.com/9599/124050883-42ad2300-d9d0-11eb-83e6-44ad85f7ef64.png">

  ## Alternatives

  - [django-sql-explorer](https://github.com/groveco/django-sql-explorer) provides a related set of functionality that also works against database backends other than PostgreSQL

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">django-sql-dashboard</h1><a id="user-content-django-sql-dashboard" class="anchor" aria-label="Permalink: django-sql-dashboard" href="#django-sql-dashboard"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/django-sql-dashboard/" rel="nofollow"><img src="https://img.shields.io/pypi/v/django-sql-dashboard.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/b032f1b547f0d60347cc43b0ba9165750155aa50b4ae93d1e91f9276dec6110c/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f646a616e676f2d73716c2d64617368626f6172642e737667"></a>
  <a href="https://github.com/simonw/django-sql-dashboard/releases"><img src="https://img.shields.io/github/v/release/simonw/django-sql-dashboard?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/034b468aa072bd196e91db649812ab758cc10be9d062c6142bed3a4c8de7ba9e/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f646a616e676f2d73716c2d64617368626f6172643f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/django-sql-dashboard/actions?query=workflow%3ATest"><img src="https://github.com/simonw/django-sql-dashboard/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="http://django-sql-dashboard.datasette.io/en/latest/?badge=latest" rel="nofollow"><img src="https://readthedocs.org/projects/django-sql-dashboard/badge/?version=latest" alt="Documentation Status" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/ae6b18ddb084376e2d7ec7a2804272031be2b48f5a12c273dada39e752357daf/68747470733a2f2f72656164746865646f63732e6f72672f70726f6a656374732f646a616e676f2d73716c2d64617368626f6172642f62616467652f3f76657273696f6e3d6c6174657374"></a>
  <a href="https://github.com/simonw/django-sql-dashboard/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Django SQL Dashboard provides an authenticated interface for executing read-only SQL queries directly against your PostgreSQL database, bringing a useful subset of <a href="https://datasette.io/" rel="nofollow">Datasette</a> to Django.</p>
  <p dir="auto">Applications include ad-hoc analysis and debugging, plus the creation of reporting dashboards that can be shared with team members or published online.</p>
  <p dir="auto">See my blog for <a href="https://simonwillison.net/2021/May/10/django-sql-dashboard/" rel="nofollow">more about this project</a>, including <a href="https://www.youtube.com/watch?v=ausrmMZkPEY" rel="nofollow">a video demo</a>.</p>
  <p dir="auto">Features include:</p>
  <ul dir="auto">
  <li>Safely run read-only one or more SQL queries against your database and view the results in your browser</li>
  <li>Bookmark queries and share those links with other members of your team</li>
  <li>Create <a href="https://django-sql-dashboard.datasette.io/en/latest/saved-dashboards.html" rel="nofollow">saved dashboards</a> from your queries, with full control over who can view and edit them</li>
  <li><a href="https://django-sql-dashboard.datasette.io/en/latest/sql.html#sql-parameters" rel="nofollow">Named parameters</a> such as <code>select * from entries where id = %(id)s</code> will be turned into form fields, allowing quick creation of interactive dashboards</li>
  <li>Produce <a href="https://django-sql-dashboard.datasette.io/en/latest/widgets.html#bar-label-bar-quantity" rel="nofollow">bar charts</a>, <a href="https://django-sql-dashboard.datasette.io/en/latest/widgets.html#total-count-completed-count" rel="nofollow">progress bars</a> and more from SQL queries, with the ability to easily create new <a href="https://django-sql-dashboard.datasette.io/en/latest/widgets.html#custom-widgets" rel="nofollow">custom dashboard widgets</a> using the Django template system</li>
  <li>Write SQL queries that safely construct and render <a href="https://django-sql-dashboard.datasette.io/en/latest/widgets.html#markdown" rel="nofollow">markdown</a> and <a href="https://django-sql-dashboard.datasette.io/en/latest/widgets.html#html" rel="nofollow">HTML</a></li>
  <li>Export the full results of a SQL query as a downloadable CSV or TSV file, using a combination of Django's <a href="https://docs.djangoproject.com/en/3.2/ref/request-response/#django.http.StreamingHttpResponse" rel="nofollow">streaming HTTP response</a> mechanism and PostgreSQL <a href="https://www.psycopg.org/docs/usage.html#server-side-cursors" rel="nofollow">server-side cursors</a> to efficiently stream large amounts of data without running out of resources</li>
  <li>Copy and paste the results of SQL queries directly into tools such as Google Sheets or Excel</li>
  <li>Uses Django's authentication system, so dashboard accounts can be granted using Django's Admin tools</li>
  </ul>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Documentation</h2><a id="user-content-documentation" class="anchor" aria-label="Permalink: Documentation" href="#documentation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Full documentation is at <a href="https://django-sql-dashboard.datasette.io/" rel="nofollow">django-sql-dashboard.datasette.io</a></p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Screenshot</h2><a id="user-content-screenshot" class="anchor" aria-label="Permalink: Screenshot" href="#screenshot"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/9599/124050883-42ad2300-d9d0-11eb-83e6-44ad85f7ef64.png"><img width="1018" alt="Screenshot showing a SQL query that produces a table and one that produces a bar chart" src="https://user-images.githubusercontent.com/9599/124050883-42ad2300-d9d0-11eb-83e6-44ad85f7ef64.png" style="max-width: 100%;"></a></p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Alternatives</h2><a id="user-content-alternatives" class="anchor" aria-label="Permalink: Alternatives" href="#alternatives"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <ul dir="auto">
  <li><a href="https://github.com/groveco/django-sql-explorer">django-sql-explorer</a> provides a related set of functionality that also works against database backends other than PostgreSQL</li>
  </ul>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzNjEwMTQyNzM=
name: datasette-dashboards
full_name: rclement/datasette-dashboards
html_url: https://github.com/rclement/datasette-dashboards
description: Datasette plugin to create interactive dashboards
created_at: 2021-04-23T21:56:48Z
updated_at: 2026-06-12T12:40:49Z
pushed_at: 2026-06-13T20:50:05Z
homepage: https://datasette-dashboards-demo.vercel.app
language: Python
license: apache-2.0
visibility: public
default_branch: master
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": false}
pull_request_creation_policy: all
topics:
  ["business-intelligence", "dashboards", "data-visualization", "datasette", "datasette-io", "datasette-plugin", "sql", "vega-lite"]

readme:
  # datasette-dashboards

  > Datasette plugin providing data dashboards from metadata

  [![PyPI](https://img.shields.io/pypi/v/datasette-dashboards.svg)](https://pypi.org/project/datasette-dashboards/)
  [![CI/CD](https://github.com/rclement/datasette-dashboards/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/rclement/datasette-dashboards/actions/workflows/ci-cd.yml)
  [![Coverage Status](https://img.shields.io/codecov/c/github/rclement/datasette-dashboards)](https://codecov.io/gh/rclement/datasette-dashboards)
  [![License](https://img.shields.io/github/license/rclement/datasette-dashboards)](https://github.com/rclement/datasette-dashboards/blob/master/LICENSE)

  Try out a live demo at [https://datasette-dashboards-demo.vercel.app](https://datasette-dashboards-demo.vercel.app/-/dashboards)

  **WARNING**: this plugin is still experimental and not ready for production.
  Some breaking changes might happen between releases before reaching a stable version.
  Use it at your own risks!

  ![Datasette Dashboards Demo](https://raw.githubusercontent.com/rclement/datasette-dashboards/master/demo/datasette-dashboards-demo.png)

  ## Installation

  Install this plugin in the same environment as Datasette:

  ```bash
  $ datasette install datasette-dashboards
  ```

  ## Usage

  Define dashboards within `metadata.yml` / `metadata.json`:

  ```yaml
  plugins:
    datasette-dashboards:
      my-dashboard:
        title: My Dashboard
        description: Showing some nice metrics
        layout:
          - [analysis-note, events-count]
          - [analysis-note, events-source]
        filters:
          date_start:
            name: Date Start
            type: date
            default: "2021-01-01"
          date_end:
            name: Date End
            type: date
          category:
            name: My Category
            type: select
            options: [Option 1, Option 2, Option 3]
          dynamic_category:
            name: My Dynamic Category
            type: select
            db: jobs
            query: SELECT region FROM jobs ORDER BY region ASC
        charts:
          analysis-note:
            library: markdown
            display: |-
              # Analysis notes
              > A quick rundown of events statistics and KPIs

          events-count:
            title: Total number of events
            db: jobs
            query: SELECT count(*) as count FROM events
            library: metric
            display:
              field: count
              prefix:
              suffix:

          events-source:
            title: Number of events by source
            db: jobs
            query: SELECT source, count(*) as count FROM events WHERE TRUE [[ AND date >= date(:date_start) ]] [[ AND date <= date(:date_end) ]] GROUP BY source ORDER BY count DESC
            library: vega-lite
            display:
              mark: { type: arc, tooltip: true }
              encoding:
                color: { field: source, type: nominal }
                theta: { field: count, type: quantitative }
  ```

  A new menu entry is now available, pointing at `/-/dashboards` to access all defined dashboards.

  ### Properties

  Dashboard properties:

  | Property      | Type     | Description           |
  | ------------- | -------- | --------------------- |
  | `title`       | `string` | Dashboard title       |
  | `description` | `string` | Dashboard description |
  | `settings`    | `object` | Dashboard settings    |
  | `layout`      | `array`  | Dashboard layout      |
  | `filters`     | `object` | Dashboard filters     |

  Dashboard settings:

  | Property           | Type     | Description                                                    |
  | ------------------ | -------- | -------------------------------------------------------------- |
  | `allow_fullscreen` | `bool`   | Allow dashboard to be toggled in fullscreen  (default `false`) |
  | `autorefresh`      | `number` | Auto-refresh timeout in minutes                                |

  Dashboard filters:

  | Property  | Type               | Description                                      |
  | --------- | ------------------ | ------------------------------------------------ |
  | `name`    | `string`           | Filter display name                              |
  | `type`    | `string`           | Filter type (`text`, `date`, `number`, `select`) |
  | `default` | `string`, `number` | (optional) Filter default value                  |
  | `min`     | `number`           | (optional) Filter minimum value                  |
  | `max`     | `number`           | (optional) Filter maximum value                  |
  | `step`    | `number`           | (optional) Filter stepping value                 |
  | `options` | `list`             | (optional) Select filter options list            |
  | `db`      | `string`           | (optional) Dynamic select filter database        |
  | `query`   | `string`           | (optional) Dynamic select filter query           |

  Common chart properties for all chart types:

  | Property  | Type     | Description                                                                           |
  | --------- | -------- | ------------------------------------------------------------------------------------- |
  | `title`   | `string` | Chart title                                                                           |
  | `db`      | `string` | Database name against which to run the query                                          |
  | `query`   | `string` | SQL query to run and extract data from                                                |
  | `library` | `string` | One of supported libraries: `vega`, `vega-lite`, `markdown`, `metric`, `table`, `map` |
  | `display` | `object` | Chart display specification (depend on the used library)                              |

  To define SQL queries using dashboard filters:

  ```sql
  SELECT * FROM mytable [[ WHERE col >= :my_filter ]]
  ```

  ```sql
  SELECT * FROM mytable WHERE TRUE [[ AND col1 = :my_filter_1 ]] [[ AND col2 = :my_filter_2 ]]
  ```

  **Important notes:**

  - When a `select` filter has more than 100 options, the dropdown list will be automatically converted to a text filter with autocompletion

  #### Vega properties

  Available configuration for `vega` charts:

  | Property  | Type     | Description               |
  | --------- | -------- | ------------------------- |
  | `library` | `string` | Must be set to `vega`     |
  | `display` | `object` | Vega specification object |

  Notes about the `display` property:

  - Requires a valid [Vega specification object](https://vega.github.io/vega/docs/)
  - Some fields are pre-defined: `$schema`, `description`, `autosize`, `data`, `signals`
  - All fields are passed along as-is (overriding pre-defined fields if any)
  - Only `mark` and `encoding` fields are required as the bare-minimum

  #### Vega-Lite properties

  Available configuration for `vega-lite` charts:

  | Property  | Type     | Description                |
  | --------- | -------- | -------------------------- |
  | `library` | `string` | Must be set to `vega-lite` |
  | `display` | `object` | Vega specification object  |

  Notes about the `display` property:

  - Requires a valid [Vega-Lite specification object](https://vega.github.io/vega-lite/docs/)
  - Some fields are pre-defined: `$schema`, `description`, `width`, `view`, `config`, `data`
  - All fields are passed along as-is (overriding pre-defined fields if any)
  - Only `mark` and `encoding` fields are required as the bare-minimum

  #### Markdown properties

  Available configuration for `markdown` chart:

  | Property  | Type     | Description                                       |
  | --------- | -------- | ------------------------------------------------- |
  | `library` | `string` | Must be set to `markdown`                         |
  | `display` | `string` | Multi-line string containing the Markdown content |

  Note :

  - Some common properties do not apply and can be omitted: `title`, `db`, `query`
  - Markdown rendering is done by [`datasette-render-markdown`](https://datasette.io/plugins/datasette-render-markdown)
  - To configure Markdown rendering, extensions can be enabled in [metadata](https://datasette.io/plugins/datasette-render-markdown#user-content-markdown-extensions)

  #### Metric properties

  Available configuration for `metric` chart:

  | Property         | Type     | Description                               |
  | ---------------- | -------- | ----------------------------------------- |
  | `library`        | `string` | Must be set to `metric`                   |
  | `display.field`  | `string` | Numerical field to be displayed as metric |
  | `display.prefix` | `string` | Prefix to be displayed before metric      |
  | `display.suffix` | `string` | Prefix to be displayed after metric       |

  Note:

  - The `display.field` must reference a single-numerical value from the SQL query
    (e.g. numerical `number` field in `SELECT count(*) as number FROM events`)

  #### Table properties

  There is no required configured in `display`, so you can either ignored or
  leave it empty for table charts.

  Some advice for a nice table chart:

  - Set proper column names in the `SELECT` clause
  - Limit the number of columns in the `SELECT` clause
  - Limit the number of rows with the `LIMIT` clause
  - Order the rows explicitely with the `ORDER BY` clause
  - Use SQLite string concatenation operator (`||`) to format column data (for instance to include HTML markup!)

  #### Map properties

  Available configuration for `map` chart:

  | Property                    | Type      | Description                                                                         |
  | --------------------------- | --------- | ----------------------------------------------------------------------------------- |
  | `library`                   | `string`  | Must be set to `map`                                                                |
  | `display.latitude_column`   | `string`  | Name of the latitude column (default: `latitude`)                                   |
  | `display.longitude_column`  | `string`  | Name of the latitude column (default: `longitude`)                                  |
  | `display.show_latlng_popup` | `boolean` | Whether or not to display latitude and longitude values in popup (default: `false`) |

  **Warning**: do not try to load more than a thousand rows for a map at the risk of
  slugginess and being unreadable. Make sensible use of the `LIMIT` clause to reduce
  the number of items to display on the map.

  ### Dashboard layout

  The default dashboard layout will present two charts per row (one per row on mobile).
  To make use of custom dashboard layout using [CSS Grid Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout),
  define the `layout` array property as a grid / matrix:

  - Each entry represents a row of charts
  - Each column is referring a chart by its property name
  - An empty slot in the grid can be specified using the `.` (_full stop_) placeholder

  **WARNINGS**:
  - All rows must specify the same number of columns
  - All charts must be placed somewhere on the custom layout

  Here is a simple 2x3 grid example with 4 different charts:

  ```yaml
  layout:
    - [chart1, chart2, chart3]
    - [chart1, chart4, chart4]
  ```

  Here is a more subtle example involving an empty spot at the end of the second row:

  ```yaml
  layout:
    - [chart1, chart2, chart3]
    - [chart1, chart4, .]
  ```

  ### Embedding dashboards and charts

  Dashboards can be embedded within an HTML page using an `iframe` element:

  ```html
  <iframe
    src="/-/dashboards/my-dashboard/embed?start_date=2023-01-01&end_date=2023-12-31"
    frameborder="0"
    width="100%"
    height="600"
    allowtransparency
  >
  </iframe>
  ```

  Same goes for charts:

  ```html
  <iframe
    src="/-/dashboards/my-dashboard/my-chart/embed?start_date=2023-01-01&end_date=2023-12-31"
    frameborder="0"
    width="100%"
    height="600"
    allowtransparency
  >
  </iframe>
  ```

  ## Development

  To set up this plugin locally, first checkout the code.
  Then create a new virtual environment and the required dependencies:

  ```bash
  poetry install
  poetry shell
  ```

  To run the QA suite:

  ```bash
  black --check datasette_dashboards tests
  flake8 datasette_dashboards tests
  mypy datasette_dashboards tests
  pytest -v --cov=datasette_dashboards --cov=tests --cov-branch --cov-report=term-missing tests
  ```

  ## Updating JS dependencies

  External JS dependencies are tracked and bundled using NPM and `package.json` (`package-lock.json` is not needed here):

  ```bash
  npm install --no-package-lock
  ```

  ## Demo

  With the developmnent environment setup, you can run the demo locally:

  ```bash
  datasette \
    --metadata demo/metadata.yml \
    --template-dir demo/templates \
    demo/jobs.db
  ```

  ## License

  Licensed under Apache License, Version 2.0

  Copyright (c) 2021 - present Romain Clement

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-dashboards</h1><a id="user-content-datasette-dashboards" class="anchor" aria-label="Permalink: datasette-dashboards" href="#datasette-dashboards"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <blockquote>
  <p dir="auto">Datasette plugin providing data dashboards from metadata</p>
  </blockquote>
  <p dir="auto"><a href="https://pypi.org/project/datasette-dashboards/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-dashboards.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/1d2cb25117bd2c3357b55b9b4ff8a2d4d91b937bf5662d5dee0fd8b662a67953/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d64617368626f617264732e737667"></a>
  <a href="https://github.com/rclement/datasette-dashboards/actions/workflows/ci-cd.yml"><img src="https://github.com/rclement/datasette-dashboards/actions/workflows/ci-cd.yml/badge.svg" alt="CI/CD" style="max-width: 100%;"></a>
  <a href="https://codecov.io/gh/rclement/datasette-dashboards" rel="nofollow"><img src="https://img.shields.io/codecov/c/github/rclement/datasette-dashboards" alt="Coverage Status" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/d2e4654a69233d5dd3fdadc7927a3eb94bb0c6383e22d3c09008d5e383516fed/68747470733a2f2f696d672e736869656c64732e696f2f636f6465636f762f632f6769746875622f72636c656d656e742f6461746173657474652d64617368626f61726473"></a>
  <a href="https://github.com/rclement/datasette-dashboards/blob/master/LICENSE"><img src="https://img.shields.io/github/license/rclement/datasette-dashboards" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/7421741095dde43a76368f96e54864d1990e04d83639ddc245bb9573ae8d1b7a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f72636c656d656e742f6461746173657474652d64617368626f61726473"></a></p>
  <p dir="auto">Try out a live demo at <a href="https://datasette-dashboards-demo.vercel.app/-/dashboards" rel="nofollow">https://datasette-dashboards-demo.vercel.app</a></p>
  <p dir="auto"><strong>WARNING</strong>: this plugin is still experimental and not ready for production.
  Some breaking changes might happen between releases before reaching a stable version.
  Use it at your own risks!</p>
  <p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/rclement/datasette-dashboards/master/demo/datasette-dashboards-demo.png"><img src="https://raw.githubusercontent.com/rclement/datasette-dashboards/master/demo/datasette-dashboards-demo.png" alt="Datasette Dashboards Demo" style="max-width: 100%;"></a></p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="$ datasette install datasette-dashboards"><pre>$ datasette install datasette-dashboards</pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Define dashboards within <code>metadata.yml</code> / <code>metadata.json</code>:</p>
  <div class="highlight highlight-source-yaml notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="plugins:
    datasette-dashboards:
      my-dashboard:
        title: My Dashboard
        description: Showing some nice metrics
        layout:
          - [analysis-note, events-count]
          - [analysis-note, events-source]
        filters:
          date_start:
            name: Date Start
            type: date
            default: "2021-01-01"
          date_end:
            name: Date End
            type: date
          category:
            name: My Category
            type: select
            options: [Option 1, Option 2, Option 3]
          dynamic_category:
            name: My Dynamic Category
            type: select
            db: jobs
            query: SELECT region FROM jobs ORDER BY region ASC
        charts:
          analysis-note:
            library: markdown
            display: |-
              # Analysis notes
              > A quick rundown of events statistics and KPIs

          events-count:
            title: Total number of events
            db: jobs
            query: SELECT count(*) as count FROM events
            library: metric
            display:
              field: count
              prefix:
              suffix:

          events-source:
            title: Number of events by source
            db: jobs
            query: SELECT source, count(*) as count FROM events WHERE TRUE [[ AND date >= date(:date_start) ]] [[ AND date <= date(:date_end) ]] GROUP BY source ORDER BY count DESC
            library: vega-lite
            display:
              mark: { type: arc, tooltip: true }
              encoding:
                color: { field: source, type: nominal }
                theta: { field: count, type: quantitative }"><pre><span class="pl-ent">plugins</span>:
    <span class="pl-ent">datasette-dashboards</span>:
      <span class="pl-ent">my-dashboard</span>:
        <span class="pl-ent">title</span>: <span class="pl-s">My Dashboard</span>
        <span class="pl-ent">description</span>: <span class="pl-s">Showing some nice metrics</span>
        <span class="pl-ent">layout</span>:
          - <span class="pl-s">[analysis-note, events-count]</span>
          - <span class="pl-s">[analysis-note, events-source]</span>
        <span class="pl-ent">filters</span>:
          <span class="pl-ent">date_start</span>:
            <span class="pl-ent">name</span>: <span class="pl-s">Date Start</span>
            <span class="pl-ent">type</span>: <span class="pl-s">date</span>
            <span class="pl-ent">default</span>: <span class="pl-s"><span class="pl-pds">"</span>2021-01-01<span class="pl-pds">"</span></span>
          <span class="pl-ent">date_end</span>:
            <span class="pl-ent">name</span>: <span class="pl-s">Date End</span>
            <span class="pl-ent">type</span>: <span class="pl-s">date</span>
          <span class="pl-ent">category</span>:
            <span class="pl-ent">name</span>: <span class="pl-s">My Category</span>
            <span class="pl-ent">type</span>: <span class="pl-s">select</span>
            <span class="pl-ent">options</span>: <span class="pl-s">[Option 1, Option 2, Option 3]</span>
          <span class="pl-ent">dynamic_category</span>:
            <span class="pl-ent">name</span>: <span class="pl-s">My Dynamic Category</span>
            <span class="pl-ent">type</span>: <span class="pl-s">select</span>
            <span class="pl-ent">db</span>: <span class="pl-s">jobs</span>
            <span class="pl-ent">query</span>: <span class="pl-s">SELECT region FROM jobs ORDER BY region ASC</span>
        <span class="pl-ent">charts</span>:
          <span class="pl-ent">analysis-note</span>:
            <span class="pl-ent">library</span>: <span class="pl-s">markdown</span>
            <span class="pl-ent">display</span>: <span class="pl-s">|-</span>
  <span class="pl-s">            # Analysis notes</span>
  <span class="pl-s">            > A quick rundown of events statistics and KPIs</span>
  <span class="pl-s"></span>
  <span class="pl-s"></span>        <span class="pl-ent">events-count</span>:
            <span class="pl-ent">title</span>: <span class="pl-s">Total number of events</span>
            <span class="pl-ent">db</span>: <span class="pl-s">jobs</span>
            <span class="pl-ent">query</span>: <span class="pl-s">SELECT count(*) as count FROM events</span>
            <span class="pl-ent">library</span>: <span class="pl-s">metric</span>
            <span class="pl-ent">display</span>:
              <span class="pl-ent">field</span>: <span class="pl-s">count</span>
              <span class="pl-ent">prefix</span>:
              <span class="pl-ent">suffix</span>:

          <span class="pl-ent">events-source</span>:
            <span class="pl-ent">title</span>: <span class="pl-s">Number of events by source</span>
            <span class="pl-ent">db</span>: <span class="pl-s">jobs</span>
            <span class="pl-ent">query</span>: <span class="pl-s">SELECT source, count(*) as count FROM events WHERE TRUE [[ AND date >= date(:date_start) ]] [[ AND date <= date(:date_end) ]] GROUP BY source ORDER BY count DESC</span>
            <span class="pl-ent">library</span>: <span class="pl-s">vega-lite</span>
            <span class="pl-ent">display</span>:
              <span class="pl-ent">mark</span>: <span class="pl-s">{ type: arc, tooltip: true }</span>
              <span class="pl-ent">encoding</span>:
                <span class="pl-ent">color</span>: <span class="pl-s">{ field: source, type: nominal }</span>
                <span class="pl-ent">theta</span>: <span class="pl-s">{ field: count, type: quantitative }</span></pre></div>
  <p dir="auto">A new menu entry is now available, pointing at <code>/-/dashboards</code> to access all defined dashboards.</p>
  <div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">Properties</h3><a id="user-content-properties" class="anchor" aria-label="Permalink: Properties" href="#properties"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Dashboard properties:</p>
  <markdown-accessiblity-table><table>
  <thead>
  <tr>
  <th>Property</th>
  <th>Type</th>
  <th>Description</th>
  </tr>
  </thead>
  <tbody>
  <tr>
  <td><code>title</code></td>
  <td><code>string</code></td>
  <td>Dashboard title</td>
  </tr>
  <tr>
  <td><code>description</code></td>
  <td><code>string</code></td>
  <td>Dashboard description</td>
  </tr>
  <tr>
  <td><code>settings</code></td>
  <td><code>object</code></td>
  <td>Dashboard settings</td>
  </tr>
  <tr>
  <td><code>layout</code></td>
  <td><code>array</code></td>
  <td>Dashboard layout</td>
  </tr>
  <tr>
  <td><code>filters</code></td>
  <td><code>object</code></td>
  <td>Dashboard filters</td>
  </tr>
  </tbody>
  </table></markdown-accessiblity-table>
  <p dir="auto">Dashboard settings:</p>
  <markdown-accessiblity-table><table>
  <thead>
  <tr>
  <th>Property</th>
  <th>Type</th>
  <th>Description</th>
  </tr>
  </thead>
  <tbody>
  <tr>
  <td><code>allow_fullscreen</code></td>
  <td><code>bool</code></td>
  <td>Allow dashboard to be toggled in fullscreen  (default <code>false</code>)</td>
  </tr>
  <tr>
  <td><code>autorefresh</code></td>
  <td><code>number</code></td>
  <td>Auto-refresh timeout in minutes</td>
  </tr>
  </tbody>
  </table></markdown-accessiblity-table>
  <p dir="auto">Dashboard filters:</p>
  <markdown-accessiblity-table><table>
  <thead>
  <tr>
  <th>Property</th>
  <th>Type</th>
  <th>Description</th>
  </tr>
  </thead>
  <tbody>
  <tr>
  <td><code>name</code></td>
  <td><code>string</code></td>
  <td>Filter display name</td>
  </tr>
  <tr>
  <td><code>type</code></td>
  <td><code>string</code></td>
  <td>Filter type (<code>text</code>, <code>date</code>, <code>number</code>, <code>select</code>)</td>
  </tr>
  <tr>
  <td><code>default</code></td>
  <td><code>string</code>, <code>number</code></td>
  <td>(optional) Filter default value</td>
  </tr>
  <tr>
  <td><code>min</code></td>
  <td><code>number</code></td>
  <td>(optional) Filter minimum value</td>
  </tr>
  <tr>
  <td><code>max</code></td>
  <td><code>number</code></td>
  <td>(optional) Filter maximum value</td>
  </tr>
  <tr>
  <td><code>step</code></td>
  <td><code>number</code></td>
  <td>(optional) Filter stepping value</td>
  </tr>
  <tr>
  <td><code>options</code></td>
  <td><code>list</code></td>
  <td>(optional) Select filter options list</td>
  </tr>
  <tr>
  <td><code>db</code></td>
  <td><code>string</code></td>
  <td>(optional) Dynamic select filter database</td>
  </tr>
  <tr>
  <td><code>query</code></td>
  <td><code>string</code></td>
  <td>(optional) Dynamic select filter query</td>
  </tr>
  </tbody>
  </table></markdown-accessiblity-table>
  <p dir="auto">Common chart properties for all chart types:</p>
  <markdown-accessiblity-table><table>
  <thead>
  <tr>
  <th>Property</th>
  <th>Type</th>
  <th>Description</th>
  </tr>
  </thead>
  <tbody>
  <tr>
  <td><code>title</code></td>
  <td><code>string</code></td>
  <td>Chart title</td>
  </tr>
  <tr>
  <td><code>db</code></td>
  <td><code>string</code></td>
  <td>Database name against which to run the query</td>
  </tr>
  <tr>
  <td><code>query</code></td>
  <td><code>string</code></td>
  <td>SQL query to run and extract data from</td>
  </tr>
  <tr>
  <td><code>library</code></td>
  <td><code>string</code></td>
  <td>One of supported libraries: <code>vega</code>, <code>vega-lite</code>, <code>markdown</code>, <code>metric</code>, <code>table</code>, <code>map</code></td>
  </tr>
  <tr>
  <td><code>display</code></td>
  <td><code>object</code></td>
  <td>Chart display specification (depend on the used library)</td>
  </tr>
  </tbody>
  </table></markdown-accessiblity-table>
  <p dir="auto">To define SQL queries using dashboard filters:</p>
  <div class="highlight highlight-source-sql notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="SELECT * FROM mytable [[ WHERE col >= :my_filter ]]"><pre><span class="pl-k">SELECT</span> <span class="pl-k">*</span> <span class="pl-k">FROM</span> mytable [[ <span class="pl-k">WHERE</span> col <span class="pl-k">>=</span> :my_filter ]]</pre></div>
  <div class="highlight highlight-source-sql notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="SELECT * FROM mytable WHERE TRUE [[ AND col1 = :my_filter_1 ]] [[ AND col2 = :my_filter_2 ]]"><pre><span class="pl-k">SELECT</span> <span class="pl-k">*</span> <span class="pl-k">FROM</span> mytable <span class="pl-k">WHERE</span> TRUE [[ <span class="pl-k">AND</span> col1 <span class="pl-k">=</span> :my_filter_1 ]] [[ <span class="pl-k">AND</span> col2 <span class="pl-k">=</span> :my_filter_2 ]]</pre></div>
  <p dir="auto"><strong>Important notes:</strong></p>
  <ul dir="auto">
  <li>When a <code>select</code> filter has more than 100 options, the dropdown list will be automatically converted to a text filter with autocompletion</li>
  </ul>
  <div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">Vega properties</h4><a id="user-content-vega-properties" class="anchor" aria-label="Permalink: Vega properties" href="#vega-properties"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Available configuration for <code>vega</code> charts:</p>
  <markdown-accessiblity-table><table>
  <thead>
  <tr>
  <th>Property</th>
  <th>Type</th>
  <th>Description</th>
  </tr>
  </thead>
  <tbody>
  <tr>
  <td><code>library</code></td>
  <td><code>string</code></td>
  <td>Must be set to <code>vega</code></td>
  </tr>
  <tr>
  <td><code>display</code></td>
  <td><code>object</code></td>
  <td>Vega specification object</td>
  </tr>
  </tbody>
  </table></markdown-accessiblity-table>
  <p dir="auto">Notes about the <code>display</code> property:</p>
  <ul dir="auto">
  <li>Requires a valid <a href="https://vega.github.io/vega/docs/" rel="nofollow">Vega specification object</a></li>
  <li>Some fields are pre-defined: <code>$schema</code>, <code>description</code>, <code>autosize</code>, <code>data</code>, <code>signals</code></li>
  <li>All fields are passed along as-is (overriding pre-defined fields if any)</li>
  <li>Only <code>mark</code> and <code>encoding</code> fields are required as the bare-minimum</li>
  </ul>
  <div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">Vega-Lite properties</h4><a id="user-content-vega-lite-properties" class="anchor" aria-label="Permalink: Vega-Lite properties" href="#vega-lite-properties"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Available configuration for <code>vega-lite</code> charts:</p>
  <markdown-accessiblity-table><table>
  <thead>
  <tr>
  <th>Property</th>
  <th>Type</th>
  <th>Description</th>
  </tr>
  </thead>
  <tbody>
  <tr>
  <td><code>library</code></td>
  <td><code>string</code></td>
  <td>Must be set to <code>vega-lite</code></td>
  </tr>
  <tr>
  <td><code>display</code></td>
  <td><code>object</code></td>
  <td>Vega specification object</td>
  </tr>
  </tbody>
  </table></markdown-accessiblity-table>
  <p dir="auto">Notes about the <code>display</code> property:</p>
  <ul dir="auto">
  <li>Requires a valid <a href="https://vega.github.io/vega-lite/docs/" rel="nofollow">Vega-Lite specification object</a></li>
  <li>Some fields are pre-defined: <code>$schema</code>, <code>description</code>, <code>width</code>, <code>view</code>, <code>config</code>, <code>data</code></li>
  <li>All fields are passed along as-is (overriding pre-defined fields if any)</li>
  <li>Only <code>mark</code> and <code>encoding</code> fields are required as the bare-minimum</li>
  </ul>
  <div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">Markdown properties</h4><a id="user-content-markdown-properties" class="anchor" aria-label="Permalink: Markdown properties" href="#markdown-properties"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Available configuration for <code>markdown</code> chart:</p>
  <markdown-accessiblity-table><table>
  <thead>
  <tr>
  <th>Property</th>
  <th>Type</th>
  <th>Description</th>
  </tr>
  </thead>
  <tbody>
  <tr>
  <td><code>library</code></td>
  <td><code>string</code></td>
  <td>Must be set to <code>markdown</code></td>
  </tr>
  <tr>
  <td><code>display</code></td>
  <td><code>string</code></td>
  <td>Multi-line string containing the Markdown content</td>
  </tr>
  </tbody>
  </table></markdown-accessiblity-table>
  <p dir="auto">Note :</p>
  <ul dir="auto">
  <li>Some common properties do not apply and can be omitted: <code>title</code>, <code>db</code>, <code>query</code></li>
  <li>Markdown rendering is done by <a href="https://datasette.io/plugins/datasette-render-markdown" rel="nofollow"><code>datasette-render-markdown</code></a></li>
  <li>To configure Markdown rendering, extensions can be enabled in <a href="https://datasette.io/plugins/datasette-render-markdown#user-content-markdown-extensions" rel="nofollow">metadata</a></li>
  </ul>
  <div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">Metric properties</h4><a id="user-content-metric-properties" class="anchor" aria-label="Permalink: Metric properties" href="#metric-properties"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Available configuration for <code>metric</code> chart:</p>
  <markdown-accessiblity-table><table>
  <thead>
  <tr>
  <th>Property</th>
  <th>Type</th>
  <th>Description</th>
  </tr>
  </thead>
  <tbody>
  <tr>
  <td><code>library</code></td>
  <td><code>string</code></td>
  <td>Must be set to <code>metric</code></td>
  </tr>
  <tr>
  <td><code>display.field</code></td>
  <td><code>string</code></td>
  <td>Numerical field to be displayed as metric</td>
  </tr>
  <tr>
  <td><code>display.prefix</code></td>
  <td><code>string</code></td>
  <td>Prefix to be displayed before metric</td>
  </tr>
  <tr>
  <td><code>display.suffix</code></td>
  <td><code>string</code></td>
  <td>Prefix to be displayed after metric</td>
  </tr>
  </tbody>
  </table></markdown-accessiblity-table>
  <p dir="auto">Note:</p>
  <ul dir="auto">
  <li>The <code>display.field</code> must reference a single-numerical value from the SQL query
  (e.g. numerical <code>number</code> field in <code>SELECT count(*) as number FROM events</code>)</li>
  </ul>
  <div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">Table properties</h4><a id="user-content-table-properties" class="anchor" aria-label="Permalink: Table properties" href="#table-properties"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">There is no required configured in <code>display</code>, so you can either ignored or
  leave it empty for table charts.</p>
  <p dir="auto">Some advice for a nice table chart:</p>
  <ul dir="auto">
  <li>Set proper column names in the <code>SELECT</code> clause</li>
  <li>Limit the number of columns in the <code>SELECT</code> clause</li>
  <li>Limit the number of rows with the <code>LIMIT</code> clause</li>
  <li>Order the rows explicitely with the <code>ORDER BY</code> clause</li>
  <li>Use SQLite string concatenation operator (<code>||</code>) to format column data (for instance to include HTML markup!)</li>
  </ul>
  <div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">Map properties</h4><a id="user-content-map-properties" class="anchor" aria-label="Permalink: Map properties" href="#map-properties"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Available configuration for <code>map</code> chart:</p>
  <markdown-accessiblity-table><table>
  <thead>
  <tr>
  <th>Property</th>
  <th>Type</th>
  <th>Description</th>
  </tr>
  </thead>
  <tbody>
  <tr>
  <td><code>library</code></td>
  <td><code>string</code></td>
  <td>Must be set to <code>map</code></td>
  </tr>
  <tr>
  <td><code>display.latitude_column</code></td>
  <td><code>string</code></td>
  <td>Name of the latitude column (default: <code>latitude</code>)</td>
  </tr>
  <tr>
  <td><code>display.longitude_column</code></td>
  <td><code>string</code></td>
  <td>Name of the latitude column (default: <code>longitude</code>)</td>
  </tr>
  <tr>
  <td><code>display.show_latlng_popup</code></td>
  <td><code>boolean</code></td>
  <td>Whether or not to display latitude and longitude values in popup (default: <code>false</code>)</td>
  </tr>
  </tbody>
  </table></markdown-accessiblity-table>
  <p dir="auto"><strong>Warning</strong>: do not try to load more than a thousand rows for a map at the risk of
  slugginess and being unreadable. Make sensible use of the <code>LIMIT</code> clause to reduce
  the number of items to display on the map.</p>
  <div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">Dashboard layout</h3><a id="user-content-dashboard-layout" class="anchor" aria-label="Permalink: Dashboard layout" href="#dashboard-layout"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">The default dashboard layout will present two charts per row (one per row on mobile).
  To make use of custom dashboard layout using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout" rel="nofollow">CSS Grid Layout</a>,
  define the <code>layout</code> array property as a grid / matrix:</p>
  <ul dir="auto">
  <li>Each entry represents a row of charts</li>
  <li>Each column is referring a chart by its property name</li>
  <li>An empty slot in the grid can be specified using the <code>.</code> (<em>full stop</em>) placeholder</li>
  </ul>
  <p dir="auto"><strong>WARNINGS</strong>:</p>
  <ul dir="auto">
  <li>All rows must specify the same number of columns</li>
  <li>All charts must be placed somewhere on the custom layout</li>
  </ul>
  <p dir="auto">Here is a simple 2x3 grid example with 4 different charts:</p>
  <div class="highlight highlight-source-yaml notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="layout:
    - [chart1, chart2, chart3]
    - [chart1, chart4, chart4]"><pre><span class="pl-ent">layout</span>:
    - <span class="pl-s">[chart1, chart2, chart3]</span>
    - <span class="pl-s">[chart1, chart4, chart4]</span></pre></div>
  <p dir="auto">Here is a more subtle example involving an empty spot at the end of the second row:</p>
  <div class="highlight highlight-source-yaml notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="layout:
    - [chart1, chart2, chart3]
    - [chart1, chart4, .]"><pre><span class="pl-ent">layout</span>:
    - <span class="pl-s">[chart1, chart2, chart3]</span>
    - <span class="pl-s">[chart1, chart4, .]</span></pre></div>
  <div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">Embedding dashboards and charts</h3><a id="user-content-embedding-dashboards-and-charts" class="anchor" aria-label="Permalink: Embedding dashboards and charts" href="#embedding-dashboards-and-charts"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Dashboards can be embedded within an HTML page using an <code>iframe</code> element:</p>
  <div class="highlight highlight-text-html-basic notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="<iframe
    src="/-/dashboards/my-dashboard/embed?start_date=2023-01-01&end_date=2023-12-31"
    frameborder="0"
    width="100%"
    height="600"
    allowtransparency
  >
  </iframe>"><pre><span class="pl-kos"><</span><span class="pl-ent">iframe</span>
    <span class="pl-c1">src</span>="<span class="pl-s">/-/dashboards/my-dashboard/embed?start_date=2023-01-01&end_date=2023-12-31</span>"
    <span class="pl-c1">frameborder</span>="<span class="pl-s">0</span>"
    <span class="pl-c1">width</span>="<span class="pl-s">100%</span>"
    <span class="pl-c1">height</span>="<span class="pl-s">600</span>"
    <span class="pl-c1">allowtransparency</span>
  <span class="pl-kos">></span>
  <span class="pl-kos"></</span><span class="pl-ent">iframe</span><span class="pl-kos">></span></pre></div>
  <p dir="auto">Same goes for charts:</p>
  <div class="highlight highlight-text-html-basic notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="<iframe
    src="/-/dashboards/my-dashboard/my-chart/embed?start_date=2023-01-01&end_date=2023-12-31"
    frameborder="0"
    width="100%"
    height="600"
    allowtransparency
  >
  </iframe>"><pre><span class="pl-kos"><</span><span class="pl-ent">iframe</span>
    <span class="pl-c1">src</span>="<span class="pl-s">/-/dashboards/my-dashboard/my-chart/embed?start_date=2023-01-01&end_date=2023-12-31</span>"
    <span class="pl-c1">frameborder</span>="<span class="pl-s">0</span>"
    <span class="pl-c1">width</span>="<span class="pl-s">100%</span>"
    <span class="pl-c1">height</span>="<span class="pl-s">600</span>"
    <span class="pl-c1">allowtransparency</span>
  <span class="pl-kos">></span>
  <span class="pl-kos"></</span><span class="pl-ent">iframe</span><span class="pl-kos">></span></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code.
  Then create a new virtual environment and the required dependencies:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="poetry install
  poetry shell"><pre>poetry install
  poetry shell</pre></div>
  <p dir="auto">To run the QA suite:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="black --check datasette_dashboards tests
  flake8 datasette_dashboards tests
  mypy datasette_dashboards tests
  pytest -v --cov=datasette_dashboards --cov=tests --cov-branch --cov-report=term-missing tests"><pre>black --check datasette_dashboards tests
  flake8 datasette_dashboards tests
  mypy datasette_dashboards tests
  pytest -v --cov=datasette_dashboards --cov=tests --cov-branch --cov-report=term-missing tests</pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Updating JS dependencies</h2><a id="user-content-updating-js-dependencies" class="anchor" aria-label="Permalink: Updating JS dependencies" href="#updating-js-dependencies"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">External JS dependencies are tracked and bundled using NPM and <code>package.json</code> (<code>package-lock.json</code> is not needed here):</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="npm install --no-package-lock"><pre>npm install --no-package-lock</pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Demo</h2><a id="user-content-demo" class="anchor" aria-label="Permalink: Demo" href="#demo"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">With the developmnent environment setup, you can run the demo locally:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="datasette \
    --metadata demo/metadata.yml \
    --template-dir demo/templates \
    demo/jobs.db"><pre>datasette \
    --metadata demo/metadata.yml \
    --template-dir demo/templates \
    demo/jobs.db</pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">License</h2><a id="user-content-license" class="anchor" aria-label="Permalink: License" href="#license"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Licensed under Apache License, Version 2.0</p>
  <p dir="auto">Copyright (c) 2021 - present Romain Clement</p>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzNzQ4NDYzMTE=
name: datasette-geojson
full_name: eyeseast/datasette-geojson
html_url: https://github.com/eyeseast/datasette-geojson
description: Add GeoJSON output to Datasette queries
created_at: 2021-06-08T01:33:19Z
updated_at: 2024-11-14T11:09:30Z
pushed_at: 2024-01-13T01:00:15Z
language: Python
topics: ["datasette-io", "datasette-plugin", "geojson", "gis", "sqlite"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # datasette-geojson

  [![PyPI](https://img.shields.io/pypi/v/datasette-geojson.svg)](https://pypi.org/project/datasette-geojson/)
  [![Changelog](https://img.shields.io/github/v/release/eyeseast/datasette-geojson?include_prereleases&label=changelog)](https://github.com/eyeseast/datasette-geojson/releases)
  [![Tests](https://github.com/eyeseast/datasette-geojson/workflows/Test/badge.svg)](https://github.com/eyeseast/datasette-geojson/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/eyeseast/datasette-geojson/blob/main/LICENSE)

  Add GeoJSON as an output option for datasette queries.

  ## Installation

  Install this plugin in the same environment as Datasette.

      datasette install datasette-geojson

  ## Usage

  To render GeoJSON, add a `.geojson` extension to any query URL that includes a `geometry` column. That column should be a valid [GeoJSON geometry](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1).

  For example, you might use [geojson-to-sqlite](https://pypi.org/project/geojson-to-sqlite/) or [shapefile-to-sqlite](https://pypi.org/project/shapefile-to-sqlite/) to load [neighborhood boundaries](https://bostonopendata-boston.opendata.arcgis.com/datasets/3525b0ee6e6b427f9aab5d0a1d0a1a28_0/explore) into a SQLite database.

  ```sh
  wget -O neighborhoods.geojson https://opendata.arcgis.com/datasets/3525b0ee6e6b427f9aab5d0a1d0a1a28_0.geojson
  geojson-to-sqlite boston.db neighborhoods neighborhoods.geojson --spatial-index # create a spatial index
  datasette serve boston.db --load-extension spatialite
  ```

  If you're using Spatialite, the geometry column will be in a binary format. If not, make sure the `geometry` column is a well-formed [GeoJSON geometry](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1). If you used `geojson-to-sqlite` or `shapefile-to-sqlite`, you should be all set.

  Run this query in Datasette and you'll see a link to download GeoJSON:

  ```sql
  select
    rowid,
    OBJECTID,
    Name,
    Acres,
    Neighborhood_ID,
    SqMiles,
    ShapeSTArea,
    ShapeSTLength,
    geometry
  from
    neighborhoods
  order by
    rowid
  limit
    101
  ```

  Note that the geometry column needs to be explicitly _named_ `geometry` or you won't get the option to export GeoJSON. If you want to use a different column, rename it with `AS`: `SELECT other AS geometry FROM my_table`.

  ![export geojson](img/export-options.png)

  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:

      cd datasette-geojson
      python3 -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and tests:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-geojson</h1><a id="user-content-datasette-geojson" class="anchor" aria-label="Permalink: datasette-geojson" href="#datasette-geojson"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-geojson/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-geojson.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/7b3ad0fcb3818adfc39f51e01385e67eb8439abc467ac37f82535ad63d24b80d/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d67656f6a736f6e2e737667"></a>
  <a href="https://github.com/eyeseast/datasette-geojson/releases"><img src="https://img.shields.io/github/v/release/eyeseast/datasette-geojson?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/249ccfd43dec45523593e6d6497f901dcb56f905206a2c79b228865228b8bdb5/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f65796573656173742f6461746173657474652d67656f6a736f6e3f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/eyeseast/datasette-geojson/actions?query=workflow%3ATest"><img src="https://github.com/eyeseast/datasette-geojson/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/eyeseast/datasette-geojson/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Add GeoJSON as an output option for datasette queries.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="datasette install datasette-geojson"><pre class="notranslate"><code>datasette install datasette-geojson
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To render GeoJSON, add a <code>.geojson</code> extension to any query URL that includes a <code>geometry</code> column. That column should be a valid <a href="https://datatracker.ietf.org/doc/html/rfc7946#section-3.1" rel="nofollow">GeoJSON geometry</a>.</p>
  <p dir="auto">For example, you might use <a href="https://pypi.org/project/geojson-to-sqlite/" rel="nofollow">geojson-to-sqlite</a> or <a href="https://pypi.org/project/shapefile-to-sqlite/" rel="nofollow">shapefile-to-sqlite</a> to load <a href="https://bostonopendata-boston.opendata.arcgis.com/datasets/3525b0ee6e6b427f9aab5d0a1d0a1a28_0/explore" rel="nofollow">neighborhood boundaries</a> into a SQLite database.</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="wget -O neighborhoods.geojson https://opendata.arcgis.com/datasets/3525b0ee6e6b427f9aab5d0a1d0a1a28_0.geojson
  geojson-to-sqlite boston.db neighborhoods neighborhoods.geojson --spatial-index # create a spatial index
  datasette serve boston.db --load-extension spatialite"><pre>wget -O neighborhoods.geojson https://opendata.arcgis.com/datasets/3525b0ee6e6b427f9aab5d0a1d0a1a28_0.geojson
  geojson-to-sqlite boston.db neighborhoods neighborhoods.geojson --spatial-index <span class="pl-c"><span class="pl-c">#</span> create a spatial index</span>
  datasette serve boston.db --load-extension spatialite</pre></div>
  <p dir="auto">If you're using Spatialite, the geometry column will be in a binary format. If not, make sure the <code>geometry</code> column is a well-formed <a href="https://datatracker.ietf.org/doc/html/rfc7946#section-3.1" rel="nofollow">GeoJSON geometry</a>. If you used <code>geojson-to-sqlite</code> or <code>shapefile-to-sqlite</code>, you should be all set.</p>
  <p dir="auto">Run this query in Datasette and you'll see a link to download GeoJSON:</p>
  <div class="highlight highlight-source-sql notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="select
    rowid,
    OBJECTID,
    Name,
    Acres,
    Neighborhood_ID,
    SqMiles,
    ShapeSTArea,
    ShapeSTLength,
    geometry
  from
    neighborhoods
  order by
    rowid
  limit
    101"><pre><span class="pl-k">select</span>
    rowid,
    OBJECTID,
    Name,
    Acres,
    Neighborhood_ID,
    SqMiles,
    ShapeSTArea,
    ShapeSTLength,
    geometry
  <span class="pl-k">from</span>
    neighborhoods
  <span class="pl-k">order by</span>
    rowid
  <span class="pl-k">limit</span>
    <span class="pl-c1">101</span></pre></div>
  <p dir="auto">Note that the geometry column needs to be explicitly <em>named</em> <code>geometry</code> or you won't get the option to export GeoJSON. If you want to use a different column, rename it with <code>AS</code>: <code>SELECT other AS geometry FROM my_table</code>.</p>
  <p dir="auto"><a target="_blank" rel="noopener noreferrer" href="img/export-options.png"><img src="img/export-options.png" alt="export geojson" style="max-width: 100%;"></a></p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd datasette-geojson
  python3 -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd datasette-geojson
  python3 -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzNzU1NDY2NzU=
name: datasette-placekey
full_name: simonw/datasette-placekey
html_url: https://github.com/simonw/datasette-placekey
description: SQL functions for working with placekeys
created_at: 2021-06-10T02:31:27Z
updated_at: 2021-06-22T22:13:18Z
pushed_at: 2021-06-10T02:32:42Z
homepage: https://datasette.io/plugins/datasette-placekey
language: Python
topics: ["datasette", "datasette-io", "datasette-plugin", "placekey"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # datasette-placekey

  [![PyPI](https://img.shields.io/pypi/v/datasette-placekey.svg)](https://pypi.org/project/datasette-placekey/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-placekey?include_prereleases&label=changelog)](https://github.com/simonw/datasette-placekey/releases)
  [![Tests](https://github.com/simonw/datasette-placekey/workflows/Test/badge.svg)](https://github.com/simonw/datasette-placekey/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-placekey/blob/main/LICENSE)

  SQL functions for working with [placekeys](https://www.placekey.io/).

  ## Installation

  Install this plugin in the same environment as Datasette.

      $ datasette install datasette-placekey

  ## Usage

  The following SQL functions are exposed - [documentation here](https://placekey.github.io/placekey-py/placekey.html#module-placekey.placekey).

  ```sql
  select
    geo_to_placekey(33.0896104,129.7900839),
    placekey_to_geo('@6nh-nhh-kvf'),
    placekey_to_geo_latitude('@6nh-nhh-kvf'),
    placekey_to_geo_longitude('@6nh-nhh-kvf'),
    placekey_to_h3('@6nh-nhh-kvf'),
    h3_to_placekey('8a30d94e4c87fff'),
    placekey_to_geojson('@6nh-nhh-kvf'),
    placekey_to_wkt('@6nh-nhh-kvf'),
    placekey_format_is_valid('@6nh-nhh-kvf');
  ```

  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:

      cd datasette-placekey
      python3 -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and tests:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-placekey</h1><a id="user-content-datasette-placekey" class="anchor" aria-label="Permalink: datasette-placekey" href="#datasette-placekey"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-placekey/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-placekey.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/5fa1fa6fc5594ab2a7d7cb5a340638124b4faa8d2f07741845458b32e3eac760/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d706c6163656b65792e737667"></a>
  <a href="https://github.com/simonw/datasette-placekey/releases"><img src="https://img.shields.io/github/v/release/simonw/datasette-placekey?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/dc7cd8d06392dd1a8ff3bef97e3348ddac00de153e7360169ac349c436f72a81/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f6461746173657474652d706c6163656b65793f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/datasette-placekey/actions?query=workflow%3ATest"><img src="https://github.com/simonw/datasette-placekey/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/datasette-placekey/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">SQL functions for working with <a href="https://www.placekey.io/" rel="nofollow">placekeys</a>.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ datasette install datasette-placekey"><pre class="notranslate"><code>$ datasette install datasette-placekey
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">The following SQL functions are exposed - <a href="https://placekey.github.io/placekey-py/placekey.html#module-placekey.placekey" rel="nofollow">documentation here</a>.</p>
  <div class="highlight highlight-source-sql notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="select
    geo_to_placekey(33.0896104,129.7900839),
    placekey_to_geo('@6nh-nhh-kvf'),
    placekey_to_geo_latitude('@6nh-nhh-kvf'),
    placekey_to_geo_longitude('@6nh-nhh-kvf'),
    placekey_to_h3('@6nh-nhh-kvf'),
    h3_to_placekey('8a30d94e4c87fff'),
    placekey_to_geojson('@6nh-nhh-kvf'),
    placekey_to_wkt('@6nh-nhh-kvf'),
    placekey_format_is_valid('@6nh-nhh-kvf');"><pre><span class="pl-k">select</span>
    geo_to_placekey(<span class="pl-c1">33</span>.<span class="pl-c1">0896104</span>,<span class="pl-c1">129</span>.<span class="pl-c1">7900839</span>),
    placekey_to_geo(<span class="pl-s"><span class="pl-pds">'</span>@6nh-nhh-kvf<span class="pl-pds">'</span></span>),
    placekey_to_geo_latitude(<span class="pl-s"><span class="pl-pds">'</span>@6nh-nhh-kvf<span class="pl-pds">'</span></span>),
    placekey_to_geo_longitude(<span class="pl-s"><span class="pl-pds">'</span>@6nh-nhh-kvf<span class="pl-pds">'</span></span>),
    placekey_to_h3(<span class="pl-s"><span class="pl-pds">'</span>@6nh-nhh-kvf<span class="pl-pds">'</span></span>),
    h3_to_placekey(<span class="pl-s"><span class="pl-pds">'</span>8a30d94e4c87fff<span class="pl-pds">'</span></span>),
    placekey_to_geojson(<span class="pl-s"><span class="pl-pds">'</span>@6nh-nhh-kvf<span class="pl-pds">'</span></span>),
    placekey_to_wkt(<span class="pl-s"><span class="pl-pds">'</span>@6nh-nhh-kvf<span class="pl-pds">'</span></span>),
    placekey_format_is_valid(<span class="pl-s"><span class="pl-pds">'</span>@6nh-nhh-kvf<span class="pl-pds">'</span></span>);</pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd datasette-placekey
  python3 -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd datasette-placekey
  python3 -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzODI5ODY1NjQ=
name: datasette-geojson-map
full_name: eyeseast/datasette-geojson-map
html_url: https://github.com/eyeseast/datasette-geojson-map
description: Render a map for any query with a geometry column
created_at: 2021-07-05T01:54:13Z
updated_at: 2025-03-18T21:23:02Z
pushed_at: 2024-08-10T21:47:47Z
language: Python
topics: ["datasette-io", "datasette-plugin", "geojson", "gis", "leafletjs", "mapping"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # datasette-geojson-map

  [![PyPI](https://img.shields.io/pypi/v/datasette-geojson-map.svg)](https://pypi.org/project/datasette-geojson-map/)
  [![Changelog](https://img.shields.io/github/v/release/eyeseast/datasette-geojson-map?include_prereleases&label=changelog)](https://github.com/eyeseast/datasette-geojson-map/releases)
  [![Tests](https://github.com/eyeseast/datasette-geojson-map/workflows/Test/badge.svg)](https://github.com/eyeseast/datasette-geojson-map/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/eyeseast/datasette-geojson-map/blob/main/LICENSE)

  Render a map for any query with a geometry column

  ## Installation

  Install this plugin in the same environment as Datasette.

      $ datasette install datasette-geojson-map

  ## Usage

  Start by loading a GIS file.

  For example, you might use [geojson-to-sqlite](https://pypi.org/project/geojson-to-sqlite/) or [shapefile-to-sqlite](https://pypi.org/project/shapefile-to-sqlite/) to load [neighborhood boundaries](https://bostonopendata-boston.opendata.arcgis.com/datasets/3525b0ee6e6b427f9aab5d0a1d0a1a28_0/explore) into a SQLite database.

  ```sh
  wget -O neighborhoods.geojson https://opendata.arcgis.com/datasets/3525b0ee6e6b427f9aab5d0a1d0a1a28_0.geojson
  geojson-to-sqlite boston.db neighborhoods neighborhoods.geojson
  ```

  (The command above uses Spatialite, but that's not required.)

  Start up `datasette` and navigate to the `neighborhoods` table.

  ```sh
  datasette serve boston.db

  # in another terminal tab
  open http://localhost:8001/boston/neighborhoods
  ```

  You should see a map centered on Boston with each neighborhood outlined. Clicking a boundary will bring up a popup with details on that feature.

  ![Boston neighbhorhoods map](https://raw.githubusercontent.com/eyeseast/datasette-geojson-map/main/img/boston-neighborhoods-map.png)

  This plugin relies on (and will install) [datasette-geojson](https://github.com/eyeseast/datasette-geojson). Any query that includes a `geometry` column will produce a map of the results. This also includes single row views.

  Run the included `demo` project to see it live.

  ## Configuration

  This project uses the same map configuration as [datasette-cluster-map](https://github.com/simonw/datasette-cluster-map). Here's how you would use [Stamen's terrain tiles](http://maps.stamen.com/terrain/#12/37.7706/-122.3782):

  ```yaml
  plugins:
    datasette-geojson-map:
      tile_layer: https://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.{ext}
      tile_layer_options:
        attribution: >-
          Map tiles by <a href="http://stamen.com">Stamen Design</a>, 
          under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. 
          Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, 
          under <a href="http://www.openstreetmap.org/copyright">ODbL</a>.
        subdomains: abcd
        minZoom: 1
        maxZoom: 16
        ext: jpg
  ```

  Options:

  - `tile_layer`: Use a URL template that can be passed to a [Leaflet Tilelayer](https://leafletjs.com/reference-1.7.1.html#tilelayer)
  - `tile_layer_options`: All options will be passed to the tile layer. See [Leaflet documentation](https://leafletjs.com/reference-1.7.1.html#tilelayer) for more on possible values here.

  ## Styling map features

  Map features can be styled using the [simplestyle-spec](https://github.com/mapbox/simplestyle-spec). This requires setting specific fields on returned rows. Here's an example:

  ```sql
  SELECT Name, geometry, "#ff0000" as fill, "#0000ff" as stroke, 0.2 as stroke-width,  from neighborhoods
  ```

  That will render a neighborhood map where each polygon is filled in red, outlined in blue and lines are 0.2 pixels wide.

  A more useful approach would use the `CASE` statement to color features based on data:

  ```sql
  SELECT
    Name,
    geometry,
    CASE
      Name
      WHEN "Roslindale" THEN "#ff0000"
      WHEN "Dorchester" THEN "#0000ff"
      ELSE "#dddddd"
    END fill
  FROM
    neighborhoods
  ```

  This will fill Roslindale in red, Dorchester in blue and all other neighborhoods in gray.

  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:

      cd datasette-geojson-map
      python3 -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and test dependencies:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-geojson-map</h1><a id="user-content-datasette-geojson-map" class="anchor" aria-label="Permalink: datasette-geojson-map" href="#datasette-geojson-map"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-geojson-map/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-geojson-map.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/f9ed3dd29f6c765e5ed4f479d0753ab1839e218e799eb02cb4358ce756ab41f7/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d67656f6a736f6e2d6d61702e737667"></a>
  <a href="https://github.com/eyeseast/datasette-geojson-map/releases"><img src="https://img.shields.io/github/v/release/eyeseast/datasette-geojson-map?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/dc0e4530df50c849f66381a73ce56a248a7cc79ba187c23e397074502bcf8a50/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f65796573656173742f6461746173657474652d67656f6a736f6e2d6d61703f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/eyeseast/datasette-geojson-map/actions?query=workflow%3ATest"><img src="https://github.com/eyeseast/datasette-geojson-map/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/eyeseast/datasette-geojson-map/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Render a map for any query with a geometry column</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ datasette install datasette-geojson-map"><pre class="notranslate"><code>$ datasette install datasette-geojson-map
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Start by loading a GIS file.</p>
  <p dir="auto">For example, you might use <a href="https://pypi.org/project/geojson-to-sqlite/" rel="nofollow">geojson-to-sqlite</a> or <a href="https://pypi.org/project/shapefile-to-sqlite/" rel="nofollow">shapefile-to-sqlite</a> to load <a href="https://bostonopendata-boston.opendata.arcgis.com/datasets/3525b0ee6e6b427f9aab5d0a1d0a1a28_0/explore" rel="nofollow">neighborhood boundaries</a> into a SQLite database.</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="wget -O neighborhoods.geojson https://opendata.arcgis.com/datasets/3525b0ee6e6b427f9aab5d0a1d0a1a28_0.geojson
  geojson-to-sqlite boston.db neighborhoods neighborhoods.geojson"><pre>wget -O neighborhoods.geojson https://opendata.arcgis.com/datasets/3525b0ee6e6b427f9aab5d0a1d0a1a28_0.geojson
  geojson-to-sqlite boston.db neighborhoods neighborhoods.geojson</pre></div>
  <p dir="auto">(The command above uses Spatialite, but that's not required.)</p>
  <p dir="auto">Start up <code>datasette</code> and navigate to the <code>neighborhoods</code> table.</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="datasette serve boston.db

  # in another terminal tab
  open http://localhost:8001/boston/neighborhoods"><pre>datasette serve boston.db

  <span class="pl-c"><span class="pl-c">#</span> in another terminal tab</span>
  open http://localhost:8001/boston/neighborhoods</pre></div>
  <p dir="auto">You should see a map centered on Boston with each neighborhood outlined. Clicking a boundary will bring up a popup with details on that feature.</p>
  <p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/eyeseast/datasette-geojson-map/main/img/boston-neighborhoods-map.png"><img src="https://raw.githubusercontent.com/eyeseast/datasette-geojson-map/main/img/boston-neighborhoods-map.png" alt="Boston neighbhorhoods map" style="max-width: 100%;"></a></p>
  <p dir="auto">This plugin relies on (and will install) <a href="https://github.com/eyeseast/datasette-geojson">datasette-geojson</a>. Any query that includes a <code>geometry</code> column will produce a map of the results. This also includes single row views.</p>
  <p dir="auto">Run the included <code>demo</code> project to see it live.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Configuration</h2><a id="user-content-configuration" class="anchor" aria-label="Permalink: Configuration" href="#configuration"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">This project uses the same map configuration as <a href="https://github.com/simonw/datasette-cluster-map">datasette-cluster-map</a>. Here's how you would use <a href="http://maps.stamen.com/terrain/#12/37.7706/-122.3782" rel="nofollow">Stamen's terrain tiles</a>:</p>
  <div class="highlight highlight-source-yaml notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="plugins:
    datasette-geojson-map:
      tile_layer: https://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.{ext}
      tile_layer_options:
        attribution: >-
          Map tiles by <a href="http://stamen.com">Stamen Design</a>, 
          under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. 
          Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, 
          under <a href="http://www.openstreetmap.org/copyright">ODbL</a>.
        subdomains: abcd
        minZoom: 1
        maxZoom: 16
        ext: jpg"><pre><span class="pl-ent">plugins</span>:
    <span class="pl-ent">datasette-geojson-map</span>:
      <span class="pl-ent">tile_layer</span>: <span class="pl-s">https://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.{ext}</span>
      <span class="pl-ent">tile_layer_options</span>:
        <span class="pl-ent">attribution</span>: <span class="pl-s">>-</span>
  <span class="pl-s">        Map tiles by <a href="http://stamen.com">Stamen Design</a>, </span>
  <span class="pl-s">        under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. </span>
  <span class="pl-s">        Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, </span>
  <span class="pl-s">        under <a href="http://www.openstreetmap.org/copyright">ODbL</a>.</span>
  <span class="pl-s"></span>      <span class="pl-ent">subdomains</span>: <span class="pl-s">abcd</span>
        <span class="pl-ent">minZoom</span>: <span class="pl-c1">1</span>
        <span class="pl-ent">maxZoom</span>: <span class="pl-c1">16</span>
        <span class="pl-ent">ext</span>: <span class="pl-s">jpg</span></pre></div>
  <p dir="auto">Options:</p>
  <ul dir="auto">
  <li><code>tile_layer</code>: Use a URL template that can be passed to a <a href="https://leafletjs.com/reference-1.7.1.html#tilelayer" rel="nofollow">Leaflet Tilelayer</a></li>
  <li><code>tile_layer_options</code>: All options will be passed to the tile layer. See <a href="https://leafletjs.com/reference-1.7.1.html#tilelayer" rel="nofollow">Leaflet documentation</a> for more on possible values here.</li>
  </ul>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Styling map features</h2><a id="user-content-styling-map-features" class="anchor" aria-label="Permalink: Styling map features" href="#styling-map-features"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Map features can be styled using the <a href="https://github.com/mapbox/simplestyle-spec">simplestyle-spec</a>. This requires setting specific fields on returned rows. Here's an example:</p>
  <div class="highlight highlight-source-sql notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="SELECT Name, geometry, "#ff0000" as fill, "#0000ff" as stroke, 0.2 as stroke-width,  from neighborhoods"><pre><span class="pl-k">SELECT</span> Name, geometry, <span class="pl-s"><span class="pl-pds">"</span>#ff0000<span class="pl-pds">"</span></span> <span class="pl-k">as</span> fill, <span class="pl-s"><span class="pl-pds">"</span>#0000ff<span class="pl-pds">"</span></span> <span class="pl-k">as</span> stroke, <span class="pl-c1">0</span>.<span class="pl-c1">2</span> <span class="pl-k">as</span> stroke<span class="pl-k">-</span>width,  <span class="pl-k">from</span> neighborhoods</pre></div>
  <p dir="auto">That will render a neighborhood map where each polygon is filled in red, outlined in blue and lines are 0.2 pixels wide.</p>
  <p dir="auto">A more useful approach would use the <code>CASE</code> statement to color features based on data:</p>
  <div class="highlight highlight-source-sql notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="SELECT
    Name,
    geometry,
    CASE
      Name
      WHEN "Roslindale" THEN "#ff0000"
      WHEN "Dorchester" THEN "#0000ff"
      ELSE "#dddddd"
    END fill
  FROM
    neighborhoods"><pre><span class="pl-k">SELECT</span>
    Name,
    geometry,
    CASE
      Name
      WHEN <span class="pl-s"><span class="pl-pds">"</span>Roslindale<span class="pl-pds">"</span></span> THEN <span class="pl-s"><span class="pl-pds">"</span>#ff0000<span class="pl-pds">"</span></span>
      WHEN <span class="pl-s"><span class="pl-pds">"</span>Dorchester<span class="pl-pds">"</span></span> THEN <span class="pl-s"><span class="pl-pds">"</span>#0000ff<span class="pl-pds">"</span></span>
      ELSE <span class="pl-s"><span class="pl-pds">"</span>#dddddd<span class="pl-pds">"</span></span>
    END fill
  <span class="pl-k">FROM</span>
    neighborhoods</pre></div>
  <p dir="auto">This will fill Roslindale in red, Dorchester in blue and all other neighborhoods in gray.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd datasette-geojson-map
  python3 -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd datasette-geojson-map
  python3 -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and test dependencies:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzOTA1MzU1MDA=
name: datasette-remote-metadata
full_name: simonw/datasette-remote-metadata
html_url: https://github.com/simonw/datasette-remote-metadata
description: Periodically refresh Datasette metadata from a remote URL
created_at: 2021-07-28T23:17:19Z
updated_at: 2025-03-18T21:26:55Z
pushed_at: 2024-08-05T22:53:30Z
language: Python
license: apache-2.0
topics: []
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # datasette-remote-metadata

  [![PyPI](https://img.shields.io/pypi/v/datasette-remote-metadata.svg)](https://pypi.org/project/datasette-remote-metadata/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-remote-metadata?include_prereleases&label=changelog)](https://github.com/simonw/datasette-remote-metadata/releases)
  [![Tests](https://github.com/simonw/datasette-remote-metadata/workflows/Test/badge.svg)](https://github.com/simonw/datasette-remote-metadata/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-remote-metadata/blob/main/LICENSE)

  Periodically refresh Datasette metadata from a remote URL

  ## Installation

  Install this plugin in the same environment as Datasette.

      $ datasette install datasette-remote-metadata

  ## Usage

  Add the following to your `metadata.json`:

  ```json
  {
      "plugins": {
          "datasette-remote-metadata": {
              "url": "https://example.com/remote-metadata.yml"
          }
      }
  }
  ```
  The plugin will fetch the specified metadata  from that URL at startup and combine it with any existing metadata. You can use a URL to either a JSON file or a YAML file.

  It will periodically refresh that metadata - by default every 30 seconds, unless you specify an alternative `"ttl"` value in the plugin configuration.

  ## Configuration

  Available configuration options are as follows:

  - `"url"` - the URL to retrieve remote metadata from. Can link to a JSON or a YAML file.
  - `"ttl"` - integer value in secords: how frequently should the script check for fresh metadata. Defaults to 30 seconds.
  - `"headers"` - a dictionary of additional request headers to send.
  - `"cachebust"` - if true, a random `?0.29508` value will be added to the query string of the remote metadata to bust any intermediary caches.

  This example `metadata.json` configuration refreshes every 10 seconds, uses cache busting and sends an `Authorization: Bearer xyz` header with the request:

  ```json
  {
      "plugins": {
          "datasette-remote-metadata": {
              "url": "https://example.com/remote-metadata.yml",
              "ttl": 10,
              "cachebust": true,
              "headers": {
                  "Authorization": "Bearer xyz"
              }
          }
      }
  }
  ```
  This example if you are using `metadata.yaml` for configuration:
  ```yaml
  plugins:
    datasette-remote-metadata:
      url: https://example.com/remote-metadata.yml
      ttl: 10
      cachebust: true
      headers:
        Authorization: Bearer xyz
  ```

  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:

      cd datasette-remote-metadata
      python3 -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and test dependencies:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-remote-metadata</h1><a id="user-content-datasette-remote-metadata" class="anchor" aria-label="Permalink: datasette-remote-metadata" href="#datasette-remote-metadata"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-remote-metadata/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-remote-metadata.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/a63f8c384848f96a1ef4a5f80a92bc2adf8accdf45b2fe5eee51494657bf52e5/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d72656d6f74652d6d657461646174612e737667"></a>
  <a href="https://github.com/simonw/datasette-remote-metadata/releases"><img src="https://img.shields.io/github/v/release/simonw/datasette-remote-metadata?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/8fddd48d01b9b4642cb1bc5190df405db27ee5b234c994123dc983e631dd6919/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f6461746173657474652d72656d6f74652d6d657461646174613f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/datasette-remote-metadata/actions?query=workflow%3ATest"><img src="https://github.com/simonw/datasette-remote-metadata/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/datasette-remote-metadata/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Periodically refresh Datasette metadata from a remote URL</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ datasette install datasette-remote-metadata"><pre class="notranslate"><code>$ datasette install datasette-remote-metadata
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Add the following to your <code>metadata.json</code>:</p>
  <div class="highlight highlight-source-json notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="{
      "plugins": {
          "datasette-remote-metadata": {
              "url": "https://example.com/remote-metadata.yml"
          }
      }
  }"><pre>{
      <span class="pl-ent">"plugins"</span>: {
          <span class="pl-ent">"datasette-remote-metadata"</span>: {
              <span class="pl-ent">"url"</span>: <span class="pl-s"><span class="pl-pds">"</span>https://example.com/remote-metadata.yml<span class="pl-pds">"</span></span>
          }
      }
  }</pre></div>
  <p dir="auto">The plugin will fetch the specified metadata  from that URL at startup and combine it with any existing metadata. You can use a URL to either a JSON file or a YAML file.</p>
  <p dir="auto">It will periodically refresh that metadata - by default every 30 seconds, unless you specify an alternative <code>"ttl"</code> value in the plugin configuration.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Configuration</h2><a id="user-content-configuration" class="anchor" aria-label="Permalink: Configuration" href="#configuration"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Available configuration options are as follows:</p>
  <ul dir="auto">
  <li><code>"url"</code> - the URL to retrieve remote metadata from. Can link to a JSON or a YAML file.</li>
  <li><code>"ttl"</code> - integer value in secords: how frequently should the script check for fresh metadata. Defaults to 30 seconds.</li>
  <li><code>"headers"</code> - a dictionary of additional request headers to send.</li>
  <li><code>"cachebust"</code> - if true, a random <code>?0.29508</code> value will be added to the query string of the remote metadata to bust any intermediary caches.</li>
  </ul>
  <p dir="auto">This example <code>metadata.json</code> configuration refreshes every 10 seconds, uses cache busting and sends an <code>Authorization: Bearer xyz</code> header with the request:</p>
  <div class="highlight highlight-source-json notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="{
      "plugins": {
          "datasette-remote-metadata": {
              "url": "https://example.com/remote-metadata.yml",
              "ttl": 10,
              "cachebust": true,
              "headers": {
                  "Authorization": "Bearer xyz"
              }
          }
      }
  }"><pre>{
      <span class="pl-ent">"plugins"</span>: {
          <span class="pl-ent">"datasette-remote-metadata"</span>: {
              <span class="pl-ent">"url"</span>: <span class="pl-s"><span class="pl-pds">"</span>https://example.com/remote-metadata.yml<span class="pl-pds">"</span></span>,
              <span class="pl-ent">"ttl"</span>: <span class="pl-c1">10</span>,
              <span class="pl-ent">"cachebust"</span>: <span class="pl-c1">true</span>,
              <span class="pl-ent">"headers"</span>: {
                  <span class="pl-ent">"Authorization"</span>: <span class="pl-s"><span class="pl-pds">"</span>Bearer xyz<span class="pl-pds">"</span></span>
              }
          }
      }
  }</pre></div>
  <p dir="auto">This example if you are using <code>metadata.yaml</code> for configuration:</p>
  <div class="highlight highlight-source-yaml notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="plugins:
    datasette-remote-metadata:
      url: https://example.com/remote-metadata.yml
      ttl: 10
      cachebust: true
      headers:
        Authorization: Bearer xyz"><pre><span class="pl-ent">plugins</span>:
    <span class="pl-ent">datasette-remote-metadata</span>:
      <span class="pl-ent">url</span>: <span class="pl-s">https://example.com/remote-metadata.yml</span>
      <span class="pl-ent">ttl</span>: <span class="pl-c1">10</span>
      <span class="pl-ent">cachebust</span>: <span class="pl-c1">true</span>
      <span class="pl-ent">headers</span>:
        <span class="pl-ent">Authorization</span>: <span class="pl-s">Bearer xyz</span></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd datasette-remote-metadata
  python3 -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd datasette-remote-metadata
  python3 -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and test dependencies:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzOTM5OTk1OTg=
name: datasette-pyinstrument
full_name: simonw/datasette-pyinstrument
html_url: https://github.com/simonw/datasette-pyinstrument
description: Use pyinstrument to analyze Datasette page performance
created_at: 2021-08-08T15:33:29Z
updated_at: 2022-04-27T15:06:42Z
pushed_at: 2021-08-08T15:50:52Z
language: Python
topics: []
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # datasette-pyinstrument

  [![PyPI](https://img.shields.io/pypi/v/datasette-pyinstrument.svg)](https://pypi.org/project/datasette-pyinstrument/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-pyinstrument?include_prereleases&label=changelog)](https://github.com/simonw/datasette-pyinstrument/releases)
  [![Tests](https://github.com/simonw/datasette-pyinstrument/workflows/Test/badge.svg)](https://github.com/simonw/datasette-pyinstrument/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-pyinstrument/blob/main/LICENSE)

  Use pyinstrument to analyze Datasette page performance

  ## Installation

  Install this plugin in the same environment as Datasette.

      $ datasette install datasette-pyinstrument

  ## Usage

  Once installed, adding `?_pyinstrument=1` to any URL within Datasette will replace the output of that page with the pyinstrument profiler results for it.

  ## Demo

  You can see the output of this plugin at https://latest-with-plugins.datasette.io/fixtures/sortable?_pyinstrument=1

  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:

      cd datasette-pyinstrument
      python3 -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and test dependencies:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-pyinstrument</h1><a id="user-content-datasette-pyinstrument" class="anchor" aria-label="Permalink: datasette-pyinstrument" href="#datasette-pyinstrument"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-pyinstrument/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-pyinstrument.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/8b643fb5a767ce0943732b82e77d059cd0e835ee3c61dfb3bdd19ac9988ffdf3/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d7079696e737472756d656e742e737667"></a>
  <a href="https://github.com/simonw/datasette-pyinstrument/releases"><img src="https://img.shields.io/github/v/release/simonw/datasette-pyinstrument?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/5868f551d767819879c66efcaad1b7d89bf043504786f749780dc70fd003597d/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f6461746173657474652d7079696e737472756d656e743f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/datasette-pyinstrument/actions?query=workflow%3ATest"><img src="https://github.com/simonw/datasette-pyinstrument/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/datasette-pyinstrument/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Use pyinstrument to analyze Datasette page performance</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ datasette install datasette-pyinstrument"><pre class="notranslate"><code>$ datasette install datasette-pyinstrument
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Once installed, adding <code>?_pyinstrument=1</code> to any URL within Datasette will replace the output of that page with the pyinstrument profiler results for it.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Demo</h2><a id="user-content-demo" class="anchor" aria-label="Permalink: Demo" href="#demo"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">You can see the output of this plugin at <a href="https://latest-with-plugins.datasette.io/fixtures/sortable?_pyinstrument=1" rel="nofollow">https://latest-with-plugins.datasette.io/fixtures/sortable?_pyinstrument=1</a></p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd datasette-pyinstrument
  python3 -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd datasette-pyinstrument
  python3 -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and test dependencies:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzOTQxMDc2MTQ=
name: datasette-query-links
full_name: simonw/datasette-query-links
html_url: https://github.com/simonw/datasette-query-links
description: Turn SELECT queries returned by a query into links to execute them
created_at: 2021-08-09T01:16:59Z
updated_at: 2022-04-27T15:06:37Z
pushed_at: 2021-08-09T02:56:40Z
language: Python
topics: []
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # datasette-query-links

  [![PyPI](https://img.shields.io/pypi/v/datasette-query-links.svg)](https://pypi.org/project/datasette-query-links/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-query-links?include_prereleases&label=changelog)](https://github.com/simonw/datasette-query-links/releases)
  [![Tests](https://github.com/simonw/datasette-query-links/workflows/Test/badge.svg)](https://github.com/simonw/datasette-query-links/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-query-links/blob/main/LICENSE)

  Turn SELECT queries returned by a query into links to execute them

  ## Installation

  Install this plugin in the same environment as Datasette.

      $ datasette install datasette-query-links

  ## Usage

  This is an experimental plugin, requiring Datasette  0.59a1 or higher.

  Any SQL query that returns a value that itself looks like a valid SQL query will be converted into a link to execute that SQL query when it is displayed in the Datasette interface.

  These links will only show for valid SQL query - if a SQL query would return an error it will not be turned into a link.

  ## Demo

  * [Here's an example query](https://latest-with-plugins.datasette.io/fixtures?sql=select%0D%0A++%27select+*+from+%5Bfacetable%5D%27+as+query%0D%0Aunion%0D%0Aselect%0D%0A++%27select+sqlite_version()%27%0D%0Aunion%0D%0Aselect%0D%0A++%27select+this+is+invalid+SQL+so+will+not+be+linked%27) showing the plugin in action.

  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:

      cd datasette-query-links
      python3 -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and test dependencies:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-query-links</h1><a id="user-content-datasette-query-links" class="anchor" aria-label="Permalink: datasette-query-links" href="#datasette-query-links"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-query-links/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-query-links.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/7d06313119fe08973b52bf9ca11ee5d79b7feaeb81c94205218b3abf060fa870/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d71756572792d6c696e6b732e737667"></a>
  <a href="https://github.com/simonw/datasette-query-links/releases"><img src="https://img.shields.io/github/v/release/simonw/datasette-query-links?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/a8cb3774c2b951d12985ae2566cdc2c8813879c5426f389db7a0382a76228372/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f6461746173657474652d71756572792d6c696e6b733f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/datasette-query-links/actions?query=workflow%3ATest"><img src="https://github.com/simonw/datasette-query-links/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/datasette-query-links/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Turn SELECT queries returned by a query into links to execute them</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ datasette install datasette-query-links"><pre class="notranslate"><code>$ datasette install datasette-query-links
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">This is an experimental plugin, requiring Datasette  0.59a1 or higher.</p>
  <p dir="auto">Any SQL query that returns a value that itself looks like a valid SQL query will be converted into a link to execute that SQL query when it is displayed in the Datasette interface.</p>
  <p dir="auto">These links will only show for valid SQL query - if a SQL query would return an error it will not be turned into a link.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Demo</h2><a id="user-content-demo" class="anchor" aria-label="Permalink: Demo" href="#demo"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <ul dir="auto">
  <li><a href="https://latest-with-plugins.datasette.io/fixtures?sql=select%0D%0A++%27select+*+from+%5Bfacetable%5D%27+as+query%0D%0Aunion%0D%0Aselect%0D%0A++%27select+sqlite_version()%27%0D%0Aunion%0D%0Aselect%0D%0A++%27select+this+is+invalid+SQL+so+will+not+be+linked%27" rel="nofollow">Here's an example query</a> showing the plugin in action.</li>
  </ul>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd datasette-query-links
  python3 -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd datasette-query-links
  python3 -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and test dependencies:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzOTUxMzc1MTM=
name: datasette-x-forwarded-host
full_name: simonw/datasette-x-forwarded-host
html_url: https://github.com/simonw/datasette-x-forwarded-host
description: Treat the X-Forwarded-Host header as the Host header
created_at: 2021-08-11T23:10:44Z
updated_at: 2022-04-27T15:06:31Z
pushed_at: 2021-11-12T20:48:41Z
language: Python
topics: ["datasette-io", "datasette-plugin"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # datasette-x-forwarded-host

  [![PyPI](https://img.shields.io/pypi/v/datasette-x-forwarded-host.svg)](https://pypi.org/project/datasette-x-forwarded-host/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-x-forwarded-host?include_prereleases&label=changelog)](https://github.com/simonw/datasette-x-forwarded-host/releases)
  [![Tests](https://github.com/simonw/datasette-x-forwarded-host/workflows/Test/badge.svg)](https://github.com/simonw/datasette-x-forwarded-host/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-x-forwarded-host/blob/main/LICENSE)

  Treat the X-Forwarded-Host header as the Host header

  ## Installation

  Install this plugin in the same environment as Datasette.

      $ datasette install datasette-x-forwarded-host

  ## Usage

  Once installed, Datasette will replace the `host` header with the content of the incoming `x-forwarded-host` header.

  This helps Datasette generate links to new pages that work when hosted behind a proxy that rewrites the `host` header.

  Only use this plugin in deployment environmens where you know the `x-forwarded-host` header can be trusted!

  This has been tested on [GitHub Codespaces](https://github.com/features/codespaces) and [GitPod](https://gitpod.io/).

  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:

      cd datasette-x-forwarded-host
      python3 -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and test dependencies:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-x-forwarded-host</h1><a id="user-content-datasette-x-forwarded-host" class="anchor" aria-label="Permalink: datasette-x-forwarded-host" href="#datasette-x-forwarded-host"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-x-forwarded-host/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-x-forwarded-host.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/b60260acced3029677c9830cc11c0b5744ce5c3632ed476fdb89743460252b41/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d782d666f727761726465642d686f73742e737667"></a>
  <a href="https://github.com/simonw/datasette-x-forwarded-host/releases"><img src="https://img.shields.io/github/v/release/simonw/datasette-x-forwarded-host?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/cf330a027f677e04396fcc0d373d3413572ed912a2c3ae239224f6d969f47ba9/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f6461746173657474652d782d666f727761726465642d686f73743f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/datasette-x-forwarded-host/actions?query=workflow%3ATest"><img src="https://github.com/simonw/datasette-x-forwarded-host/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/datasette-x-forwarded-host/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Treat the X-Forwarded-Host header as the Host header</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ datasette install datasette-x-forwarded-host"><pre class="notranslate"><code>$ datasette install datasette-x-forwarded-host
  </code></pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Once installed, Datasette will replace the <code>host</code> header with the content of the incoming <code>x-forwarded-host</code> header.</p>
  <p dir="auto">This helps Datasette generate links to new pages that work when hosted behind a proxy that rewrites the <code>host</code> header.</p>
  <p dir="auto">Only use this plugin in deployment environmens where you know the <code>x-forwarded-host</code> header can be trusted!</p>
  <p dir="auto">This has been tested on <a href="https://github.com/features/codespaces">GitHub Codespaces</a> and <a href="https://gitpod.io/" rel="nofollow">GitPod</a>.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd datasette-x-forwarded-host
  python3 -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd datasette-x-forwarded-host
  python3 -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and test dependencies:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnkzOTkzMDg2MDQ=
name: datasette-app
full_name: simonw/datasette-app
html_url: https://github.com/simonw/datasette-app
description: The Datasette macOS application
created_at: 2021-08-24T02:21:37Z
updated_at: 2026-02-26T10:21:38Z
pushed_at: 2024-08-27T21:14:34Z
homepage: https://datasette.io/desktop
language: JavaScript
topics: ["datasette"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": false}
pull_request_creation_policy: all
readme:
  # Datasette Desktop

  A macOS desktop application that wraps [Datasette](https://datasette.io/). See [Building a desktop application for Datasette](https://simonwillison.net/2021/Aug/30/datasette-app/) for background on this project.

  ## Installation

  Grab the latest release from [the releases page](https://github.com/simonw/datasette-app/releases). Download `Datasette.app.zip`, uncompress it and drag `Datasette.app` to your `/Applications` folder - then double-click the icon.

  The first time you launch the app it will install the latest version of Datasette, which could take a little while. Subsequent application launches will be a lot quicker.

  ## Application features

  - Includes a full copy of Python which stays separate from any other Python versions you may have installed
  - Installs the latest Datasette release the first time it runs
  - The application can open existing SQLite database files or read CSV files into an in-memory database
  - It can also create a new, empty SQLite database file and create tables in that database by importing CSV data
  - By default the server only accepts connections from your computer, but you can use "File -> Access Control -> Anyone on my networks" to make it visible to other computers on your network (or devices on your [Tailscale](https://tailscale.com/) network).
  - Datasette plugins can be installed using the "Install Plugin" menu item

  ## How it works

  The app consists of two parts: the Electron app, and a custom Datasette plugin called [datasette-app-support](https://github.com/simonw/datasette-app-support).

  You can install a development version of the app like so:

      # Clone the repo
      git clone https://github.com/simonw/datasette-app
      cd datasette-app
      
      # Download standalone Python
      ./download-python.sh
      
      # Install Electron dependencies and start it running:
      npm install
      npm start

  When the app first starts up it will create a Python virtual environment in `~/.datasette-app/venv` and install both Datasette and the `datasette-app-support` plugin into that environment.

  To run the Electron tests:

      npm test

  The Electron tests may leave a `datasette` process running. You can find the process ID for this using:

      ps aux | grep xyz

  Then use `kill PROCESS_ID` to terminate it.

  ![datasette-app](https://user-images.githubusercontent.com/9599/131289203-18186b26-49a4-46e9-8925-b9e4745f3252.png)

  ## How to develop plugins

  You can develop new Datasette plugins directly against your installation of Datasette Desktop. The [Writing Plugins](https://docs.datasette.io/en/stable/writing_plugins.html) documentation mostly applies as-is, but the one extra thing you will need to do is to install an editable version of your plugin directly into the virtual environment used by Datasette Desktop.

  To do this, first create a new plugin in a folder called `datasette-your-new-plugin` with a `setup.py`, as described in the plugin documentation. The easiest way to do that is using the [datasette-plugin cookiecutter template](https://github.com/simonw/datasette-plugin).

  Then `cd` into that directory and run the following:

      ~/.datasette-app/venv/bin/pip install -e .

  This will install the plugin into your Datasette Desktop environment, such that any edits you make to the files in that directory will be picked up the next time the embedded Datasette server is restarted.

  You can restart the server either by quitting and restarting the Datasette Desktop application, or by enabling the Debug menu ("Datasette -> About Datasette -> Enable Debug Menu") and then using "Debug -> Restart Server".

  ## Release process

  To ship a new release, increment the version number in `package.json` and then [create a new release](https://github.com/simonw/datasette-app/releases/new) with a matching tag.

  Then [run a deploy](https://github.com/simonw/datasette.io/actions/workflows/deploy.yml) of [datasette.io](https://datasette.io/) to update the latest release link that is displayed on the [datasette.io/desktop](https://datasette.io/desktop) page.

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">Datasette Desktop</h1><a id="user-content-datasette-desktop" class="anchor" aria-label="Permalink: Datasette Desktop" href="#datasette-desktop"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">A macOS desktop application that wraps <a href="https://datasette.io/" rel="nofollow">Datasette</a>. See <a href="https://simonwillison.net/2021/Aug/30/datasette-app/" rel="nofollow">Building a desktop application for Datasette</a> for background on this project.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Grab the latest release from <a href="https://github.com/simonw/datasette-app/releases">the releases page</a>. Download <code>Datasette.app.zip</code>, uncompress it and drag <code>Datasette.app</code> to your <code>/Applications</code> folder - then double-click the icon.</p>
  <p dir="auto">The first time you launch the app it will install the latest version of Datasette, which could take a little while. Subsequent application launches will be a lot quicker.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Application features</h2><a id="user-content-application-features" class="anchor" aria-label="Permalink: Application features" href="#application-features"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <ul dir="auto">
  <li>Includes a full copy of Python which stays separate from any other Python versions you may have installed</li>
  <li>Installs the latest Datasette release the first time it runs</li>
  <li>The application can open existing SQLite database files or read CSV files into an in-memory database</li>
  <li>It can also create a new, empty SQLite database file and create tables in that database by importing CSV data</li>
  <li>By default the server only accepts connections from your computer, but you can use "File -&gt; Access Control -&gt; Anyone on my networks" to make it visible to other computers on your network (or devices on your <a href="https://tailscale.com/" rel="nofollow">Tailscale</a> network).</li>
  <li>Datasette plugins can be installed using the "Install Plugin" menu item</li>
  </ul>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">How it works</h2><a id="user-content-how-it-works" class="anchor" aria-label="Permalink: How it works" href="#how-it-works"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">The app consists of two parts: the Electron app, and a custom Datasette plugin called <a href="https://github.com/simonw/datasette-app-support">datasette-app-support</a>.</p>
  <p dir="auto">You can install a development version of the app like so:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# Clone the repo
  git clone https://github.com/simonw/datasette-app
  cd datasette-app

  # Download standalone Python
  ./download-python.sh

  # Install Electron dependencies and start it running:
  npm install
  npm start"><pre class="notranslate"><code># Clone the repo
  git clone https://github.com/simonw/datasette-app
  cd datasette-app

  # Download standalone Python
  ./download-python.sh

  # Install Electron dependencies and start it running:
  npm install
  npm start
  </code></pre></div>
  <p dir="auto">When the app first starts up it will create a Python virtual environment in <code>~/.datasette-app/venv</code> and install both Datasette and the <code>datasette-app-support</code> plugin into that environment.</p>
  <p dir="auto">To run the Electron tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="npm test"><pre class="notranslate"><code>npm test
  </code></pre></div>
  <p dir="auto">The Electron tests may leave a <code>datasette</code> process running. You can find the process ID for this using:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="ps aux | grep xyz"><pre class="notranslate"><code>ps aux | grep xyz
  </code></pre></div>
  <p dir="auto">Then use <code>kill PROCESS_ID</code> to terminate it.</p>
  <p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/9599/131289203-18186b26-49a4-46e9-8925-b9e4745f3252.png"><img src="https://user-images.githubusercontent.com/9599/131289203-18186b26-49a4-46e9-8925-b9e4745f3252.png" alt="datasette-app" style="max-width: 100%;"></a></p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">How to develop plugins</h2><a id="user-content-how-to-develop-plugins" class="anchor" aria-label="Permalink: How to develop plugins" href="#how-to-develop-plugins"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">You can develop new Datasette plugins directly against your installation of Datasette Desktop. The <a href="https://docs.datasette.io/en/stable/writing_plugins.html" rel="nofollow">Writing Plugins</a> documentation mostly applies as-is, but the one extra thing you will need to do is to install an editable version of your plugin directly into the virtual environment used by Datasette Desktop.</p>
  <p dir="auto">To do this, first create a new plugin in a folder called <code>datasette-your-new-plugin</code> with a <code>setup.py</code>, as described in the plugin documentation. The easiest way to do that is using the <a href="https://github.com/simonw/datasette-plugin">datasette-plugin cookiecutter template</a>.</p>
  <p dir="auto">Then <code>cd</code> into that directory and run the following:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="~/.datasette-app/venv/bin/pip install -e ."><pre class="notranslate"><code>~/.datasette-app/venv/bin/pip install -e .
  </code></pre></div>
  <p dir="auto">This will install the plugin into your Datasette Desktop environment, such that any edits you make to the files in that directory will be picked up the next time the embedded Datasette server is restarted.</p>
  <p dir="auto">You can restart the server either by quitting and restarting the Datasette Desktop application, or by enabling the Debug menu ("Datasette -&gt; About Datasette -&gt; Enable Debug Menu") and then using "Debug -&gt; Restart Server".</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Release process</h2><a id="user-content-release-process" class="anchor" aria-label="Permalink: Release process" href="#release-process"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To ship a new release, increment the version number in <code>package.json</code> and then <a href="https://github.com/simonw/datasette-app/releases/new">create a new release</a> with a matching tag.</p>
  <p dir="auto">Then <a href="https://github.com/simonw/datasette.io/actions/workflows/deploy.yml">run a deploy</a> of <a href="https://datasette.io/" rel="nofollow">datasette.io</a> to update the latest release link that is displayed on the <a href="https://datasette.io/desktop" rel="nofollow">datasette.io/desktop</a> page.</p>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnk0MDA2NzgzMTc=
name: datasette-verify
full_name: simonw/datasette-verify
html_url: https://github.com/simonw/datasette-verify
description: Verify that files can be opened by Datasette
created_at: 2021-08-28T01:59:12Z
updated_at: 2021-09-07T22:56:11Z
pushed_at: 2021-08-28T02:31:34Z
homepage: https://datasette.io/tools/datasette-verify
language: Python
topics: ["datasette", "datasette-io", "datasette-plugin"]
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
readme:
  # datasette-verify

  [![PyPI](https://img.shields.io/pypi/v/datasette-verify.svg)](https://pypi.org/project/datasette-verify/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-verify?include_prereleases&label=changelog)](https://github.com/simonw/datasette-verify/releases)
  [![Tests](https://github.com/simonw/datasette-verify/workflows/Test/badge.svg)](https://github.com/simonw/datasette-verify/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-verify/blob/main/LICENSE)

  Verify that SQLite files can be opened using Datasette

  ## Installation

  Install this plugin in the same environment as Datasette.

      $ datasette install datasette-verify

  This plugin depends on [Datasette 0.59a2](https://github.com/simonw/datasette/releases/tag/0.59a2) or higher, as it uses the [register_commands()](https://docs.datasette.io/en/latest/plugin_hooks.html#plugin-hook-register-commands) plugin hook.

  ## Usage

  To confirm that files can be opened by Datasette, run the following:

      datasette verify file1.db file2.db

  You can pass one or more file paths.

  The command will exit silently with a 0 exit code if the files are all valid SQLite databases that Datasette can open.

  It will exit with a 1 exit code and display an error for the first file it finds that is not valid.

  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:

      cd datasette-verify
      python3 -mvenv venv
      source venv/bin/activate

  Or if you are using `pipenv`:

      pipenv shell

  Now install the dependencies and test dependencies:

      pip install -e '.[test]'

  To run the tests:

      pytest

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-verify</h1><a id="user-content-datasette-verify" class="anchor" aria-label="Permalink: datasette-verify" href="#datasette-verify"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-verify/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-verify.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/457fd330ca73674557aa82ead8805054e9f99ecd7fcd1e6bb285810d7c469b4c/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d7665726966792e737667"></a>
  <a href="https://github.com/simonw/datasette-verify/releases"><img src="https://img.shields.io/github/v/release/simonw/datasette-verify?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/03ae678c1e62f09ac927c755f87ccfe9a2ebab09470697b8cb9be0d3842130eb/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f6461746173657474652d7665726966793f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/datasette-verify/actions?query=workflow%3ATest"><img src="https://github.com/simonw/datasette-verify/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/datasette-verify/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">Verify that SQLite files can be opened using Datasette</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="$ datasette install datasette-verify"><pre class="notranslate"><code>$ datasette install datasette-verify
  </code></pre></div>
  <p dir="auto">This plugin depends on <a href="https://github.com/simonw/datasette/releases/tag/0.59a2">Datasette 0.59a2</a> or higher, as it uses the <a href="https://docs.datasette.io/en/latest/plugin_hooks.html#plugin-hook-register-commands" rel="nofollow">register_commands()</a> plugin hook.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To confirm that files can be opened by Datasette, run the following:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="datasette verify file1.db file2.db"><pre class="notranslate"><code>datasette verify file1.db file2.db
  </code></pre></div>
  <p dir="auto">You can pass one or more file paths.</p>
  <p dir="auto">The command will exit silently with a 0 exit code if the files are all valid SQLite databases that Datasette can open.</p>
  <p dir="auto">It will exit with a 1 exit code and display an error for the first file it finds that is not valid.</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="cd datasette-verify
  python3 -mvenv venv
  source venv/bin/activate"><pre class="notranslate"><code>cd datasette-verify
  python3 -mvenv venv
  source venv/bin/activate
  </code></pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pipenv shell"><pre class="notranslate"><code>pipenv shell
  </code></pre></div>
  <p dir="auto">Now install the dependencies and test dependencies:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre class="notranslate"><code>pip install -e '.[test]'
  </code></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="pytest"><pre class="notranslate"><code>pytest
  </code></pre></div>
  </article></div>


node_id: MDEwOlJlcG9zaXRvcnk0MDY5OTY0NzI=
name: datasette-statistics
full_name: simonw/datasette-statistics
html_url: https://github.com/simonw/datasette-statistics
description: SQL statistics functions for Datasette
created_at: 2021-09-16T02:51:09Z
updated_at: 2024-08-28T17:20:27Z
pushed_at: 2023-11-30T23:34:58Z
language: Python
topics: []
visibility: public
default_branch: main
permissions: {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}
template_repository:
  {"id": 400878073, "node_id": "MDEwOlJlcG9zaXRvcnk0MDA4NzgwNzM=", "name": "datasette-plugin-template-repository", "full_name": "simonw/datasette-plugin-template-repository", "private": false, "owner": {"login": "simonw", "id": 9599, "node_id": "MDQ6VXNlcjk1OTk=", "avatar_url": "https://avatars.githubusercontent.com/u/9599?v=4", "gravatar_id": "", "url": "https://api.github.com/users/simonw", "html_url": "https://github.com/simonw", "followers_url": "https://api.github.com/users/simonw/followers", "following_url": "https://api.github.com/users/simonw/following{/other_user}", "gists_url": "https://api.github.com/users/simonw/gists{/gist_id}", "starred_url": "https://api.github.com/users/simonw/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/simonw/subscriptions", "organizations_url": "https://api.github.com/users/simonw/orgs", "repos_url": "https://api.github.com/users/simonw/repos", "events_url": "https://api.github.com/users/simonw/events{/privacy}", "received_events_url": "https://api.github.com/users/simonw/received_events", "type": "User", "user_view_type": "public", "site_admin": false}, "html_url": "https://github.com/simonw/datasette-plugin-template-repository", "description": "GitHub template repository for creating new Datasette plugins, using the simonw/datasette-plugin cookiecutter template", "fork": false, "url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository", "forks_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/forks", "keys_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/teams", "hooks_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/hooks", "issue_events_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/issues/events{/number}", "events_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/events", "assignees_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/assignees{/user}", "branches_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/branches{/branch}", "tags_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/tags", "blobs_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/git/refs{/sha}", "trees_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/statuses/{sha}", "languages_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/languages", "stargazers_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/stargazers", "contributors_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/contributors", "subscribers_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/subscribers", "subscription_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/subscription", "commits_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/commits{/sha}", "git_commits_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/git/commits{/sha}", "comments_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/comments{/number}", "issue_comment_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/issues/comments{/number}", "contents_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/contents/{+path}", "compare_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/merges", "archive_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/downloads", "issues_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/issues{/number}", "pulls_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/pulls{/number}", "milestones_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/milestones{/number}", "notifications_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/labels{/name}", "releases_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/releases{/id}", "deployments_url": "https://api.github.com/repos/simonw/datasette-plugin-template-repository/deployments", "created_at": "2021-08-28T19:50:28Z", "updated_at": "2025-02-18T20:42:29Z", "pushed_at": "2025-02-18T20:42:25Z", "git_url": "git://github.com/simonw/datasette-plugin-template-repository.git", "ssh_url": "git@github.com:simonw/datasette-plugin-template-repository.git", "clone_url": "https://github.com/simonw/datasette-plugin-template-repository.git", "svn_url": "https://github.com/simonw/datasette-plugin-template-repository", "homepage": "", "size": 24, "stargazers_count": 24, "watchers_count": 24, "language": null, "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "has_discussions": false, "forks_count": 0, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 0, "license": null, "allow_forking": true, "is_template": true, "web_commit_signoff_required": false, "topics": [], "visibility": "public", "forks": 0, "open_issues": 0, "watchers": 24, "default_branch": "main", "permissions": {"admin": false, "maintain": false, "push": false, "triage": false, "pull": true}}

readme:
  # datasette-statistics

  [![PyPI](https://img.shields.io/pypi/v/datasette-statistics.svg)](https://pypi.org/project/datasette-statistics/)
  [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-statistics?include_prereleases&label=changelog)](https://github.com/simonw/datasette-statistics/releases)
  [![Tests](https://github.com/simonw/datasette-statistics/workflows/Test/badge.svg)](https://github.com/simonw/datasette-statistics/actions?query=workflow%3ATest)
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-statistics/blob/main/LICENSE)

  SQL statistics functions for Datasette

  ## Installation

  Install this plugin in the same environment as Datasette.
  ```bash
  datasette install datasette-statistics
  ```
  ## Usage

  This plugin adds new SQL aggregate functions for use within Datasette:

  - `statistics_mean()` for calculating the [mean](https://docs.python.org/3/library/statistics.html#statistics.mean)
  - `statistics_geometric_mean()` for calculating the [geometric mean](https://docs.python.org/3/library/statistics.html#statistics.geometric_mean) (requires Python 3.8+)
  - `statistics_median()` for calculating the [median](https://docs.python.org/3/library/statistics.html#statistics.median)
  - `statistics_median_low()` for calculating the [low median](https://docs.python.org/3/library/statistics.html#statistics.median_low)
  - `statistics_median_high()` for calculating the [high median](https://docs.python.org/3/library/statistics.html#statistics.median_high)
  - `statistics_mode()` for calculating the [mode](https://docs.python.org/3/library/statistics.html#statistics.mode)
  - `statistics_stdev()` for calculating the [sample standard deviation](https://docs.python.org/3/library/statistics.html#statistics.stdev)
  - `statistics_pstdev()` for calculating the [population standard deviation](https://docs.python.org/3/library/statistics.html#statistics.pstdev)
  - `statistics_variance()` for calculating the [sample variance](https://docs.python.org/3/library/statistics.html#statistics.variance)
  - `statistics_pvariance()` for calculating the [population variance](https://docs.python.org/3/library/statistics.html#statistics.pvariance)

  These all use the implementations from the [Python statistics library](https://docs.python.org/3/library/statistics.html).

  Use them like this:
  ```sql
  select statistics_mean(numeric_column) from mytable
  ```
  ## Development

  To set up this plugin locally, first checkout the code. Then create a new virtual environment:
  ```bash
  cd datasette-statistics
  python3 -mvenv venv
  source venv/bin/activate
  ```
  Or if you are using `pipenv`:
  ```bash
  pipenv shell
  ```
  Now install the dependencies and test dependencies:
  ```bash
  pip install -e '.[test]'
  ```
  To run the tests:
  ```bash
  pytest
  ```

readme_html:
  <div id="readme" class="md" data-path="README.md"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">datasette-statistics</h1><a id="user-content-datasette-statistics" class="anchor" aria-label="Permalink: datasette-statistics" href="#datasette-statistics"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto"><a href="https://pypi.org/project/datasette-statistics/" rel="nofollow"><img src="https://img.shields.io/pypi/v/datasette-statistics.svg" alt="PyPI" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/e2a7f15ec38ebb8d3fbe3e56d87d996e13ff6800b14f5747a653977c70d49fa2/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f6461746173657474652d737461746973746963732e737667"></a>
  <a href="https://github.com/simonw/datasette-statistics/releases"><img src="https://img.shields.io/github/v/release/simonw/datasette-statistics?include_prereleases&label=changelog" alt="Changelog" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/5b4f33250caede324cf377ad3f0f0364f2f9d1e6fda9c09346cfddd6d1f4e83d/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f73696d6f6e772f6461746173657474652d737461746973746963733f696e636c7564655f70726572656c6561736573266c6162656c3d6368616e67656c6f67"></a>
  <a href="https://github.com/simonw/datasette-statistics/actions?query=workflow%3ATest"><img src="https://github.com/simonw/datasette-statistics/workflows/Test/badge.svg" alt="Tests" style="max-width: 100%;"></a>
  <a href="https://github.com/simonw/datasette-statistics/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License" style="max-width: 100%;" data-camo-src="https://camo.githubusercontent.com/c355f200ea90fddaa407b6eaab303663a669248ea3ca7b1fcf77dbe04ff5f48c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d417061636865253230322e302d626c75652e737667"></a></p>
  <p dir="auto">SQL statistics functions for Datasette</p>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Installation</h2><a id="user-content-installation" class="anchor" aria-label="Permalink: Installation" href="#installation"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">Install this plugin in the same environment as Datasette.</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="datasette install datasette-statistics"><pre>datasette install datasette-statistics</pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Usage</h2><a id="user-content-usage" class="anchor" aria-label="Permalink: Usage" href="#usage"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">This plugin adds new SQL aggregate functions for use within Datasette:</p>
  <ul dir="auto">
  <li><code>statistics_mean()</code> for calculating the <a href="https://docs.python.org/3/library/statistics.html#statistics.mean" rel="nofollow">mean</a></li>
  <li><code>statistics_geometric_mean()</code> for calculating the <a href="https://docs.python.org/3/library/statistics.html#statistics.geometric_mean" rel="nofollow">geometric mean</a> (requires Python 3.8+)</li>
  <li><code>statistics_median()</code> for calculating the <a href="https://docs.python.org/3/library/statistics.html#statistics.median" rel="nofollow">median</a></li>
  <li><code>statistics_median_low()</code> for calculating the <a href="https://docs.python.org/3/library/statistics.html#statistics.median_low" rel="nofollow">low median</a></li>
  <li><code>statistics_median_high()</code> for calculating the <a href="https://docs.python.org/3/library/statistics.html#statistics.median_high" rel="nofollow">high median</a></li>
  <li><code>statistics_mode()</code> for calculating the <a href="https://docs.python.org/3/library/statistics.html#statistics.mode" rel="nofollow">mode</a></li>
  <li><code>statistics_stdev()</code> for calculating the <a href="https://docs.python.org/3/library/statistics.html#statistics.stdev" rel="nofollow">sample standard deviation</a></li>
  <li><code>statistics_pstdev()</code> for calculating the <a href="https://docs.python.org/3/library/statistics.html#statistics.pstdev" rel="nofollow">population standard deviation</a></li>
  <li><code>statistics_variance()</code> for calculating the <a href="https://docs.python.org/3/library/statistics.html#statistics.variance" rel="nofollow">sample variance</a></li>
  <li><code>statistics_pvariance()</code> for calculating the <a href="https://docs.python.org/3/library/statistics.html#statistics.pvariance" rel="nofollow">population variance</a></li>
  </ul>
  <p dir="auto">These all use the implementations from the <a href="https://docs.python.org/3/library/statistics.html" rel="nofollow">Python statistics library</a>.</p>
  <p dir="auto">Use them like this:</p>
  <div class="highlight highlight-source-sql notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="select statistics_mean(numeric_column) from mytable"><pre><span class="pl-k">select</span> statistics_mean(numeric_column) <span class="pl-k">from</span> mytable</pre></div>
  <div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Development</h2><a id="user-content-development" class="anchor" aria-label="Permalink: Development" href="#development"><svg class="octicon octicon-link" viewbox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
  <p dir="auto">To set up this plugin locally, first checkout the code. Then create a new virtual environment:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="cd datasette-statistics
  python3 -mvenv venv
  source venv/bin/activate"><pre><span class="pl-c1">cd</span> datasette-statistics
  python3 -mvenv venv
  <span class="pl-c1">source</span> venv/bin/activate</pre></div>
  <p dir="auto">Or if you are using <code>pipenv</code>:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="pipenv shell"><pre>pipenv shell</pre></div>
  <p dir="auto">Now install the dependencies and test dependencies:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="pip install -e '.[test]'"><pre>pip install -e <span class="pl-s"><span class="pl-pds">'</span>.[test]<span class="pl-pds">'</span></span></pre></div>
  <p dir="auto">To run the tests:</p>
  <div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="pytest"><pre>pytest</pre></div>
  </article></div>


