- Fix Python formatting (sync no longer strips blank lines from .py files) - Remove test_community_build.py (tests private sync module) - Fix ruff warnings in test files - Add CODE_OF_CONDUCT.md - Add .github/PULL_REQUEST_TEMPLATE.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
422 lines
15 KiB
Python
422 lines
15 KiB
Python
"""
|
|
Files & Folders Handler - Asset management
|
|
|
|
Phase J.2: 12 tools
|
|
- Files: list, get, update, delete, delete_files, import_url (6)
|
|
- Folders: list, get, create, update, delete (5)
|
|
- Note: File upload requires multipart form - import_url is the alternative
|
|
"""
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
from plugins.directus.client import DirectusClient
|
|
|
|
|
|
def _parse_json_param(value: Any, param_name: str = "parameter") -> Any:
|
|
"""Parse a parameter that may be a JSON string or already a native type."""
|
|
if value is None:
|
|
return None
|
|
if isinstance(value, (dict, list)):
|
|
return value
|
|
if isinstance(value, str):
|
|
stripped = value.strip()
|
|
if stripped.startswith("{") or stripped.startswith("["):
|
|
try:
|
|
return json.loads(stripped)
|
|
except json.JSONDecodeError as e:
|
|
raise ValueError(f"Invalid JSON in '{param_name}': {e}")
|
|
return value
|
|
|
|
|
|
def get_tool_specifications() -> list[dict[str, Any]]:
|
|
"""Return tool specifications for ToolGenerator (12 tools)"""
|
|
return [
|
|
# =====================
|
|
# FILES (7)
|
|
# =====================
|
|
{
|
|
"name": "list_files",
|
|
"method_name": "list_files",
|
|
"description": "List all files in Directus storage with filtering options.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"filter": {
|
|
"anyOf": [{"type": "object"}, {"type": "null"}],
|
|
"description": 'Filter object (e.g., {"type": {"_contains": "image"}})',
|
|
},
|
|
"sort": {
|
|
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
|
"description": "Sort fields (e.g., ['-uploaded_on'])",
|
|
},
|
|
"limit": {
|
|
"type": "integer",
|
|
"description": "Maximum files to return",
|
|
"default": 100,
|
|
},
|
|
"offset": {"type": "integer", "description": "Files to skip", "default": 0},
|
|
"search": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "Search term",
|
|
},
|
|
},
|
|
"required": [],
|
|
},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "get_file",
|
|
"method_name": "get_file",
|
|
"description": "Get file metadata by ID.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {"id": {"type": "string", "description": "File UUID"}},
|
|
"required": ["id"],
|
|
},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "update_file",
|
|
"method_name": "update_file",
|
|
"description": "Update file metadata (title, description, tags, folder).",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"id": {"type": "string", "description": "File UUID"},
|
|
"data": {
|
|
"type": "object",
|
|
"description": "Fields to update (title, description, tags, folder, etc.)",
|
|
},
|
|
},
|
|
"required": ["id", "data"],
|
|
},
|
|
"scope": "write",
|
|
},
|
|
{
|
|
"name": "delete_file",
|
|
"method_name": "delete_file",
|
|
"description": "Delete a file. This action is irreversible.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {"id": {"type": "string", "description": "File UUID to delete"}},
|
|
"required": ["id"],
|
|
},
|
|
"scope": "admin",
|
|
},
|
|
{
|
|
"name": "delete_files",
|
|
"method_name": "delete_files",
|
|
"description": "Delete multiple files. This action is irreversible.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"ids": {
|
|
"type": "array",
|
|
"items": {"type": "string"},
|
|
"description": "Array of file UUIDs to delete",
|
|
}
|
|
},
|
|
"required": ["ids"],
|
|
},
|
|
"scope": "admin",
|
|
},
|
|
{
|
|
"name": "import_file_url",
|
|
"method_name": "import_file_url",
|
|
"description": "Import a file from a URL into Directus storage.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"url": {"type": "string", "description": "URL of the file to import"},
|
|
"data": {
|
|
"anyOf": [{"type": "object"}, {"type": "null"}],
|
|
"description": "Additional file data (title, description, folder, etc.)",
|
|
},
|
|
},
|
|
"required": ["url"],
|
|
},
|
|
"scope": "write",
|
|
},
|
|
{
|
|
"name": "get_file_url",
|
|
"method_name": "get_file_url",
|
|
"description": "Get the direct URL to access a file.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"id": {"type": "string", "description": "File UUID"},
|
|
"download": {
|
|
"type": "boolean",
|
|
"description": "Whether to force download instead of display",
|
|
"default": False,
|
|
},
|
|
},
|
|
"required": ["id"],
|
|
},
|
|
"scope": "read",
|
|
},
|
|
# =====================
|
|
# FOLDERS (5)
|
|
# =====================
|
|
{
|
|
"name": "list_folders",
|
|
"method_name": "list_folders",
|
|
"description": "List all folders in Directus storage.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"filter": {
|
|
"anyOf": [{"type": "object"}, {"type": "null"}],
|
|
"description": "Filter object",
|
|
},
|
|
"sort": {
|
|
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
|
"description": "Sort fields",
|
|
},
|
|
"limit": {
|
|
"type": "integer",
|
|
"description": "Maximum folders to return",
|
|
"default": 100,
|
|
},
|
|
"search": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "Search term",
|
|
},
|
|
},
|
|
"required": [],
|
|
},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "get_folder",
|
|
"method_name": "get_folder",
|
|
"description": "Get folder details by ID.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {"id": {"type": "string", "description": "Folder UUID"}},
|
|
"required": ["id"],
|
|
},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "create_folder",
|
|
"method_name": "create_folder",
|
|
"description": "Create a new folder.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {"type": "string", "description": "Folder name"},
|
|
"parent": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "Parent folder UUID (null for root)",
|
|
},
|
|
},
|
|
"required": ["name"],
|
|
},
|
|
"scope": "write",
|
|
},
|
|
{
|
|
"name": "update_folder",
|
|
"method_name": "update_folder",
|
|
"description": "Update folder name or parent.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"id": {"type": "string", "description": "Folder UUID"},
|
|
"data": {"type": "object", "description": "Fields to update (name, parent)"},
|
|
},
|
|
"required": ["id", "data"],
|
|
},
|
|
"scope": "write",
|
|
},
|
|
{
|
|
"name": "delete_folder",
|
|
"method_name": "delete_folder",
|
|
"description": "Delete a folder. This action is irreversible.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {"id": {"type": "string", "description": "Folder UUID to delete"}},
|
|
"required": ["id"],
|
|
},
|
|
"scope": "admin",
|
|
},
|
|
]
|
|
|
|
|
|
# =====================
|
|
# HANDLER FUNCTIONS
|
|
# =====================
|
|
|
|
|
|
async def list_files(
|
|
client: DirectusClient,
|
|
filter: dict | None = None,
|
|
sort: list[str] | None = None,
|
|
limit: int = 100,
|
|
offset: int = 0,
|
|
search: str | None = None,
|
|
) -> str:
|
|
"""List files."""
|
|
try:
|
|
# Parse JSON string parameters
|
|
parsed_filter = _parse_json_param(filter, "filter")
|
|
parsed_sort = _parse_json_param(sort, "sort")
|
|
|
|
result = await client.list_files(
|
|
filter=parsed_filter, sort=parsed_sort, limit=limit, offset=offset, search=search
|
|
)
|
|
files = result.get("data", [])
|
|
return json.dumps(
|
|
{"success": True, "total": len(files), "files": files}, indent=2, ensure_ascii=False
|
|
)
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def get_file(client: DirectusClient, id: str) -> str:
|
|
"""Get file metadata."""
|
|
try:
|
|
result = await client.get_file(id)
|
|
return json.dumps(
|
|
{"success": True, "file": result.get("data")}, indent=2, ensure_ascii=False
|
|
)
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def update_file(client: DirectusClient, id: str, data: dict[str, Any]) -> str:
|
|
"""Update file metadata."""
|
|
try:
|
|
# Parse JSON string parameter
|
|
parsed_data = _parse_json_param(data, "data")
|
|
result = await client.update_file(id, parsed_data)
|
|
return json.dumps(
|
|
{"success": True, "message": "File updated", "file": result.get("data")},
|
|
indent=2,
|
|
ensure_ascii=False,
|
|
)
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def delete_file(client: DirectusClient, id: str) -> str:
|
|
"""Delete a file."""
|
|
try:
|
|
await client.delete_file(id)
|
|
return json.dumps({"success": True, "message": f"File {id} deleted"}, indent=2)
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def delete_files(client: DirectusClient, ids: list[str]) -> str:
|
|
"""Delete multiple files."""
|
|
try:
|
|
# Parse JSON string parameter
|
|
parsed_ids = _parse_json_param(ids, "ids")
|
|
await client.delete_files(parsed_ids)
|
|
return json.dumps({"success": True, "message": f"Deleted {len(ids)} files"}, indent=2)
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def import_file_url(client: DirectusClient, url: str, data: dict | None = None) -> str:
|
|
"""Import file from URL."""
|
|
try:
|
|
# Parse JSON string parameter
|
|
parsed_data = _parse_json_param(data, "data")
|
|
result = await client.import_file_url(url, parsed_data)
|
|
return json.dumps(
|
|
{"success": True, "message": "File imported", "file": result.get("data")},
|
|
indent=2,
|
|
ensure_ascii=False,
|
|
)
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def get_file_url(client: DirectusClient, id: str, download: bool = False) -> str:
|
|
"""Get file URL."""
|
|
try:
|
|
base_url = client.base_url
|
|
url = f"{base_url}/assets/{id}"
|
|
if download:
|
|
url += "?download=true"
|
|
return json.dumps({"success": True, "id": id, "url": url, "download": download}, indent=2)
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def list_folders(
|
|
client: DirectusClient,
|
|
filter: dict | None = None,
|
|
sort: list[str] | None = None,
|
|
limit: int = 100,
|
|
search: str | None = None,
|
|
) -> str:
|
|
"""List folders."""
|
|
try:
|
|
# Parse JSON string parameters
|
|
parsed_filter = _parse_json_param(filter, "filter")
|
|
parsed_sort = _parse_json_param(sort, "sort")
|
|
|
|
result = await client.list_folders(
|
|
filter=parsed_filter, sort=parsed_sort, limit=limit, search=search
|
|
)
|
|
folders = result.get("data", [])
|
|
return json.dumps(
|
|
{"success": True, "total": len(folders), "folders": folders},
|
|
indent=2,
|
|
ensure_ascii=False,
|
|
)
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def get_folder(client: DirectusClient, id: str) -> str:
|
|
"""Get folder details."""
|
|
try:
|
|
result = await client.get_folder(id)
|
|
return json.dumps(
|
|
{"success": True, "folder": result.get("data")}, indent=2, ensure_ascii=False
|
|
)
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def create_folder(client: DirectusClient, name: str, parent: str | None = None) -> str:
|
|
"""Create a folder."""
|
|
try:
|
|
result = await client.create_folder(name, parent)
|
|
return json.dumps(
|
|
{"success": True, "message": f"Folder '{name}' created", "folder": result.get("data")},
|
|
indent=2,
|
|
ensure_ascii=False,
|
|
)
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def update_folder(client: DirectusClient, id: str, data: dict[str, Any]) -> str:
|
|
"""Update folder."""
|
|
try:
|
|
# Parse JSON string parameter
|
|
parsed_data = _parse_json_param(data, "data")
|
|
result = await client.update_folder(id, parsed_data)
|
|
return json.dumps(
|
|
{"success": True, "message": "Folder updated", "folder": result.get("data")},
|
|
indent=2,
|
|
ensure_ascii=False,
|
|
)
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def delete_folder(client: DirectusClient, id: str) -> str:
|
|
"""Delete a folder."""
|
|
try:
|
|
await client.delete_folder(id)
|
|
return json.dumps({"success": True, "message": f"Folder {id} deleted"}, indent=2)
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|