From 97ea8469d1e1516aab538ea89e7c281d00041f3d Mon Sep 17 00:00:00 2001 From: Rami Abdelrazzaq Date: Sat, 7 Mar 2026 12:39:15 -0600 Subject: [PATCH] fix: add backwards-compatible shim(s) for 0.x to 1.0 transition Fixes simonw/datasette#2638 --- datasette/app.py | 25 ++++++++++++++++++ tests/test_internals_datasette.py | 44 ++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/datasette/app.py b/datasette/app.py index 2df6e4e8e1..586d30a80b 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -1441,6 +1441,31 @@ async def allowed( return result + async def permission_allowed( + self, actor, action, resource=None, *, default=DEFAULT_NOT_SET + ): + """Backward-compatible wrapper around allowed(). + + Supports the pre-1.0 signature and resource formats: + - None (instance-level) + - "database" + - ("database", "table") + + The ``default=`` argument is accepted for compatibility but ignored. + """ + _ = default + + if resource is None: + resource_obj = None + elif isinstance(resource, str): + resource_obj = DatabaseResource(database=resource) + elif isinstance(resource, (tuple, list)) and len(resource) == 2: + resource_obj = TableResource(database=resource[0], table=resource[1]) + else: + raise TypeError("resource must be None, str, or (database, table) tuple") + + return await self.allowed(action=action, resource=resource_obj, actor=actor) + async def ensure_permission( self, *, diff --git a/tests/test_internals_datasette.py b/tests/test_internals_datasette.py index b378a15875..ce70dd3042 100644 --- a/tests/test_internals_datasette.py +++ b/tests/test_internals_datasette.py @@ -5,7 +5,7 @@ import dataclasses from datasette import Context from datasette.app import Datasette, Database, ResourcesSQL -from datasette.resources import DatabaseResource +from datasette.resources import DatabaseResource, TableResource from itsdangerous import BadSignature import pytest @@ -206,3 +206,45 @@ async def test_allowed_resources_sql(datasette): assert isinstance(result, ResourcesSQL) assert "all_rules AS" in result.sql assert result.params["action"] == "view-table" + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "resource,expected_resource", + ( + (None, None), + ("fixtures", DatabaseResource(database="fixtures")), + ( + ("fixtures", "simple_primary_key"), + TableResource("fixtures", "simple_primary_key"), + ), + ), +) +async def test_permission_allowed_backwards_compatibility(datasette, monkeypatch, resource, expected_resource): + captured = {} + + async def fake_allowed(*, action, resource, actor): + captured["action"] = action + captured["resource"] = resource + captured["actor"] = actor + return True + + monkeypatch.setattr(datasette, "allowed", fake_allowed) + + actor = {"id": "root"} + result = await datasette.permission_allowed(actor=actor, action="view-table", resource=resource) + + assert result is True + assert captured["action"] == "view-table" + if expected_resource is None: + assert captured["resource"] is None + else: + assert captured["resource"].parent == expected_resource.parent + assert captured["resource"].child == expected_resource.child + assert captured["actor"] == actor + + +@pytest.mark.asyncio +async def test_permission_allowed_rejects_invalid_resource(datasette): + with pytest.raises(TypeError): + await datasette.permission_allowed(actor=None, action="view-table", resource=("only-one",))