From cf887e02771ff13b859e3073f5339bdb113fffc5 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Thu, 23 Oct 2025 09:25:33 -0700 Subject: [PATCH] ds.allowed() is now keyword-argument only, closes #2519 --- datasette/app.py | 7 ++++--- tests/test_actions_sql.py | 36 +++++++++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/datasette/app.py b/datasette/app.py index 53e8b8f2..90030e2c 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -1369,6 +1369,7 @@ class Datasette: async def allowed( self, + *, action: str, resource: "Resource", actor: dict | None = None, @@ -1382,9 +1383,9 @@ class Datasette: Example: from datasette.resources import TableResource can_view = await datasette.allowed( - "view-table", - TableResource(database="analytics", table="users"), - actor + action="view-table", + resource=TableResource(database="analytics", table="users"), + actor=actor ) """ from datasette.utils.actions_sql import check_permission_for_resource diff --git a/tests/test_actions_sql.py b/tests/test_actions_sql.py index 774aaaf5..756e3801 100644 --- a/tests/test_actions_sql.py +++ b/tests/test_actions_sql.py @@ -113,13 +113,19 @@ async def test_allowed_specific_resource(test_ds): # Check specific resources using allowed() # This should use SQL WHERE clause, not fetch all resources assert await test_ds.allowed( - "view-table", TableResource("analytics", "users"), actor + action="view-table", + resource=TableResource("analytics", "users"), + actor=actor, ) assert await test_ds.allowed( - "view-table", TableResource("analytics", "events"), actor + action="view-table", + resource=TableResource("analytics", "events"), + actor=actor, ) assert not await test_ds.allowed( - "view-table", TableResource("production", "orders"), actor + action="view-table", + resource=TableResource("production", "orders"), + actor=actor, ) finally: @@ -200,10 +206,14 @@ async def test_child_deny_overrides_parent_allow(test_ds): # Verify with allowed() method assert await test_ds.allowed( - "view-table", TableResource("analytics", "users"), actor + action="view-table", + resource=TableResource("analytics", "users"), + actor=actor, ) assert not await test_ds.allowed( - "view-table", TableResource("analytics", "sensitive"), actor + action="view-table", + resource=TableResource("analytics", "sensitive"), + actor=actor, ) finally: @@ -240,10 +250,14 @@ async def test_child_allow_overrides_parent_deny(test_ds): # Verify with allowed() method assert await test_ds.allowed( - "view-table", TableResource("production", "orders"), actor + action="view-table", + resource=TableResource("production", "orders"), + actor=actor, ) assert not await test_ds.allowed( - "view-table", TableResource("production", "customers"), actor + action="view-table", + resource=TableResource("production", "customers"), + actor=actor, ) finally: @@ -301,10 +315,14 @@ async def test_sql_does_filtering_not_python(test_ds): # allowed() should execute a targeted SQL query # NOT fetch all resources and filter in Python assert await test_ds.allowed( - "view-table", TableResource("analytics", "users"), actor + action="view-table", + resource=TableResource("analytics", "users"), + actor=actor, ) assert not await test_ds.allowed( - "view-table", TableResource("analytics", "events"), actor + action="view-table", + resource=TableResource("analytics", "events"), + actor=actor, ) # allowed_resources() should also use SQL filtering