datasette.allowed_resources_sql() returns namedtuple

This commit is contained in:
Simon Willison 2025-10-31 15:07:37 -07:00
commit e5f392ae7a
3 changed files with 20 additions and 5 deletions

View file

@ -248,6 +248,9 @@ FAVICON_PATH = app_root / "datasette" / "static" / "favicon.png"
DEFAULT_NOT_SET = object()
ResourcesSQL = collections.namedtuple("ResourcesSQL", ("sql", "params"))
async def favicon(request, send):
await asgi_send_file(
send,
@ -1110,7 +1113,7 @@ class Datasette:
actor: dict | None = None,
parent: str | None = None,
include_is_private: bool = False,
) -> tuple[str, dict]:
) -> ResourcesSQL:
"""
Build SQL query to get all resources the actor can access for the given action.
@ -1120,7 +1123,7 @@ class Datasette:
parent: Optional parent filter (e.g., database name) to limit results
include_is_private: If True, include is_private column showing if anonymous cannot access
Returns a tuple of (query: str, params: dict) that can be executed against the internal database.
Returns a namedtuple of (query: str, params: dict) that can be executed against the internal database.
The query returns rows with (parent, child, reason) columns, plus is_private if requested.
Example:
@ -1138,9 +1141,10 @@ class Datasette:
if not action_obj:
raise ValueError(f"Unknown action: {action}")
return await build_allowed_resources_sql(
sql, params = await build_allowed_resources_sql(
self, actor, action, parent=parent, include_is_private=include_is_private
)
return ResourcesSQL(sql, params)
async def allowed_resources(
self,

View file

@ -467,7 +467,7 @@ This method uses :ref:`datasette_allowed_resources_sql` under the hood and is an
await .allowed_resources_sql(\*, action, actor=None, parent=None, include_is_private=False)
-------------------------------------------------------------------------------------------
Builds the SQL query that Datasette uses to determine which resources an actor may access for a specific action. Returns a ``(sql: str, params: dict)`` tuple that can be executed against the internal ``catalog_*`` database tables. ``parent`` can be used to limit results to a specific database, and ``include_is_private`` adds a column indicating whether anonymous users would be denied access to that resource.
Builds the SQL query that Datasette uses to determine which resources an actor may access for a specific action. Returns a ``(sql: str, params: dict)`` namedtuple that can be executed against the internal ``catalog_*`` database tables. ``parent`` can be used to limit results to a specific database, and ``include_is_private`` adds a column indicating whether anonymous users would be denied access to that resource.
Plugins that need to execute custom analysis over the raw allow/deny rules can use this helper to run the same query that powers the ``/-/allowed`` debugging interface.

View file

@ -4,7 +4,7 @@ Tests for the datasette.app.Datasette class
import dataclasses
from datasette import Context
from datasette.app import Datasette, Database
from datasette.app import Datasette, Database, ResourcesSQL
from datasette.resources import DatabaseResource
from itsdangerous import BadSignature
import pytest
@ -195,3 +195,14 @@ async def test_apply_metadata_json():
assert (await ds.client.get("/")).status_code == 200
value = (await ds.get_instance_metadata()).get("weird_instance_value")
assert value == '{"nested": [1, 2, 3]}'
@pytest.mark.asyncio
async def test_allowed_resources_sql(datasette):
result = await datasette.allowed_resources_sql(
action="view-table",
actor=None,
)
assert isinstance(result, ResourcesSQL)
assert "all_rules AS" in result.sql
assert result.params["action"] == "view-table"