datasette/tests
Simon Willison c96dc5ce26
register_token_handler() plugin hook for custom API token backends (#2650)
Closes #2649

* Add register_token_handler plugin hook for pluggable token backends

Adds a new register_token_handler hook that allows plugins to provide
custom token creation and verification backends. This enables plugins
like datasette-oauth to issue tokens without depending on specific
backend plugins like datasette-auth-tokens.

Key changes:
- New datasette/tokens.py with TokenHandler base class and SignedTokenHandler
  (the default signed-token implementation moved here)
- New register_token_handler hookspec in hookspecs.py
- Datasette.create_token() is now async and delegates to token handlers
- New Datasette.verify_token() method tries all handlers in sequence
- handler= parameter on create_token() to select a specific backend
- TokenHandler exported from datasette package for plugin use
- Fixed actor_from_request loop to await all coroutines (avoids warnings)

* Add documentation and hook test for register_token_handler

Fixes CI failures: the new hook needs a section in docs/plugin_hooks.rst
(checked by test_plugin_hooks_are_documented) and a test_hook_* function
in test_plugins.py (checked by test_plugin_hooks_have_tests).

* Register tokens module as separate default plugin

Instead of re-exporting hookimpls from default_permissions/__init__.py,
register datasette.default_permissions.tokens as its own DEFAULT_PLUGINS
entry. Cleaner and avoids confusing import-for-side-effect patterns.

* Replace restrict_x params with TokenRestrictions dataclass

Consolidates the three separate restrict_all, restrict_database, and
restrict_resource parameters into a single TokenRestrictions dataclass.
Cleaner API surface for both Datasette.create_token() and
TokenHandler.create_token().

Also clarifies docs re: default handler selection via pluggy ordering.

* Add builder methods to TokenRestrictions

Adds allow_all(), allow_database(), and allow_resource() methods that
return self for chaining. Callers no longer need to manipulate nested
dicts directly:

    restrictions = (TokenRestrictions()
        .allow_all("view-instance")
        .allow_database("mydb", "create-table")
        .allow_resource("mydb", "mytable", "insert-row"))

* docs: add 1.0a25 upgrade guide section for create_token() signature change

Ref: https://github.com/simonw/datasette/issues/2649#issuecomment-3962639393

* docs: note that create_token() is now async in upgrade guide

* docs: update internals, plugin_hooks, authentication for new token API

- internals.rst: new async create_token() signature with restrictions
  and handler params, add TokenRestrictions reference docs
- plugin_hooks.rst: show full create_token signature in TokenHandler
  example, note list returns and error cases
- authentication.rst: cross-reference TokenRestrictions from the
  restrictions section

* style: apply black formatting to token handler files

* docs: fix RST heading underline length in internals.rst

* tests: add restrictions round-trip and expiration tests for token handler

Covers allow_database/allow_resource builders, _r payload encoding,
and token_expires in verified actors. Coverage 76% -> 90%.

* tests: add test for signed tokens disabled

* fix: add TokenRestrictions TYPE_CHECKING import to fix ruff F821

* docs: regenerate plugins.rst with cog

* docs: reformat code blocks in plugin_hooks.rst with blacken-docs

* docs: add await .verify_token() to internals.rst

* tests: rewrite register_token_handler test to use real plugin handler

Adds a HardcodedTokenHandler to the test plugins dir that creates
tokens like dstok_hardcoded_token_1. The test now exercises creating
tokens via the default handler (which is the plugin's hardcoded one),
by explicitly naming the hardcoded handler, and by explicitly naming
the signed handler -- then verifies each token round-trips correctly.

* tests: clarify test_token_handler_via_http tests the default signed handler

* fix: use handler="signed" explicitly where signed tokens are expected

The HardcodedTokenHandler in my_plugin.py gets globally registered,
so create_token() without a handler name picks it up as the default.
Fix the create-token view, CLI, and tests to explicitly request the
signed handler where they depend on signed token behavior.

* fix: use handler="signed" in test_create_table_permissions

https://claude.ai/code/session_013cQFiDQjYRrRBH2biFfKuS
2026-02-25 16:32:45 -08:00
..
plugins register_token_handler() plugin hook for custom API token backends (#2650) 2026-02-25 16:32:45 -08:00
test_templates Fix handling of nested custom page wildcard paths, closes #996 2020-10-07 15:51:11 -07:00
__init__.py Broke up test_app into test_api and test_html 2017-12-15 04:08:24 -08:00
build_small_spatialite_db.py New run_sanity_checks mechanism, for SpatiLite 2019-05-11 15:55:30 -07:00
conftest.py Black formatting 2026-02-17 13:30:24 -08:00
ext.c Add new entrypoint option to --load-extensions. (#1789) 2022-08-23 11:34:30 -07:00
fixtures.py register_token_handler() plugin hook for custom API token backends (#2650) 2026-02-25 16:32:45 -08:00
spatialite.db New run_sanity_checks mechanism, for SpatiLite 2019-05-11 15:55:30 -07:00
test-datasette-load-plugins.sh fix (typo): Corrected spelling of 'environments' (#2268) 2024-02-19 14:41:32 -08:00
test_actions_sql.py datasette.pm property, closes #2595 2025-11-13 10:31:03 -08:00
test_actor_restriction_bug.py New PermissionSQL.restriction_sql mechanism for actor restrictions 2025-11-03 14:17:51 -08:00
test_allowed_resources.py Switch to ruff and fix all lint errors, refs #2630 2026-01-23 20:43:16 -08:00
test_api.py Fix test assertions broken by new fixture rows in 170f9de 2026-02-17 13:22:57 -08:00
test_api_write.py register_token_handler() plugin hook for custom API token backends (#2650) 2026-02-25 16:32:45 -08:00
test_auth.py Fix type annotation bugs and remove unused imports 2025-10-26 16:03:13 -07:00
test_base_view.py New View base class (#2080) 2023-05-25 17:18:43 -07:00
test_canned_queries.py Fix type annotation bugs and remove unused imports 2025-10-26 16:03:13 -07:00
test_cli.py Black formatting 2026-02-17 13:30:24 -08:00
test_cli_serve_get.py Black formatting 2026-02-17 13:30:24 -08:00
test_cli_serve_server.py Move HTTPS test to a bash script 2022-12-17 18:33:07 -08:00
test_config_dir.py Black formatting 2026-02-17 13:30:24 -08:00
test_config_permission_rules.py Run black formatter 2025-10-25 15:38:07 -07:00
test_crossdb.py Switch to ruff and fix all lint errors, refs #2630 2026-01-23 20:43:16 -08:00
test_csv.py Black formatting 2026-02-17 13:30:24 -08:00
test_custom_pages.py Move open redirect fix to asgi_send_redirect, refs #2429 2025-11-04 17:08:06 -08:00
test_datasette_https_server.sh Detect server start/stop more reliably. 2022-12-18 08:01:51 -08:00
test_default_deny.py datasette serve --default-deny option (#2593) 2025-11-12 16:14:21 -08:00
test_docs.py Use subtests in tests/test_docs.py (#2609) 2025-12-04 21:36:39 -08:00
test_docs_plugins.py datasette.pm property, closes #2595 2025-11-13 10:31:03 -08:00
test_facets.py Consider just 1000 rows for suggest facet, closes #2406 2024-08-21 13:36:42 -07:00
test_filters.py Switch to ruff and fix all lint errors, refs #2630 2026-01-23 20:43:16 -08:00
test_html.py Black formatting 2026-02-17 13:30:24 -08:00
test_internal_db.py Fix for stale internal database bug, closes #2605 2025-12-02 16:22:42 -08:00
test_internals_database.py Black formatting 2026-02-17 13:30:24 -08:00
test_internals_datasette.py Switch to ruff and fix all lint errors, refs #2630 2026-01-23 20:43:16 -08:00
test_internals_datasette_client.py datasette.pm property, closes #2595 2025-11-13 10:31:03 -08:00
test_internals_request.py Request.fake(... url_vars), plus .fake() is now documented 2022-03-31 19:01:58 -07:00
test_internals_response.py response.set_cookie(), closes #795 2020-06-09 15:19:37 -07:00
test_internals_urls.py Remove hashed URL mode 2022-03-18 17:12:03 -07:00
test_label_column_for_table.py Detect single unique text column in label_column_for_table, closes #2458 2025-02-01 17:02:49 -08:00
test_load_extensions.py Introduce new /$DB/-/query endpoint, soft replaces /$DB?sql=... (#2363) 2024-07-15 10:33:51 -07:00
test_messages.py Introduce new /$DB/-/query endpoint, soft replaces /$DB?sql=... (#2363) 2024-07-15 10:33:51 -07:00
test_multipart.py Add request.form() for multipart form data and file uploads 2026-01-28 18:41:03 -08:00
test_package.py Upgrade Docker images to Python 3.11, closes #1853 2022-10-25 12:04:53 -07:00
test_permission_endpoints.py datasette.pm property, closes #2595 2025-11-13 10:31:03 -08:00
test_permissions.py register_token_handler() plugin hook for custom API token backends (#2650) 2026-02-25 16:32:45 -08:00
test_plugins.py register_token_handler() plugin hook for custom API token backends (#2650) 2026-02-25 16:32:45 -08:00
test_publish_cloudrun.py Black formatting 2026-02-17 13:30:24 -08:00
test_publish_heroku.py Upgrade to Python 3.11 on Heroku, refs #1905 2022-11-18 16:44:46 -08:00
test_restriction_sql.py Switch to ruff and fix all lint errors, refs #2630 2026-01-23 20:43:16 -08:00
test_routes.py Black formatting 2026-02-17 13:30:24 -08:00
test_schema_endpoints.py Switch to ruff and fix all lint errors, refs #2630 2026-01-23 20:43:16 -08:00
test_search_tables.py Rename test_tables_endpoint.py and remove outdated tests 2025-10-25 17:32:48 -07:00
test_spatialite.py Skip SpatiaLite test if no conn.enable_load_extension() 2022-09-05 17:09:57 -07:00
test_table_api.py Black formatting 2026-02-17 13:30:24 -08:00
test_table_html.py Fix test assertions broken by new fixture rows in 170f9de 2026-02-17 13:22:57 -08:00
test_token_handler.py register_token_handler() plugin hook for custom API token backends (#2650) 2026-02-25 16:32:45 -08:00
test_tracer.py Tracer now catches errors, closes #2405 2024-08-21 12:19:18 -07:00
test_utils.py Black formatting 2026-02-17 13:30:24 -08:00
test_utils_check_callable.py Rename callable.py to check_callable.py, refs #2078 2023-05-25 11:49:40 -07:00
test_utils_permissions.py Black formatting 2026-02-17 13:30:24 -08:00
test_write_wrapper.py Fix test_write_wrapper_set_authorizer: use permissive callback instead of None 2026-02-17 13:30:46 -08:00
utils.py Test improvements and fixed deprecation warnings (#2464) 2025-02-04 14:49:52 -08:00