Three changes:
1. Rewrite test_utils_permissions.py to exercise production code paths
(allowed_resources / allowed) instead of the test-only
resolve_permissions_from_catalog function. Tests now register plugins
via ds.pm.register and call the real Datasette methods.
2. Remove resolve_permissions_from_catalog, resolve_permissions_with_candidates,
and build_rules_union from datasette/utils/permissions.py. These were
only used by tests and implemented the cascading logic a third time,
independently of the two production implementations.
3. Fix bug in gather_permission_sql_from_hooks where empty params dicts
({}) would cause framework-injected params (:actor_id, :actor, :action)
to be silently lost. The expression `params = permission_sql.params or {}`
creates a new dict when params is {} (falsy), so setdefault writes to a
throwaway dict. Fixed by explicitly checking `is None`.
https://claude.ai/code/session_013EkyroQKPhcjdMbpHc9g4X
Detailed design for extracting build_cascading_ctes(),
collect_permission_rules(), and build_restriction_filter() to
replace three separate implementations with one shared SQL builder.
Includes migration plan and handles the include_is_private
complication.
https://claude.ai/code/session_013EkyroQKPhcjdMbpHc9g4X
Comprehensive analysis of the permission system introduced in 1.0a20
through 1.0a24, covering architecture, security, performance, and
design concerns across 12 identified issues with prioritized
recommendations.
https://claude.ai/code/session_013EkyroQKPhcjdMbpHc9g4X
* Add request.form() for multipart form data and file uploads
New Request.form() method that handles both application/x-www-form-urlencoded
and multipart/form-data content types with streaming parsing.
Features:
- Streaming multipart parser that doesn't buffer entire body in memory
- Files spill to disk above 1MB threshold via SpooledTemporaryFile
- files=False (default) discards file content, files=True stores them
- Security limits: max_request_size, max_file_size, max_fields, max_files
- FormData container with dict-like access and getlist() for multiple values
- UploadedFile class with async read(), seek(), filename, content_type, size
- Support for RFC 5987 filename* encoding for international filenames
Uses multipart-form-data-conformance test suite for validation.
* Update views to use request.form() and document new API
- Migrate PermissionsDebugView, MessagesDebugView, and CreateTokenView
from post_vars() to form()
- Add documentation for request.form(), FormData, and UploadedFile classes
Centralize multipart defaults and expose stricter limits via Request.form().
Enforce header, part, file, and disk space limits even when files are discarded; detect truncated bodies and client disconnects; and move blocking work off the event loop.
Add FormData close/aclose context managers, update internals docs, and expand multipart tests (including len semantics and stricter conformance expectations).
* Fix flaky test_database_page test with deterministic ordering
- Add ORDER BY to table_names() query in database.py
- Sort foreign keys deterministically in get_all_foreign_keys()
- Refactor test_database_page to use property-based assertions instead of
500+ lines of hardcoded expected data
- Run blacken-docs on plugin_hooks.rst
* Update test_row_foreign_key_tables for new deterministic FK ordering
The foreign keys are now sorted by (other_table, column, other_column),
so complex_foreign_keys comes before foreign_key_references alphabetically.
* Update test_table_names for new alphabetical ordering
The table_names() method now returns tables sorted alphabetically.
* Fix for test that fails prior to SQLite 3.37
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Fix test isolation bug in test_startup_error_from_plugin_is_click_exception
The test creates a plugin that raises StartupError("boom") and registers it
in the global plugin manager (pm). Without cleanup, this plugin leaks to
subsequent tests, causing test_setting_boolean_validation_false_values to
fail with "Error: boom" instead of "Forbidden".
Add try/finally block to ensure the plugin is unregistered after the test
completes, following the established cleanup pattern used elsewhere in
the test suite.
* Fix blacken-docs formatting in plugin_hooks.rst
Apply blacken-docs formatting to code example that exceeded
the 60 character line limit.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Split default_permissions.py into a package, refs #2602
* Remove unused is_resource_allowed() method, improve test coverage
- Remove dead code: is_resource_allowed() method was never called
- Change isinstance check to assertion with error message
- Add test cases for table-level restrictions in restrictions_allow_action()
- Coverage for restrictions.py improved from 79% to 99%
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Additional permission test for gap spotted by coverage