""" Items Handler - CRUD operations for any collection Phase J.1: 12 tools - list_items, get_item, create_item, create_items - update_item, update_items, delete_item, delete_items - search_items, aggregate_items, export_items, import_items """ 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 [ { "name": "list_items", "method_name": "list_items", "description": "List items from any Directus collection with filtering, sorting, and pagination.", "schema": { "type": "object", "properties": { "collection": { "type": "string", "description": "Collection name (e.g., 'posts', 'products')", }, "fields": { "anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}], "description": "Fields to return (e.g., ['id', 'title', 'author.*'])", }, "filter": { "anyOf": [{"type": "object"}, {"type": "null"}], "description": 'Filter object (e.g., {"status": {"_eq": "published"}})', }, "sort": { "anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}], "description": "Sort fields (e.g., ['-date_created', 'title'])", }, "limit": { "type": "integer", "description": "Maximum items to return", "default": 100, }, "offset": {"type": "integer", "description": "Items to skip", "default": 0}, "search": { "anyOf": [{"type": "string"}, {"type": "null"}], "description": "Full-text search query", }, }, "required": ["collection"], }, "scope": "read", }, { "name": "get_item", "method_name": "get_item", "description": "Get a single item by ID from any collection.", "schema": { "type": "object", "properties": { "collection": {"type": "string", "description": "Collection name"}, "id": {"type": "string", "description": "Item ID"}, "fields": { "anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}], "description": "Fields to return", }, }, "required": ["collection", "id"], }, "scope": "read", }, { "name": "create_item", "method_name": "create_item", "description": "Create a new item in any collection.", "schema": { "type": "object", "properties": { "collection": {"type": "string", "description": "Collection name"}, "data": {"type": "object", "description": "Item data (field values)"}, "fields": { "anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}], "description": "Fields to return in response", }, }, "required": ["collection", "data"], }, "scope": "write", }, { "name": "create_items", "method_name": "create_items", "description": "Create multiple items in a collection at once.", "schema": { "type": "object", "properties": { "collection": {"type": "string", "description": "Collection name"}, "data": { "type": "array", "items": {"type": "object"}, "description": "Array of item data objects", }, "fields": { "anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}], "description": "Fields to return in response", }, }, "required": ["collection", "data"], }, "scope": "write", }, { "name": "update_item", "method_name": "update_item", "description": "Update an existing item by ID.", "schema": { "type": "object", "properties": { "collection": {"type": "string", "description": "Collection name"}, "id": {"type": "string", "description": "Item ID"}, "data": {"type": "object", "description": "Fields to update"}, "fields": { "anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}], "description": "Fields to return in response", }, }, "required": ["collection", "id", "data"], }, "scope": "write", }, { "name": "update_items", "method_name": "update_items", "description": "Update multiple items by their IDs.", "schema": { "type": "object", "properties": { "collection": {"type": "string", "description": "Collection name"}, "keys": { "type": "array", "items": {"type": "string"}, "description": "Array of item IDs to update", }, "data": { "type": "object", "description": "Fields to update (applied to all items)", }, "fields": { "anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}], "description": "Fields to return in response", }, }, "required": ["collection", "keys", "data"], }, "scope": "write", }, { "name": "delete_item", "method_name": "delete_item", "description": "Delete an item by ID. This action is irreversible.", "schema": { "type": "object", "properties": { "collection": {"type": "string", "description": "Collection name"}, "id": {"type": "string", "description": "Item ID to delete"}, }, "required": ["collection", "id"], }, "scope": "admin", }, { "name": "delete_items", "method_name": "delete_items", "description": "Delete multiple items by their IDs. This action is irreversible.", "schema": { "type": "object", "properties": { "collection": {"type": "string", "description": "Collection name"}, "keys": { "type": "array", "items": {"type": "string"}, "description": "Array of item IDs to delete", }, }, "required": ["collection", "keys"], }, "scope": "admin", }, { "name": "search_items", "method_name": "search_items", "description": "Full-text search across items in a collection.", "schema": { "type": "object", "properties": { "collection": {"type": "string", "description": "Collection name"}, "query": {"type": "string", "description": "Search query"}, "fields": { "anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}], "description": "Fields to return", }, "limit": { "type": "integer", "description": "Maximum items to return", "default": 25, }, }, "required": ["collection", "query"], }, "scope": "read", }, { "name": "aggregate_items", "method_name": "aggregate_items", "description": "Perform aggregate operations (count, sum, avg, min, max) on items.", "schema": { "type": "object", "properties": { "collection": {"type": "string", "description": "Collection name"}, "aggregate": { "type": "object", "description": 'Aggregate functions (e.g., {"count": "*", "sum": "price"})', }, "filter": { "anyOf": [{"type": "object"}, {"type": "null"}], "description": "Filter to apply before aggregation", }, "groupBy": { "anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}], "description": "Fields to group by", }, }, "required": ["collection", "aggregate"], }, "scope": "read", }, { "name": "export_items", "method_name": "export_items", "description": "Export items from a collection with filtering options.", "schema": { "type": "object", "properties": { "collection": {"type": "string", "description": "Collection name"}, "fields": { "anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}], "description": "Fields to export", }, "filter": { "anyOf": [{"type": "object"}, {"type": "null"}], "description": "Filter to apply", }, "sort": { "anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}], "description": "Sort fields", }, "limit": { "type": "integer", "description": "Maximum items to export", "default": 1000, }, }, "required": ["collection"], }, "scope": "read", }, { "name": "import_items", "method_name": "import_items", "description": "Import multiple items into a collection.", "schema": { "type": "object", "properties": { "collection": {"type": "string", "description": "Collection name"}, "data": { "type": "array", "items": {"type": "object"}, "description": "Array of items to import", }, }, "required": ["collection", "data"], }, "scope": "write", }, ] # ===================== # HANDLER FUNCTIONS # ===================== async def list_items( client: DirectusClient, collection: str, fields: list[str] | None = None, filter: dict | None = None, sort: list[str] | None = None, limit: int = 100, offset: int = 0, search: str | None = None, ) -> str: """List items from a collection.""" try: # Parse JSON string parameters parsed_fields = _parse_json_param(fields, "fields") parsed_filter = _parse_json_param(filter, "filter") parsed_sort = _parse_json_param(sort, "sort") result = await client.list_items( collection=collection, fields=parsed_fields, filter=parsed_filter, sort=parsed_sort, limit=limit, offset=offset, search=search, ) items = result.get("data", []) return json.dumps( {"success": True, "collection": collection, "total": len(items), "items": items}, indent=2, ensure_ascii=False, ) except Exception as e: return json.dumps({"success": False, "error": str(e)}, indent=2) async def get_item( client: DirectusClient, collection: str, id: str, fields: list[str] | None = None ) -> str: """Get item by ID.""" try: # Parse JSON string parameter parsed_fields = _parse_json_param(fields, "fields") result = await client.get_item(collection, id, fields=parsed_fields) return json.dumps( {"success": True, "item": 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_item( client: DirectusClient, collection: str, data: dict[str, Any], fields: list[str] | None = None ) -> str: """Create a new item.""" try: # Parse JSON string parameters parsed_data = _parse_json_param(data, "data") parsed_fields = _parse_json_param(fields, "fields") result = await client.create_item(collection, parsed_data, fields=parsed_fields) return json.dumps( { "success": True, "message": f"Item created in {collection}", "item": 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_items( client: DirectusClient, collection: str, data: list[dict[str, Any]], fields: list[str] | None = None, ) -> str: """Create multiple items.""" try: # Parse JSON string parameters parsed_data = _parse_json_param(data, "data") parsed_fields = _parse_json_param(fields, "fields") result = await client.create_items(collection, parsed_data, fields=parsed_fields) items = result.get("data", []) return json.dumps( { "success": True, "message": f"Created {len(items)} items in {collection}", "items": items, }, indent=2, ensure_ascii=False, ) except Exception as e: return json.dumps({"success": False, "error": str(e)}, indent=2) async def update_item( client: DirectusClient, collection: str, id: str, data: dict[str, Any], fields: list[str] | None = None, ) -> str: """Update an item.""" try: # Parse JSON string parameters parsed_data = _parse_json_param(data, "data") parsed_fields = _parse_json_param(fields, "fields") result = await client.update_item(collection, id, parsed_data, fields=parsed_fields) return json.dumps( {"success": True, "message": f"Item {id} updated", "item": 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_items( client: DirectusClient, collection: str, keys: list[str], data: dict[str, Any], fields: list[str] | None = None, ) -> str: """Update multiple items.""" try: # Parse JSON string parameters parsed_keys = _parse_json_param(keys, "keys") parsed_data = _parse_json_param(data, "data") parsed_fields = _parse_json_param(fields, "fields") result = await client.update_items( collection, parsed_keys, parsed_data, fields=parsed_fields ) items = result.get("data", []) return json.dumps( { "success": True, "message": f"Updated {len(items)} items in {collection}", "items": items, }, indent=2, ensure_ascii=False, ) except Exception as e: return json.dumps({"success": False, "error": str(e)}, indent=2) async def delete_item(client: DirectusClient, collection: str, id: str) -> str: """Delete an item.""" try: await client.delete_item(collection, id) return json.dumps( {"success": True, "message": f"Item {id} deleted from {collection}"}, indent=2 ) except Exception as e: return json.dumps({"success": False, "error": str(e)}, indent=2) async def delete_items(client: DirectusClient, collection: str, keys: list[str]) -> str: """Delete multiple items.""" try: # Parse JSON string parameter parsed_keys = _parse_json_param(keys, "keys") await client.delete_items(collection, parsed_keys) return json.dumps( {"success": True, "message": f"Deleted {len(keys)} items from {collection}"}, indent=2 ) except Exception as e: return json.dumps({"success": False, "error": str(e)}, indent=2) async def search_items( client: DirectusClient, collection: str, query: str, fields: list[str] | None = None, limit: int = 25, ) -> str: """Full-text search items.""" try: # Parse JSON string parameter parsed_fields = _parse_json_param(fields, "fields") result = await client.list_items( collection=collection, fields=parsed_fields, search=query, limit=limit ) items = result.get("data", []) return json.dumps( {"success": True, "query": query, "total": len(items), "items": items}, indent=2, ensure_ascii=False, ) except Exception as e: return json.dumps({"success": False, "error": str(e)}, indent=2) async def aggregate_items( client: DirectusClient, collection: str, aggregate: dict[str, Any], filter: dict | None = None, groupBy: list[str] | None = None, ) -> str: """Aggregate items.""" try: # Parse JSON string parameters parsed_aggregate = _parse_json_param(aggregate, "aggregate") parsed_filter = _parse_json_param(filter, "filter") _parse_json_param(groupBy, "groupBy") result = await client.list_items( collection=collection, filter=parsed_filter, aggregate=parsed_aggregate ) return json.dumps( {"success": True, "collection": collection, "result": result.get("data")}, indent=2, ensure_ascii=False, ) except Exception as e: return json.dumps({"success": False, "error": str(e)}, indent=2) async def export_items( client: DirectusClient, collection: str, fields: list[str] | None = None, filter: dict | None = None, sort: list[str] | None = None, limit: int = 1000, ) -> str: """Export items from a collection.""" try: # Parse JSON string parameters parsed_fields = _parse_json_param(fields, "fields") parsed_filter = _parse_json_param(filter, "filter") parsed_sort = _parse_json_param(sort, "sort") result = await client.list_items( collection=collection, fields=parsed_fields, filter=parsed_filter, sort=parsed_sort, limit=limit, ) items = result.get("data", []) return json.dumps( { "success": True, "collection": collection, "exported_count": len(items), "data": items, }, indent=2, ensure_ascii=False, ) except Exception as e: return json.dumps({"success": False, "error": str(e)}, indent=2) async def import_items(client: DirectusClient, collection: str, data: list[dict[str, Any]]) -> str: """Import items into a collection.""" try: # Parse JSON string parameter parsed_data = _parse_json_param(data, "data") result = await client.create_items(collection, parsed_data) items = result.get("data", []) return json.dumps( { "success": True, "message": f"Imported {len(items)} items into {collection}", "imported_count": len(items), }, indent=2, ensure_ascii=False, ) except Exception as e: return json.dumps({"success": False, "error": str(e)}, indent=2)