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
6.5 KiB
(upgrade_guide)=
Upgrade guide
(upgrade_guide_v1)=
Datasette 0.X -> 1.0
This section reviews breaking changes Datasette 1.0 has when upgrading from a 0.XX version. For new features that 1.0 offers, see the {ref}changelog.
(upgrade_guide_v1_sql_queries)=
New URL for SQL queries
Prior to 1.0a14 the URL for executing a SQL query looked like this:
/databasename?sql=select+1
# Or for JSON:
/databasename.json?sql=select+1
This endpoint served two purposes: without a ?sql= it would list the tables in the database, but with that option it would return results of a query instead.
The URL for executing a SQL query now looks like this:
/databasename/-/query?sql=select+1
# Or for JSON:
/databasename/-/query.json?sql=select+1
This isn't a breaking change. API calls to the older /databasename?sql=... endpoint will redirect to the new databasename/-/query?sql=... endpoint. Upgrading to the new URL is recommended to avoid the overhead of the additional redirect.
(upgrade_guide_v1_metadata)=
Metadata changes
Metadata was completely revamped for Datasette 1.0. There are a number of related breaking changes, from the metadata.yaml file to Python APIs, that you'll need to consider when upgrading.
(upgrade_guide_v1_metadata_split)=
metadata.yaml split into datasette.yaml
Before Datasette 1.0, the metadata.yaml file became a kitchen sink if a mix of metadata, configuration, and settings. Now metadata.yaml is strictly for metadata (ex title and descriptions of database and tables, licensing info, etc). Other settings have been moved to a datasette.yml configuration file, described in {ref}configuration.
To start Datasette with both metadata and configuration files, run it like this:
datasette --metadata metadata.yaml --config datasette.yaml
# Or the shortened version:
datasette -m metadata.yml -c datasette.yml
(upgrade_guide_v1_metadata_upgrade)=
Upgrading an existing metadata.yaml file
The datasette-upgrade plugin can be used to split a Datasette 0.x.x metadata.yaml (or .json) file into separate metadata.yaml and datasette.yaml files. First, install the plugin:
datasette install datasette-upgrade
Then run it like this to produce the two new files:
datasette upgrade metadata-to-config metadata.json -m metadata.yml -c datasette.yml
Metadata "fallback" has been removed
Certain keys in metadata like license used to "fallback" up the chain of ownership.
For example, if you set an MIT to a database and a table within that database did not have a specified license, then that table would inherit an MIT license.
This behavior has been removed in Datasette 1.0. Now license fields must be placed on all items, including individual databases and tables.
(upgrade_guide_v1_metadata_removed)=
The get_metadata() plugin hook has been removed
In Datasette 0.x plugins could implement a get_metadata() plugin hook to customize how metadata was retrieved for different instances, databases and tables.
This hook could be inefficient, since some pages might load metadata for many different items (to list a large number of tables, for example) which could result in a large number of calls to potentially expensive plugin hook implementations.
As of Datasette 1.0a14 (2024-08-05), the get_metadata() hook has been deprecated:
# ❌ DEPRECATED in Datasette 1.0
@hookimpl
def get_metadata(datasette, key, database, table):
pass
Instead, plugins are encouraged to interact directly with Datasette's in-memory metadata tables in SQLite using the following methods on the {ref}internals_datasette:
- {ref}
get_instance_metadata() <datasette_get_instance_metadata>and {ref}set_instance_metadata() <datasette_set_instance_metadata> - {ref}
get_database_metadata() <datasette_get_database_metadata>and {ref}set_database_metadata() <datasette_set_database_metadata> - {ref}
get_resource_metadata() <datasette_get_resource_metadata>and {ref}set_resource_metadata() <datasette_set_resource_metadata> - {ref}
get_column_metadata() <datasette_get_column_metadata>and {ref}set_column_metadata() <datasette_set_column_metadata>
A plugin that stores or calculates its own metadata can implement the {ref}plugin_hook_startup hook to populate those items on startup, and then call those methods while it is running to persist any new metadata changes.
(upgrade_guide_v1_metadata_json_removed)=
The /metadata.json endpoint has been removed
As of Datasette 1.0a14, the root level /metadata.json endpoint has been removed. Metadata for tables will become available through currently in-development extras in a future alpha.
(upgrade_guide_v1_metadata_method_removed)=
The metadata() method on the Datasette class has been removed
As of Datasette 1.0a14, the .metadata() method on the Datasette Python API has been removed.
Instead, one should use the following methods on a Datasette class:
- {ref}
get_instance_metadata() <datasette_get_instance_metadata> - {ref}
get_database_metadata() <datasette_get_database_metadata> - {ref}
get_resource_metadata() <datasette_get_resource_metadata> - {ref}
get_column_metadata() <datasette_get_column_metadata>
:heading-offset: 1
(upgrade_guide_v1_a25)=
Datasette 1.0a25: create_token() signature change
datasette.create_token() is now an async method (previously it was synchronous). The restrict_all, restrict_database, and restrict_resource keyword arguments have been replaced by a single restrictions parameter that accepts a {ref}TokenRestrictions <TokenRestrictions> object.
Old code:
token = datasette.create_token(
actor_id="user1",
restrict_all=["view-instance", "view-table"],
restrict_database={"docs": ["view-query"]},
restrict_resource={
"docs": {
"attachments": ["insert-row", "update-row"]
}
},
)
New code:
from datasette.tokens import TokenRestrictions
token = await datasette.create_token(
actor_id="user1",
restrictions=(
TokenRestrictions()
.allow_all("view-instance")
.allow_all("view-table")
.allow_database("docs", "view-query")
.allow_resource("docs", "attachments", "insert-row")
.allow_resource("docs", "attachments", "update-row")
),
)
The datasette create-token CLI command is unchanged.