mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Major improvements to the allowed_resources() API: 1. **parent filter**: Filter results to specific database in SQL, not Python - Avoids loading thousands of tables into Python memory - Filtering happens efficiently in SQLite 2. **include_is_private flag**: Detect private resources in single SQL query - Compares actor permissions vs anonymous permissions in SQL - LEFT JOIN between actor_allowed and anon_allowed CTEs - Returns is_private column: 1 if anonymous blocked, 0 otherwise - No individual check_visibility() calls needed 3. **Resource.private property**: Safe access with clear error messages - Raises AttributeError if accessed without include_is_private=True - Prevents accidental misuse of the property 4. **Database view optimization**: Use new API to eliminate redundant checks - Single bulk query replaces N individual permission checks - Private flag computed in SQL, not via check_visibility() calls - Views filtered from allowed_dict instead of checking db.view_names() All permission filtering now happens in SQLite where it belongs, with minimal data transferred to Python. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
108 lines
3.1 KiB
Python
108 lines
3.1 KiB
Python
from abc import ABC, abstractmethod
|
|
from dataclasses import dataclass
|
|
from typing import Any, Dict, Optional, NamedTuple
|
|
|
|
|
|
class Resource(ABC):
|
|
"""
|
|
Base class for all resource types.
|
|
|
|
Each subclass represents a type of resource (e.g., TableResource, DatabaseResource).
|
|
The class itself carries metadata about the resource type.
|
|
Instances represent specific resources.
|
|
"""
|
|
|
|
# Class-level metadata (subclasses must define these)
|
|
name: str = None # e.g., "table", "database", "model"
|
|
parent_name: Optional[str] = None # e.g., "database" for tables
|
|
|
|
def __init__(self, parent: Optional[str] = None, child: Optional[str] = None):
|
|
"""
|
|
Create a resource instance.
|
|
|
|
Args:
|
|
parent: The parent identifier (meaning depends on resource type)
|
|
child: The child identifier (meaning depends on resource type)
|
|
"""
|
|
self.parent = parent
|
|
self.child = child
|
|
self._private = None # Sentinel to track if private was set
|
|
|
|
@property
|
|
def private(self) -> bool:
|
|
"""
|
|
Whether this resource is private (accessible to actor but not anonymous).
|
|
|
|
This property is only available on Resource objects returned from
|
|
allowed_resources() when include_is_private=True is used.
|
|
|
|
Raises:
|
|
AttributeError: If accessed without calling include_is_private=True
|
|
"""
|
|
if self._private is None:
|
|
raise AttributeError(
|
|
"The 'private' attribute is only available when using "
|
|
"allowed_resources(..., include_is_private=True)"
|
|
)
|
|
return self._private
|
|
|
|
@private.setter
|
|
def private(self, value: bool):
|
|
self._private = value
|
|
|
|
@classmethod
|
|
@abstractmethod
|
|
def resources_sql(cls) -> str:
|
|
"""
|
|
Return SQL query that returns all resources of this type.
|
|
|
|
Must return two columns: parent, child
|
|
"""
|
|
pass
|
|
|
|
|
|
class AllowedResource(NamedTuple):
|
|
"""A resource with the reason it was allowed (for debugging)."""
|
|
|
|
resource: Resource
|
|
reason: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Action:
|
|
name: str
|
|
abbr: str | None
|
|
description: str | None
|
|
takes_parent: bool
|
|
takes_child: bool
|
|
resource_class: type[Resource]
|
|
|
|
|
|
@dataclass
|
|
class PermissionSQL:
|
|
"""
|
|
A plugin contributes SQL that yields:
|
|
parent TEXT NULL,
|
|
child TEXT NULL,
|
|
allow INTEGER, -- 1 allow, 0 deny
|
|
reason TEXT
|
|
"""
|
|
|
|
source: str # identifier used for auditing (e.g., plugin name)
|
|
sql: str # SQL that SELECTs the 4 columns above
|
|
params: Dict[str, Any] # bound params for the SQL (values only; no ':' prefix)
|
|
|
|
|
|
# This is obsolete, replaced by Action and ResourceType
|
|
@dataclass
|
|
class Permission:
|
|
name: str
|
|
abbr: Optional[str]
|
|
description: Optional[str]
|
|
takes_database: bool
|
|
takes_resource: bool
|
|
default: bool
|
|
# This is deliberately undocumented: it's considered an internal
|
|
# implementation detail for view-table/view-database and should
|
|
# not be used by plugins as it may change in the future.
|
|
implies_can_view: bool = False
|