Skip to content

datasette.allowed_many() method and per-request permission check cache#2775

Merged
simonw merged 4 commits into
mainfrom
allowed-many
Jun 13, 2026
Merged

datasette.allowed_many() method and per-request permission check cache#2775
simonw merged 4 commits into
mainfrom
allowed-many

Conversation

@simonw

@simonw simonw commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Got this out of Fable before they shut it down.


📚 Documentation preview 📚: https://datasette--2775.org.readthedocs.build/en/2775/

simonw and others added 2 commits June 12, 2026 12:51
Adds a per-request cache for permission check results, plus wiring that
resolves action permissions in bulk before plugin hooks need them:

- New _permission_check_cache contextvar, set to a fresh dict for each
  request by DatasetteRouter and reset when the request ends. Keys
  include the full serialized actor, so actors differing in any field
  (e.g. token restrictions) never share entries. SkipPermissions mode
  bypasses the cache entirely.
- datasette.allowed_many() now consults the cache and stores its
  results there, so repeated datasette.allowed() checks within one
  request resolve without further SQL.
- Table pages resolve all registered table-level actions against the
  current table and all database-level actions against its database
  (database pages likewise) in batched queries before invoking the
  table_actions/database_actions plugin hooks - allowed() calls made
  inside those hooks are then served from the cache with no plugin
  changes required. Actions with no permission rules from any plugin
  are resolved to False without touching the database.

Benchmarks (benchmarks/) with a simulated 12-plugin ecosystem making
18 checks per table page show 34 -> 13 internal-DB queries per page;
with 2ms-per-query internal DB latency (modelling Datasette Cloud)
table page time drops from 77.9ms to 27.6ms - the caching layer
accounts for ~91% of that improvement over allowed_many() alone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 122 lines in your changes missing coverage. Please review.
✅ Project coverage is 0.00%. Comparing base (fa86ac7) to head (ab19b03).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
datasette/app.py 0.00% 59 Missing ⚠️
datasette/utils/actions_sql.py 0.00% 57 Missing ⚠️
datasette/views/table_extras.py 0.00% 4 Missing ⚠️
datasette/permissions.py 0.00% 1 Missing ⚠️
datasette/views/database.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@          Coverage Diff          @@
##            main   #2775   +/-   ##
=====================================
  Coverage   0.00%   0.00%           
=====================================
  Files         70      70           
  Lines      10744   10839   +95     
=====================================
- Misses     10744   10839   +95     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

GPT-5.5 xhigh in Codex spotted this problem and fixed it with a CTE:

https://gisthost.github.io/?46076499ee685acddc988ff6b47a74b0
@simonw

simonw commented Jun 13, 2026

Copy link
Copy Markdown
Owner Author

A code review from GPT-5.5 xhigh spotted an optimization: https://gisthost.github.io/?46076499ee685acddc988ff6b47a74b0 - the big SQL query would crash at over 500 actions because of SQLite compound SELECT limits, but using a CTE instead fixes that.

Comment thread docs/plugin_hooks.rst Outdated
Comment on lines +1461 to +1466
This hook may be called for many actions in rapid succession - for example
:ref:`datasette.allowed_many() <datasette_allowed_many>` gathers rules for every action in its batch
concurrently before table and database pages render their action menus. Hook implementations must not
assume that checks for different actions arrive one page-render apart, and expensive work (such as
network calls) should be cached independently of the ``action`` argument where possible.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this documentation is useful.

@simonw simonw merged commit d473dc5 into main Jun 13, 2026
36 of 37 checks passed
@simonw simonw deleted the allowed-many branch June 13, 2026 18:13
simonw added a commit that referenced this pull request Jun 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant