- 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>
646 lines
23 KiB
Python
646 lines
23 KiB
Python
"""
|
|
Functions Handler - manages Appwrite serverless functions
|
|
|
|
Phase I.4: 14 tools
|
|
- Functions: 5 (list, get, create, update, delete)
|
|
- Deployments: 5 (list, get, create, delete, activate)
|
|
- Executions: 4 (list, get, create, delete)
|
|
"""
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
from plugins.appwrite.client import AppwriteClient
|
|
|
|
|
|
def get_tool_specifications() -> list[dict[str, Any]]:
|
|
"""Return tool specifications for ToolGenerator (14 tools)"""
|
|
return [
|
|
# =====================
|
|
# FUNCTIONS (5)
|
|
# =====================
|
|
{
|
|
"name": "list_functions",
|
|
"method_name": "list_functions",
|
|
"description": "List all serverless functions in the project.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"queries": {
|
|
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
|
"description": "Query strings for filtering",
|
|
},
|
|
"search": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "Search term to filter functions",
|
|
},
|
|
},
|
|
"required": [],
|
|
},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "get_function",
|
|
"method_name": "get_function",
|
|
"description": "Get function details including deployment status and configuration.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {"function_id": {"type": "string", "description": "Function ID"}},
|
|
"required": ["function_id"],
|
|
},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "create_function",
|
|
"method_name": "create_function",
|
|
"description": "Create a new serverless function.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"function_id": {
|
|
"type": "string",
|
|
"description": "Unique function ID. Use 'unique()' for auto-generation",
|
|
},
|
|
"name": {"type": "string", "description": "Function name"},
|
|
"runtime": {
|
|
"type": "string",
|
|
"description": "Runtime (e.g., 'node-18.0', 'python-3.9', 'php-8.0', 'ruby-3.0')",
|
|
"examples": [
|
|
"node-18.0",
|
|
"node-20.0",
|
|
"python-3.9",
|
|
"python-3.11",
|
|
"php-8.0",
|
|
"php-8.2",
|
|
"ruby-3.0",
|
|
"go-1.21",
|
|
"dart-3.0",
|
|
"rust-1.70",
|
|
],
|
|
},
|
|
"execute": {
|
|
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
|
"description": "Execution permissions (e.g., ['any', 'users'])",
|
|
},
|
|
"events": {
|
|
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
|
"description": "Events to trigger function (e.g., ['databases.*.collections.*.documents.*.create'])",
|
|
},
|
|
"schedule": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "Cron schedule (e.g., '0 * * * *' for every hour)",
|
|
},
|
|
"timeout": {
|
|
"type": "integer",
|
|
"description": "Execution timeout in seconds",
|
|
"default": 15,
|
|
"minimum": 1,
|
|
"maximum": 900,
|
|
},
|
|
"enabled": {
|
|
"type": "boolean",
|
|
"description": "Enable function",
|
|
"default": True,
|
|
},
|
|
"logging": {
|
|
"type": "boolean",
|
|
"description": "Enable logging",
|
|
"default": True,
|
|
},
|
|
"entrypoint": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "Entrypoint file (e.g., 'src/main.js')",
|
|
},
|
|
"commands": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "Build commands",
|
|
},
|
|
},
|
|
"required": ["function_id", "name", "runtime"],
|
|
},
|
|
"scope": "write",
|
|
},
|
|
{
|
|
"name": "update_function",
|
|
"method_name": "update_function",
|
|
"description": "Update function configuration.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"function_id": {"type": "string", "description": "Function ID"},
|
|
"name": {"type": "string", "description": "Function name"},
|
|
"runtime": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "New runtime",
|
|
},
|
|
"execute": {
|
|
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
|
"description": "New execution permissions",
|
|
},
|
|
"events": {
|
|
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
|
"description": "New trigger events",
|
|
},
|
|
"schedule": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "New cron schedule",
|
|
},
|
|
"timeout": {
|
|
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
|
"description": "New timeout",
|
|
},
|
|
"enabled": {
|
|
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
|
"description": "Enable/disable function",
|
|
},
|
|
},
|
|
"required": ["function_id", "name"],
|
|
},
|
|
"scope": "write",
|
|
},
|
|
{
|
|
"name": "delete_function",
|
|
"method_name": "delete_function",
|
|
"description": "Delete a function and all its deployments.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"function_id": {"type": "string", "description": "Function ID to delete"}
|
|
},
|
|
"required": ["function_id"],
|
|
},
|
|
"scope": "admin",
|
|
},
|
|
# =====================
|
|
# DEPLOYMENTS (5)
|
|
# =====================
|
|
{
|
|
"name": "list_deployments",
|
|
"method_name": "list_deployments",
|
|
"description": "List all deployments of a function.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"function_id": {"type": "string", "description": "Function ID"},
|
|
"queries": {
|
|
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
|
"description": "Query strings for filtering",
|
|
},
|
|
"search": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "Search term",
|
|
},
|
|
},
|
|
"required": ["function_id"],
|
|
},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "get_deployment",
|
|
"method_name": "get_deployment",
|
|
"description": "Get deployment details including build status and logs.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"function_id": {"type": "string", "description": "Function ID"},
|
|
"deployment_id": {"type": "string", "description": "Deployment ID"},
|
|
},
|
|
"required": ["function_id", "deployment_id"],
|
|
},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "delete_deployment",
|
|
"method_name": "delete_deployment",
|
|
"description": "Delete a deployment.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"function_id": {"type": "string", "description": "Function ID"},
|
|
"deployment_id": {"type": "string", "description": "Deployment ID to delete"},
|
|
},
|
|
"required": ["function_id", "deployment_id"],
|
|
},
|
|
"scope": "write",
|
|
},
|
|
{
|
|
"name": "activate_deployment",
|
|
"method_name": "activate_deployment",
|
|
"description": "Activate a deployment (set as the active version for the function).",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"function_id": {"type": "string", "description": "Function ID"},
|
|
"deployment_id": {"type": "string", "description": "Deployment ID to activate"},
|
|
},
|
|
"required": ["function_id", "deployment_id"],
|
|
},
|
|
"scope": "write",
|
|
},
|
|
{
|
|
"name": "get_active_deployment",
|
|
"method_name": "get_active_deployment",
|
|
"description": "Get the currently active deployment for a function.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {"function_id": {"type": "string", "description": "Function ID"}},
|
|
"required": ["function_id"],
|
|
},
|
|
"scope": "read",
|
|
},
|
|
# =====================
|
|
# EXECUTIONS (4)
|
|
# =====================
|
|
{
|
|
"name": "list_executions",
|
|
"method_name": "list_executions",
|
|
"description": "List execution history for a function.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"function_id": {"type": "string", "description": "Function ID"},
|
|
"queries": {
|
|
"anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}],
|
|
"description": "Query strings for filtering",
|
|
},
|
|
"search": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "Search term",
|
|
},
|
|
},
|
|
"required": ["function_id"],
|
|
},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "get_execution",
|
|
"method_name": "get_execution",
|
|
"description": "Get execution details including response, logs, and timing.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"function_id": {"type": "string", "description": "Function ID"},
|
|
"execution_id": {"type": "string", "description": "Execution ID"},
|
|
},
|
|
"required": ["function_id", "execution_id"],
|
|
},
|
|
"scope": "read",
|
|
},
|
|
{
|
|
"name": "execute_function",
|
|
"method_name": "execute_function",
|
|
"description": "Execute a function immediately.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"function_id": {"type": "string", "description": "Function ID"},
|
|
"body": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "Request body (JSON string)",
|
|
},
|
|
"async_execution": {
|
|
"type": "boolean",
|
|
"description": "Run asynchronously (don't wait for response)",
|
|
"default": False,
|
|
},
|
|
"path": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"description": "Custom execution path",
|
|
},
|
|
"method": {
|
|
"type": "string",
|
|
"enum": ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
"description": "HTTP method",
|
|
"default": "POST",
|
|
},
|
|
"headers": {
|
|
"anyOf": [{"type": "object"}, {"type": "null"}],
|
|
"description": "Custom headers",
|
|
},
|
|
},
|
|
"required": ["function_id"],
|
|
},
|
|
"scope": "write",
|
|
},
|
|
{
|
|
"name": "delete_execution",
|
|
"method_name": "delete_execution",
|
|
"description": "Delete an execution log entry.",
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"function_id": {"type": "string", "description": "Function ID"},
|
|
"execution_id": {"type": "string", "description": "Execution ID to delete"},
|
|
},
|
|
"required": ["function_id", "execution_id"],
|
|
},
|
|
"scope": "write",
|
|
},
|
|
]
|
|
|
|
|
|
# =====================
|
|
# HANDLER FUNCTIONS
|
|
# =====================
|
|
|
|
|
|
async def list_functions(
|
|
client: AppwriteClient, queries: list[str] | None = None, search: str | None = None
|
|
) -> str:
|
|
"""List all functions."""
|
|
try:
|
|
result = await client.list_functions(queries=queries, search=search)
|
|
functions = result.get("functions", [])
|
|
|
|
response = {
|
|
"success": True,
|
|
"total": result.get("total", len(functions)),
|
|
"functions": functions,
|
|
}
|
|
return json.dumps(response, indent=2, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def get_function(client: AppwriteClient, function_id: str) -> str:
|
|
"""Get function by ID."""
|
|
try:
|
|
result = await client.get_function(function_id)
|
|
return json.dumps({"success": True, "function": result}, indent=2, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def create_function(
|
|
client: AppwriteClient,
|
|
function_id: str,
|
|
name: str,
|
|
runtime: str,
|
|
execute: list[str] | None = None,
|
|
events: list[str] | None = None,
|
|
schedule: str | None = None,
|
|
timeout: int = 15,
|
|
enabled: bool = True,
|
|
logging: bool = True,
|
|
entrypoint: str | None = None,
|
|
commands: str | None = None,
|
|
) -> str:
|
|
"""Create a new function."""
|
|
try:
|
|
result = await client.create_function(
|
|
function_id=function_id,
|
|
name=name,
|
|
runtime=runtime,
|
|
execute=execute,
|
|
events=events,
|
|
schedule=schedule,
|
|
timeout=timeout,
|
|
enabled=enabled,
|
|
logging=logging,
|
|
entrypoint=entrypoint,
|
|
commands=commands,
|
|
)
|
|
return json.dumps(
|
|
{
|
|
"success": True,
|
|
"message": f"Function '{name}' created successfully",
|
|
"function": result,
|
|
},
|
|
indent=2,
|
|
ensure_ascii=False,
|
|
)
|
|
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def update_function(
|
|
client: AppwriteClient,
|
|
function_id: str,
|
|
name: str,
|
|
runtime: str | None = None,
|
|
execute: list[str] | None = None,
|
|
events: list[str] | None = None,
|
|
schedule: str | None = None,
|
|
timeout: int | None = None,
|
|
enabled: bool | None = None,
|
|
) -> str:
|
|
"""Update function."""
|
|
try:
|
|
result = await client.update_function(
|
|
function_id=function_id,
|
|
name=name,
|
|
runtime=runtime,
|
|
execute=execute,
|
|
events=events,
|
|
schedule=schedule,
|
|
timeout=timeout,
|
|
enabled=enabled,
|
|
)
|
|
return json.dumps(
|
|
{"success": True, "message": "Function updated successfully", "function": result},
|
|
indent=2,
|
|
ensure_ascii=False,
|
|
)
|
|
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def delete_function(client: AppwriteClient, function_id: str) -> str:
|
|
"""Delete function."""
|
|
try:
|
|
await client.delete_function(function_id)
|
|
return json.dumps(
|
|
{"success": True, "message": f"Function '{function_id}' deleted successfully"}, indent=2
|
|
)
|
|
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def list_deployments(
|
|
client: AppwriteClient,
|
|
function_id: str,
|
|
queries: list[str] | None = None,
|
|
search: str | None = None,
|
|
) -> str:
|
|
"""List function deployments."""
|
|
try:
|
|
result = await client.list_deployments(
|
|
function_id=function_id, queries=queries, search=search
|
|
)
|
|
deployments = result.get("deployments", [])
|
|
|
|
response = {
|
|
"success": True,
|
|
"function_id": function_id,
|
|
"total": result.get("total", len(deployments)),
|
|
"deployments": deployments,
|
|
}
|
|
return json.dumps(response, indent=2, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def get_deployment(client: AppwriteClient, function_id: str, deployment_id: str) -> str:
|
|
"""Get deployment by ID."""
|
|
try:
|
|
result = await client.get_deployment(function_id, deployment_id)
|
|
return json.dumps({"success": True, "deployment": result}, indent=2, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def delete_deployment(client: AppwriteClient, function_id: str, deployment_id: str) -> str:
|
|
"""Delete deployment."""
|
|
try:
|
|
await client.delete_deployment(function_id, deployment_id)
|
|
return json.dumps(
|
|
{"success": True, "message": f"Deployment '{deployment_id}' deleted successfully"},
|
|
indent=2,
|
|
)
|
|
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def activate_deployment(client: AppwriteClient, function_id: str, deployment_id: str) -> str:
|
|
"""Activate deployment."""
|
|
try:
|
|
result = await client.update_deployment(function_id, deployment_id)
|
|
return json.dumps(
|
|
{
|
|
"success": True,
|
|
"message": f"Deployment '{deployment_id}' activated successfully",
|
|
"deployment": result,
|
|
},
|
|
indent=2,
|
|
ensure_ascii=False,
|
|
)
|
|
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def get_active_deployment(client: AppwriteClient, function_id: str) -> str:
|
|
"""Get active deployment for function."""
|
|
try:
|
|
func = await client.get_function(function_id)
|
|
deployment_id = func.get("deployment")
|
|
|
|
if not deployment_id:
|
|
return json.dumps(
|
|
{
|
|
"success": True,
|
|
"message": "No active deployment",
|
|
"function_id": function_id,
|
|
"active_deployment": None,
|
|
},
|
|
indent=2,
|
|
)
|
|
|
|
deployment = await client.get_deployment(function_id, deployment_id)
|
|
return json.dumps(
|
|
{"success": True, "function_id": function_id, "active_deployment": deployment},
|
|
indent=2,
|
|
ensure_ascii=False,
|
|
)
|
|
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def list_executions(
|
|
client: AppwriteClient,
|
|
function_id: str,
|
|
queries: list[str] | None = None,
|
|
search: str | None = None,
|
|
) -> str:
|
|
"""List function executions."""
|
|
try:
|
|
result = await client.list_executions(
|
|
function_id=function_id, queries=queries, search=search
|
|
)
|
|
executions = result.get("executions", [])
|
|
|
|
response = {
|
|
"success": True,
|
|
"function_id": function_id,
|
|
"total": result.get("total", len(executions)),
|
|
"executions": executions,
|
|
}
|
|
return json.dumps(response, indent=2, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def get_execution(client: AppwriteClient, function_id: str, execution_id: str) -> str:
|
|
"""Get execution by ID."""
|
|
try:
|
|
result = await client.get_execution(function_id, execution_id)
|
|
return json.dumps({"success": True, "execution": result}, indent=2, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def execute_function(
|
|
client: AppwriteClient,
|
|
function_id: str,
|
|
body: str | None = None,
|
|
async_execution: bool = False,
|
|
path: str | None = None,
|
|
method: str = "POST",
|
|
headers: dict[str, str] | None = None,
|
|
) -> str:
|
|
"""Execute function."""
|
|
try:
|
|
result = await client.create_execution(
|
|
function_id=function_id,
|
|
body=body,
|
|
async_execution=async_execution,
|
|
path=path,
|
|
method=method,
|
|
headers=headers,
|
|
)
|
|
|
|
response = {
|
|
"success": True,
|
|
"execution_id": result.get("$id"),
|
|
"status": result.get("status"),
|
|
"status_code": result.get("responseStatusCode"),
|
|
"duration": result.get("duration"),
|
|
"response_body": result.get("responseBody"),
|
|
"logs": result.get("logs"),
|
|
"errors": result.get("errors"),
|
|
}
|
|
|
|
if async_execution:
|
|
response["message"] = "Function execution started asynchronously"
|
|
else:
|
|
response["message"] = "Function executed successfully"
|
|
|
|
return json.dumps(response, indent=2, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|
|
|
|
|
|
async def delete_execution(client: AppwriteClient, function_id: str, execution_id: str) -> str:
|
|
"""Delete execution."""
|
|
try:
|
|
await client.delete_execution(function_id, execution_id)
|
|
return json.dumps(
|
|
{"success": True, "message": f"Execution '{execution_id}' deleted successfully"},
|
|
indent=2,
|
|
)
|
|
|
|
except Exception as e:
|
|
return json.dumps({"success": False, "error": str(e)}, indent=2)
|