diff --git a/.github/workflows/deploy-branch-preview.yml b/.github/workflows/deploy-branch-preview.yml
new file mode 100644
index 00000000..e56d9c27
--- /dev/null
+++ b/.github/workflows/deploy-branch-preview.yml
@@ -0,0 +1,35 @@
+name: Deploy a Datasette branch preview to Vercel
+
+on:
+ workflow_dispatch:
+ inputs:
+ branch:
+ description: "Branch to deploy"
+ required: true
+ type: string
+
+jobs:
+ deploy-branch-preview:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up Python 3.11
+ uses: actions/setup-python@v6
+ with:
+ python-version: "3.11"
+ - name: Install dependencies
+ run: |
+ pip install datasette-publish-vercel
+ - name: Deploy the preview
+ env:
+ VERCEL_TOKEN: ${{ secrets.BRANCH_PREVIEW_VERCEL_TOKEN }}
+ run: |
+ export BRANCH="${{ github.event.inputs.branch }}"
+ wget https://latest.datasette.io/fixtures.db
+ datasette publish vercel fixtures.db \
+ --branch $BRANCH \
+ --project "datasette-preview-$BRANCH" \
+ --token $VERCEL_TOKEN \
+ --scope datasette \
+ --about "Preview of $BRANCH" \
+ --about_url "https://github.com/simonw/datasette/tree/$BRANCH"
diff --git a/.github/workflows/deploy-latest.yml b/.github/workflows/deploy-latest.yml
index b0640ae8..9f53b01e 100644
--- a/.github/workflows/deploy-latest.yml
+++ b/.github/workflows/deploy-latest.yml
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out datasette
- uses: actions/checkout@v6
+ uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v6
with:
@@ -24,7 +24,8 @@ jobs:
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
- python -m pip install . --group dev
+ python -m pip install -e .[test]
+ python -m pip install -e .[docs]
python -m pip install sphinx-to-sqlite==0.1a1
- name: Run tests
if: ${{ github.ref == 'refs/heads/main' }}
@@ -57,7 +58,7 @@ jobs:
db.route = "alternative-route"
' > plugins/alternative_route.py
cp fixtures.db fixtures2.db
- - name: And the counters writable stored query demo
+ - name: And the counters writable canned query demo
run: |
cat > plugins/counters.py <=0.2.2' \
--service "datasette-latest$SUFFIX" \
--secret $LATEST_DATASETTE_SECRET
diff --git a/.github/workflows/documentation-links.yml b/.github/workflows/documentation-links.yml
index b8fb8aaa..a54bd83a 100644
--- a/.github/workflows/documentation-links.yml
+++ b/.github/workflows/documentation-links.yml
@@ -1,6 +1,6 @@
name: Read the Docs Pull Request Preview
on:
- pull_request:
+ pull_request_target:
types:
- opened
diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
deleted file mode 100644
index 5275ddef..00000000
--- a/.github/workflows/playwright.yml
+++ /dev/null
@@ -1,48 +0,0 @@
-name: Playwright
-
-on:
- push:
- pull_request:
- workflow_dispatch:
-
-permissions:
- contents: read
-
-jobs:
- test:
- runs-on: ubuntu-latest
- strategy:
- fail-fast: false
- matrix:
- browser: [chromium, firefox, webkit]
- steps:
- - uses: actions/checkout@v6
- - name: Set up Python 3.14
- uses: actions/setup-python@v6
- with:
- python-version: "3.14"
- allow-prereleases: true
- cache: pip
- cache-dependency-path: pyproject.toml
- - name: Cache uv
- uses: actions/cache@v5
- with:
- path: ~/.cache/uv
- key: ${{ runner.os }}-py3.14-uv-${{ hashFiles('pyproject.toml') }}
- restore-keys: |
- ${{ runner.os }}-py3.14-uv-
- - name: Cache Playwright browsers
- uses: actions/cache@v5
- with:
- path: ~/.cache/ms-playwright/
- key: ${{ runner.os }}-playwright-${{ matrix.browser }}-${{ hashFiles('pyproject.toml') }}
- restore-keys: |
- ${{ runner.os }}-playwright-${{ matrix.browser }}-
- - name: Install uv
- run: python -m pip install uv
- - name: Install dependencies
- run: uv sync --group dev --group playwright
- - name: Install ${{ matrix.browser }}
- run: uv run --group dev --group playwright playwright install --with-deps ${{ matrix.browser }}
- - name: Run Playwright tests
- run: uv run --group dev --group playwright pytest tests/test_playwright.py --playwright --browser ${{ matrix.browser }}
diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml
index 735e14e9..77cce7d1 100644
--- a/.github/workflows/prettier.yml
+++ b/.github/workflows/prettier.yml
@@ -10,8 +10,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out repo
- uses: actions/checkout@v6
- - uses: actions/cache@v5
+ uses: actions/checkout@v4
+ - uses: actions/cache@v4
name: Configure npm caching
with:
path: ~/.npm
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 87300593..e94d0bdd 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -14,7 +14,7 @@ jobs:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
@@ -23,7 +23,7 @@ jobs:
cache-dependency-path: pyproject.toml
- name: Install dependencies
run: |
- pip install . --group dev
+ pip install -e '.[test]'
- name: Run tests
run: |
pytest
@@ -35,7 +35,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v6
with:
@@ -56,7 +56,7 @@ jobs:
needs: [deploy]
if: "!github.event.release.prerelease"
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v6
with:
@@ -65,7 +65,7 @@ jobs:
cache-dependency-path: pyproject.toml
- name: Install dependencies
run: |
- python -m pip install . --group dev
+ python -m pip install -e .[docs]
python -m pip install sphinx-to-sqlite==0.1a1
- name: Build docs.db
run: |-
@@ -92,7 +92,7 @@ jobs:
needs: [deploy]
if: "!github.event.release.prerelease"
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v4
- name: Build and push to Docker Hub
env:
DOCKER_USER: ${{ secrets.DOCKER_USER }}
diff --git a/.github/workflows/push_docker_tag.yml b/.github/workflows/push_docker_tag.yml
index e622ef4c..afe8d6b2 100644
--- a/.github/workflows/push_docker_tag.yml
+++ b/.github/workflows/push_docker_tag.yml
@@ -13,7 +13,7 @@ jobs:
deploy_docker:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v2
- name: Build and push to Docker Hub
env:
DOCKER_USER: ${{ secrets.DOCKER_USER }}
diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml
index 9a808194..7c5370ce 100644
--- a/.github/workflows/spellcheck.yml
+++ b/.github/workflows/spellcheck.yml
@@ -9,7 +9,7 @@ jobs:
spellcheck:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v6
with:
@@ -18,7 +18,7 @@ jobs:
cache-dependency-path: '**/pyproject.toml'
- name: Install dependencies
run: |
- pip install . --group dev
+ pip install -e '.[docs]'
- name: Check spelling
run: |
codespell README.md --ignore-words docs/codespell-ignore-words.txt
diff --git a/.github/workflows/stable-docs.yml b/.github/workflows/stable-docs.yml
index 59b5fbc0..3119d617 100644
--- a/.github/workflows/stable-docs.yml
+++ b/.github/workflows/stable-docs.yml
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
- uses: actions/checkout@v6
+ uses: actions/checkout@v5
with:
fetch-depth: 0 # We need all commits to find docs/ changes
- name: Set up Git user
diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml
index c514048e..8d73b64d 100644
--- a/.github/workflows/test-coverage.yml
+++ b/.github/workflows/test-coverage.yml
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out datasette
- uses: actions/checkout@v6
+ uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v6
with:
@@ -25,7 +25,7 @@ jobs:
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
- python -m pip install . --group dev
+ python -m pip install -e .[test]
python -m pip install pytest-cov
- name: Run tests
run: |-
diff --git a/.github/workflows/test-pyodide.yml b/.github/workflows/test-pyodide.yml
index 5162c47a..b490a9bf 100644
--- a/.github/workflows/test-pyodide.yml
+++ b/.github/workflows/test-pyodide.yml
@@ -12,7 +12,7 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v4
- name: Set up Python 3.10
uses: actions/setup-python@v6
with:
@@ -20,7 +20,7 @@ jobs:
cache: 'pip'
cache-dependency-path: '**/pyproject.toml'
- name: Cache Playwright browsers
- uses: actions/cache@v5
+ uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright/
key: ${{ runner.os }}-browsers
diff --git a/.github/workflows/test-sqlite-support.yml b/.github/workflows/test-sqlite-support.yml
index 23fce459..76ea138a 100644
--- a/.github/workflows/test-sqlite-support.yml
+++ b/.github/workflows/test-sqlite-support.yml
@@ -25,7 +25,7 @@ jobs:
#"3.23.1" # 2018-04-10, before UPSERT
]
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
@@ -45,7 +45,7 @@ jobs:
(cd tests && gcc ext.c -fPIC -shared -o ext.so)
- name: Install dependencies
run: |
- pip install . --group dev
+ pip install -e '.[test]'
pip freeze
- name: Run tests
run: |
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 9e47db6f..1e5e03d2 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -9,11 +9,10 @@ jobs:
test:
runs-on: ubuntu-latest
strategy:
- fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
@@ -26,7 +25,7 @@ jobs:
(cd tests && gcc ext.c -fPIC -shared -o ext.so)
- name: Install dependencies
run: |
- pip install . --group dev
+ pip install -e '.[test]'
pip freeze
- name: Run tests
run: |
@@ -34,12 +33,11 @@ jobs:
pytest -m "serial"
# And the test that exceeds a localhost HTTPS server
tests/test_datasette_https_server.sh
- - name: Black
+ - name: Install docs dependencies
run: |
- black --version
- black --check .
- - name: Ruff
- run: ruff check datasette tests
+ pip install -e '.[docs]'
+ - name: Black
+ run: black --check .
- name: Check if cog needs to be run
run: |
cog --check docs/*.rst
diff --git a/.github/workflows/tmate-mac.yml b/.github/workflows/tmate-mac.yml
index a033cd92..fcee0f21 100644
--- a/.github/workflows/tmate-mac.yml
+++ b/.github/workflows/tmate-mac.yml
@@ -10,6 +10,6 @@ jobs:
build:
runs-on: macos-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v2
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
diff --git a/.github/workflows/tmate.yml b/.github/workflows/tmate.yml
index 72af1eec..123f6c71 100644
--- a/.github/workflows/tmate.yml
+++ b/.github/workflows/tmate.yml
@@ -11,7 +11,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v2
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
env:
diff --git a/.gitignore b/.gitignore
index 8c058692..70e6bbeb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,6 @@
build-metadata.json
datasets.json
-.playwright-mcp
-
scratchpad
.vscode
@@ -10,9 +8,6 @@ scratchpad
uv.lock
data.db
-# test databases
-*.db
-
# We don't use Pipfile, so ignore them
Pipfile
Pipfile.lock
@@ -132,5 +127,3 @@ node_modules
tests/*.dylib
tests/*.so
tests/*.dll
-
-.idea
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 8b3e54aa..5b30e75a 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -1,17 +1,16 @@
version: 2
-sphinx:
- configuration: docs/conf.py
-
build:
- os: ubuntu-24.04
+ os: ubuntu-20.04
tools:
- python: "3.13"
- jobs:
- install:
- - pip install --upgrade pip
- - pip install . --group dev
+ python: "3.11"
-formats:
-- pdf
-- epub
+sphinx:
+ configuration: docs/conf.py
+
+python:
+ install:
+ - method: pip
+ path: .
+ extra_requirements:
+ - docs
diff --git a/Justfile b/Justfile
index 5fcd9afd..a47662c3 100644
--- a/Justfile
+++ b/Justfile
@@ -5,51 +5,31 @@ export DATASETTE_SECRET := "not_a_secret"
# Setup project
@init:
- uv sync
+ uv sync --extra test --extra docs
# Run pytest with supplied options
@test *options: init
uv run pytest -n auto {{options}}
-# Install Playwright browser support, Chromium by default
-@playwright-install browser="chromium":
- uv run --group playwright playwright install {{browser}}
-
-# Install all Playwright browsers used by the test suite
-@playwright-install-all:
- uv run --group playwright playwright install chromium firefox webkit
-
-# Run Playwright tests, Chromium by default
-@playwright browser="chromium" *options:
- uv run --group playwright pytest tests/test_playwright.py --playwright --browser {{browser}} {{options}}
-
-# Run Playwright tests against all supported browsers
-@playwright-all *options:
- uv run --group playwright pytest tests/test_playwright.py --playwright --browser chromium --browser firefox --browser webkit {{options}}
-
@codespell:
uv run codespell README.md --ignore-words docs/codespell-ignore-words.txt
uv run codespell docs/*.rst --ignore-words docs/codespell-ignore-words.txt
uv run codespell datasette -S datasette/static --ignore-words docs/codespell-ignore-words.txt
uv run codespell tests --ignore-words docs/codespell-ignore-words.txt
-# Run linters: black, ruff, cog
+# Run linters: black, flake8, mypy, cog
@lint: codespell
- uv run black datasette tests --check
- uv run ruff check datasette tests
- uv run cog --check README.md docs/*.rst
-
-# Apply ruff fixes
-@fix:
- uv run ruff check --fix datasette tests
+ uv run black . --check
+ uv run flake8
+ uv run --extra test cog --check README.md docs/*.rst
# Rebuild docs with cog
@cog:
- uv run cog -r README.md docs/*.rst
+ uv run --extra test cog -r README.md docs/*.rst
# Serve live docs on localhost:8000
@docs: cog blacken-docs
- uv run make -C docs livehtml
+ uv run --extra docs make -C docs livehtml
# Build docs as static HTML
@docs-build: cog blacken-docs
@@ -57,7 +37,7 @@ export DATASETTE_SECRET := "not_a_secret"
# Apply Black
@black:
- uv run black datasette tests
+ uv run black .
# Apply blacken-docs
@blacken-docs:
diff --git a/datasette/__init__.py b/datasette/__init__.py
index eb18e59e..47d2b4f6 100644
--- a/datasette/__init__.py
+++ b/datasette/__init__.py
@@ -1,7 +1,6 @@
from datasette.permissions import Permission # noqa
from datasette.version import __version_info__, __version__ # noqa
from datasette.events import Event # noqa
-from datasette.tokens import TokenHandler, TokenRestrictions # noqa
from datasette.utils.asgi import Forbidden, NotFound, Request, Response # noqa
from datasette.utils import actor_matches_allow # noqa
from datasette.views import Context # noqa
diff --git a/datasette/_pytest_plugin.py b/datasette/_pytest_plugin.py
deleted file mode 100644
index 103c616d..00000000
--- a/datasette/_pytest_plugin.py
+++ /dev/null
@@ -1,123 +0,0 @@
-"""
-Pytest plugin that automatically closes any Datasette instances constructed
-during a pytest test — both in the test body and in function-scoped
-fixtures. Instances constructed by session-, module-, class- or package-
-scoped fixtures are left alone, because other tests in the session will
-still want to use them.
-
-Registered as a pytest11 entry point in pyproject.toml so that downstream
-projects using Datasette get the same FD-safety net for their own tests.
-
-Opt out by setting ``datasette_autoclose = false`` in pytest.ini (or the
-equivalent ini file).
-"""
-
-from __future__ import annotations
-
-import contextvars
-import weakref
-
-import pytest
-
-_active_instances: contextvars.ContextVar[list | None] = contextvars.ContextVar(
- "datasette_active_instances", default=None
-)
-
-_original_init = None
-
-
-def _install_tracking():
- # datasette.app is imported lazily here rather than at module level:
- # as a pytest11 entry point this module is imported during pytest
- # startup, before pytest-cov starts measuring, so a module-level
- # import would drag in all of datasette and make every import-time
- # line in the package invisible to coverage
- global _original_init
- if _original_init is not None:
- return
- from datasette.app import Datasette
-
- _original_init = Datasette.__init__
-
- def _tracking_init(self, *args, **kwargs):
- _original_init(self, *args, **kwargs)
- instances = _active_instances.get()
- if instances is not None:
- instances.append(weakref.ref(self))
-
- Datasette.__init__ = _tracking_init
-
-
-def pytest_configure(config):
- if _enabled(config):
- _install_tracking()
-
-
-def pytest_addoption(parser):
- parser.addini(
- "datasette_autoclose",
- help=(
- "Automatically close Datasette instances created inside test "
- "bodies and function-scoped fixtures (default: true)."
- ),
- default="true",
- )
-
-
-def _enabled(config) -> bool:
- value = config.getini("datasette_autoclose")
- if isinstance(value, bool):
- return value
- return str(value).strip().lower() not in ("false", "0", "no", "off")
-
-
-@pytest.hookimpl(hookwrapper=True)
-def pytest_runtest_protocol(item, nextitem):
- """Track Datasette instances across setup, call and teardown; close at end."""
- if not _enabled(item.config):
- yield
- return
- refs: list[weakref.ref] = []
- token = _active_instances.set(refs)
- try:
- yield
- finally:
- _active_instances.reset(token)
- for ref in reversed(refs):
- ds = ref()
- if ds is None:
- continue
- try:
- ds.close()
- except Exception as e:
- item.warn(
- pytest.PytestUnraisableExceptionWarning(
- f"Error closing Datasette instance: {e!r}"
- )
- )
-
-
-@pytest.hookimpl(hookwrapper=True)
-def pytest_fixture_setup(fixturedef, request):
- """Exempt instances created by non-function-scoped fixtures.
-
- Session-, module-, class- and package-scoped fixtures produce Datasette
- instances that must survive beyond the current test — other tests in
- the session will still use them. When such a fixture creates one or
- more Datasette instances during its setup, we snapshot the tracking
- list before the fixture runs and subtract off any instances that were
- added during its setup, so they don't get closed at test teardown.
- """
- refs = _active_instances.get()
- if refs is None:
- yield
- return
- before_ids = {id(ref) for ref in refs}
- yield
- if fixturedef.scope != "function":
- new_refs = [ref for ref in refs if id(ref) not in before_ids]
- for new_ref in new_refs:
- try:
- refs.remove(new_ref)
- except ValueError:
- pass
diff --git a/datasette/app.py b/datasette/app.py
index 9c9b7de4..b9955925 100644
--- a/datasette/app.py
+++ b/datasette/app.py
@@ -1,17 +1,19 @@
from __future__ import annotations
+from asgi_csrf import Errors
import asyncio
import contextvars
-from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Sequence
+from typing import TYPE_CHECKING, Any, Dict, Iterable, List
if TYPE_CHECKING:
- from datasette.permissions import Resource
- from datasette.tokens import TokenRestrictions
+ from datasette.permissions import AllowedResource, Resource
+import asgi_csrf
import collections
import dataclasses
import datetime
import functools
import glob
+import hashlib
import httpx
import importlib.metadata
import inspect
@@ -34,44 +36,18 @@ from jinja2 import (
ChoiceLoader,
Environment,
FileSystemLoader,
- pass_context,
PrefixLoader,
)
from jinja2.environment import Template
from jinja2.exceptions import TemplateNotFound
from .events import Event
-from .column_types import SQLiteType
-from . import stored_queries, write_sql
from .views import Context
-from .views.database import (
- database_download,
- DatabaseView,
- QueryView,
-)
-from .views.table_create_alter import (
- DatabaseForeignKeyTargetsView,
- TableAlterView,
- TableCreateView,
- TableForeignKeySuggestionsView,
-)
-from .views.execute_write import ExecuteWriteAnalyzeView, ExecuteWriteView
-from .views.stored_queries import (
- QueryCreateAnalyzeView,
- QueryDeleteView,
- QueryDefinitionView,
- QueryEditView,
- GlobalQueryListView,
- QueryListView,
- QueryParametersView,
- QueryStoreView,
- QueryUpdateView,
-)
+from .views.database import database_download, DatabaseView, TableCreateView, QueryView
from .views.index import IndexView
from .views.special import (
JsonDataView,
PatternPortfolioView,
- AutocompleteDebugView,
AuthTokenView,
ApiExplorerView,
CreateTokenView,
@@ -82,18 +58,15 @@ from .views.special import (
AllowedResourcesView,
PermissionRulesView,
PermissionCheckView,
- JumpView,
+ TablesView,
InstanceSchemaView,
DatabaseSchemaView,
TableSchemaView,
)
from .views.table import (
- TableAutocompleteView,
TableInsertView,
TableUpsertView,
- TableSetColumnTypeView,
TableDropView,
- TableFragmentView,
table_view,
)
from .views.row import RowView, RowDeleteView, RowUpdateView
@@ -122,7 +95,6 @@ from .utils import (
parse_metadata,
resolve_env_secrets,
resolve_routes,
- sha256_file,
tilde_decode,
tilde_encode,
to_css_class,
@@ -145,7 +117,6 @@ from .utils.asgi import (
asgi_send_file,
asgi_send_redirect,
)
-from .csrf import CrossOriginProtectionMiddleware
from .utils.internal_db import init_internal_db, populate_schema_tables
from .utils.sqlite import (
sqlite3,
@@ -300,21 +271,12 @@ DEFAULT_NOT_SET = object()
ResourcesSQL = collections.namedtuple("ResourcesSQL", ("sql", "params"))
-def _permission_cache_key(actor, action, parent, child):
- # Key on the full serialized actor so actors differing in any field
- # (e.g. token restrictions) never share cache entries
- actor_key = (
- json.dumps(actor, sort_keys=True, default=repr) if actor is not None else None
- )
- return (actor_key, action, parent, child)
-
-
async def favicon(request, send):
await asgi_send_file(
send,
str(FAVICON_PATH),
content_type="image/png",
- headers={"Cache-Control": "max-age=3600, public"},
+ headers={"Cache-Control": "max-age=3600, immutable, public"},
)
@@ -331,57 +293,6 @@ def _to_string(value):
return json.dumps(value, default=str)
-def _template_context_json_default(value):
- if dataclasses.is_dataclass(value) and not isinstance(value, type):
- return {
- field.name: getattr(value, field.name)
- for field in dataclasses.fields(value)
- }
- return repr(value)
-
-
-@pass_context
-def _legacy_template_csrftoken(context):
- request = context.get("request")
- if request and "csrftoken" in request.scope:
- return request.scope["csrftoken"]()
- return ""
-
-
-def _resolve_static_asset_path(root_path, path):
- root = Path(root_path).resolve()
- full_path = (root / path).resolve()
- try:
- full_path.relative_to(root)
- except ValueError:
- raise ValueError("Static asset path cannot escape static root") from None
- return full_path
-
-
-# Documentation for the variables Datasette.render_template() adds to the
-# context for every page. This is part of the documented template contract:
-# keys added in render_template() must be documented here - the contract
-# tests in tests/test_template_context.py enforce this, and the docs in
-# docs/template_context.rst are generated from it.
-TEMPLATE_BASE_CONTEXT = {
- "request": "The current :ref:`Request object `, or None. Common properties include ``request.path``, ``request.args``, ``request.actor``, ``request.url_vars`` and ``request.host``.",
- "crumb_items": 'Async function returning breadcrumb navigation items for the current page. Call it with ``request=request`` plus optional ``database=`` and ``table=`` arguments; it returns a list of ``{"href": url, "label": label}`` dictionaries.',
- "urls": "Object with methods for constructing URLs within Datasette. Common methods include ``urls.instance()``, ``urls.database(database)``, ``urls.table(database, table)``, ``urls.query(database, query)``, ``urls.row(database, table, row_path)`` and ``urls.static(path)`` - see :ref:`internals_datasette_urls`.",
- "actor": "The currently authenticated actor dictionary, or None. Actors usually include an ``id`` key and may include any other keys supplied by authentication plugins.",
- "menu_links": "Async function returning links for the Datasette application menu, including links added by plugins. Each item is a link dictionary with ``href`` and ``label`` keys. See :ref:`plugin_hook_menu_links`; for page action menus that can also include JavaScript-backed buttons, see :ref:`plugin_actions`.",
- "display_actor": "Function that accepts an actor dictionary and returns the display string used in the navigation menu.",
- "show_logout": "True if the logout link should be shown in the navigation menu",
- "zip": "Python's ``zip()`` builtin, made available to template logic",
- "body_scripts": 'List of JavaScript snippets contributed by plugins using :ref:`plugin_hook_extra_body_script`. Each item is a dictionary with ``script`` containing JavaScript source and ``module`` indicating whether Datasette will wrap it in ``
diff --git a/datasette/templates/_codemirror.html b/datasette/templates/_codemirror.html
index 75c16168..c4629aeb 100644
--- a/datasette/templates/_codemirror.html
+++ b/datasette/templates/_codemirror.html
@@ -1,5 +1,5 @@
-
-
+
+
diff --git a/datasette/templates/_facet_results.html b/datasette/templates/_facet_results.html
index 570bb37e..034e9678 100644
--- a/datasette/templates/_facet_results.html
+++ b/datasette/templates/_facet_results.html
@@ -12,9 +12,9 @@
{% for facet_value in facet_info.results %}
{% if not facet_value.selected %}
- - {{ (facet_value.label | string()) or "-" }} {{ "{:,}".format(facet_value.count) }}
+ - {{ (facet_value.label | string()) or "-" }} {{ "{:,}".format(facet_value.count) }}
{% else %}
- - {{ facet_value.label or "-" }} · {{ "{:,}".format(facet_value.count) }} ✖
+ - {{ facet_value.label or "-" }} · {{ "{:,}".format(facet_value.count) }} ✖
{% endif %}
{% endfor %}
{% if facet_info.truncated %}
diff --git a/datasette/templates/_query_form_styles.html b/datasette/templates/_query_form_styles.html
deleted file mode 100644
index cf2dd42c..00000000
--- a/datasette/templates/_query_form_styles.html
+++ /dev/null
@@ -1,138 +0,0 @@
-
diff --git a/datasette/templates/_query_results.html b/datasette/templates/_query_results.html
deleted file mode 100644
index 5e1e2f72..00000000
--- a/datasette/templates/_query_results.html
+++ /dev/null
@@ -1,20 +0,0 @@
-{% if display_rows %}
-
-
-
- {% for column in columns %}| {{ column }} | {% endfor %}
-
-
-
- {% for row in display_rows %}
-
- {% for column, td in zip(columns, row) %}
- | {{ td }} |
- {% endfor %}
-
- {% endfor %}
-
-
-{% elif show_zero_results %}
- 0 results
-{% endif %}
diff --git a/datasette/templates/_sql_parameter_scripts.html b/datasette/templates/_sql_parameter_scripts.html
deleted file mode 100644
index 9b83889e..00000000
--- a/datasette/templates/_sql_parameter_scripts.html
+++ /dev/null
@@ -1,307 +0,0 @@
-
diff --git a/datasette/templates/_sql_parameter_styles.html b/datasette/templates/_sql_parameter_styles.html
deleted file mode 100644
index bc6838f5..00000000
--- a/datasette/templates/_sql_parameter_styles.html
+++ /dev/null
@@ -1,58 +0,0 @@
-
diff --git a/datasette/templates/_sql_parameters.html b/datasette/templates/_sql_parameters.html
deleted file mode 100644
index b5c1bde8..00000000
--- a/datasette/templates/_sql_parameters.html
+++ /dev/null
@@ -1,10 +0,0 @@
-{% set sql_parameter_name_prefix = sql_parameter_name_prefix|default("") %}
-
- {% if parameter_names %}
-
Parameters
- {% for parameter in parameter_names %}
- {% set parameter_id = (sql_parameter_id_prefix|default("qp")) ~ loop.index %}
-
{% if sql_parameters_allow_expand|default(false) %} {% endif %}
- {% endfor %}
- {% endif %}
-
diff --git a/datasette/templates/_table.html b/datasette/templates/_table.html
index 171b6442..a1329ba7 100644
--- a/datasette/templates/_table.html
+++ b/datasette/templates/_table.html
@@ -1,12 +1,12 @@
-{% if display_columns %}
+{% if display_rows %}
{% for column in display_columns %}
- |
+ |
{% if not column.sortable %}
{{ column.name }}
{% else %}
@@ -22,7 +22,7 @@
|
{% for row in display_rows %}
-
+
{% for cell in row %}
| {{ cell.value }} |
{% endfor %}
@@ -31,7 +31,6 @@
-{% endif %}
-{% if not display_rows %}
+{% else %}
0 records
{% endif %}
diff --git a/datasette/templates/api_explorer.html b/datasette/templates/api_explorer.html
index 4927cb8d..dc393c20 100644
--- a/datasette/templates/api_explorer.html
+++ b/datasette/templates/api_explorer.html
@@ -3,7 +3,7 @@
{% block title %}API Explorer{% endblock %}
{% block extra_head %}
-
+
{% endblock %}
{% block content %}
@@ -19,7 +19,7 @@
GET
-