"""
Appwrite REST API Client (Self-Hosted)
Handles all HTTP communication with Appwrite Self-Hosted APIs.
Uses X-Appwrite-Project and X-Appwrite-Key headers for authentication.
APIs:
- Databases (/databases/) - Database, Collections, Documents
- Users (/users/) - User management
- Teams (/teams/) - Team management
- Storage (/storage/) - Buckets and Files
- Functions (/functions/) - Serverless Functions
- Messaging (/messaging/) - Email, SMS, Push
- Health (/health/) - Service health checks
- Avatars (/avatars/) - Avatar utilities
"""
import base64
import logging
from typing import Any
import aiohttp
class AppwriteClient:
"""
Appwrite Self-Hosted API client.
All requests include X-Appwrite-Project and X-Appwrite-Key headers.
API key provides admin access and bypasses rate limits.
"""
def __init__(self, base_url: str, project_id: str, api_key: str):
"""
Initialize Appwrite API client.
Args:
base_url: Appwrite instance URL (e.g., https://appwrite.example.com/v1)
project_id: Appwrite project ID
api_key: API key with required scopes
"""
self.base_url = base_url.rstrip("/")
self.project_id = project_id
self.api_key = api_key
# Initialize logger
self.logger = logging.getLogger(f"AppwriteClient.{base_url}")
def _get_headers(self, additional_headers: dict | None = None) -> dict[str, str]:
"""
Get request headers with API key authentication.
Args:
additional_headers: Additional headers to include
Returns:
Dict: Headers with authentication
"""
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"X-Appwrite-Project": self.project_id,
"X-Appwrite-Key": self.api_key,
}
if additional_headers:
headers.update(additional_headers)
return headers
async def request(
self,
method: str,
endpoint: str,
params: dict | list | None = None,
json_data: dict | list | None = None,
data: bytes | None = None,
headers_override: dict | None = None,
) -> Any:
"""
Make authenticated request to Appwrite API.
Args:
method: HTTP method
endpoint: API endpoint (with leading /)
params: Query parameters (dict or list of tuples for repeated keys)
json_data: JSON body data
data: Raw binary data (for file uploads)
headers_override: Override/add headers
Returns:
API response
Raises:
Exception: On API errors
"""
url = f"{self.base_url}{endpoint}"
headers = self._get_headers(headers_override)
# Remove Content-Type for binary data (multipart)
if data is not None:
headers.pop("Content-Type", None)
# Process params: convert dict to list of tuples, handle lists and booleans
if params:
if isinstance(params, dict):
clean_params = []
for k, v in params.items():
if v is None:
continue
# Handle list values - create multiple tuples with same key
if isinstance(v, list):
for item in v:
clean_params.append((k, item))
# Convert booleans to lowercase strings for URL params
elif isinstance(v, bool):
clean_params.append((k, "true" if v else "false"))
else:
clean_params.append((k, v))
params = clean_params
# If already a list, just filter None values
elif isinstance(params, list):
params = [(k, v) for k, v in params if v is not None]
# Process JSON body: filter None values and coerce string types back to native
if isinstance(json_data, dict):
json_data = self._coerce_json_types(json_data)
async with aiohttp.ClientSession() as session:
kwargs = {
"method": method,
"url": url,
"headers": headers,
}
if params:
kwargs["params"] = params
if json_data is not None:
kwargs["json"] = json_data
if data:
kwargs["data"] = data
async with session.request(**kwargs) as response:
self.logger.debug(f"Response status: {response.status}")
# Handle 204 No Content
if response.status == 204:
return {"success": True}
# Handle binary responses (file downloads)
content_type = response.headers.get("Content-Type", "")
if "application/json" not in content_type and response.status < 400:
# Return binary data as base64
binary_data = await response.read()
return {
"data": base64.b64encode(binary_data).decode(),
"content_type": content_type,
"size": len(binary_data),
}
# Parse JSON response
try:
response_data = await response.json()
except Exception:
response_text = await response.text()
if response.status >= 400:
raise Exception(
f"Appwrite API error (status {response.status}): {response_text}"
)
return {"success": True, "message": response_text}
# Check for errors
if response.status >= 400:
error_msg = self._extract_error_message(response_data)
raise Exception(f"Appwrite API error (status {response.status}): {error_msg}")
return response_data
def _extract_error_message(self, response_data: Any) -> str:
"""Extract error message from Appwrite error response."""
if isinstance(response_data, dict):
if "message" in response_data:
return response_data["message"]
if "error" in response_data:
return response_data["error"]
if "type" in response_data:
return (
f"{response_data.get('type')}: {response_data.get('message', 'Unknown error')}"
)
return str(response_data)
def _coerce_json_types(self, data: dict) -> dict:
"""
Coerce string representations back to native JSON types.
MCP clients may send "true"/"false" strings instead of boolean,
or "123" strings instead of integers. This method converts them
back to native types for proper Appwrite API compatibility.
Args:
data: Dictionary with potential string-encoded values
Returns:
Dictionary with properly typed values
"""
result = {}
for k, v in data.items():
if v is None:
continue # Filter out None values
# Handle nested dictionaries
if isinstance(v, dict):
result[k] = self._coerce_json_types(v)
# Handle lists
elif isinstance(v, list):
result[k] = [
(
self._coerce_json_types(item)
if isinstance(item, dict)
else self._coerce_value(item)
)
for item in v
]
else:
result[k] = self._coerce_value(v)
return result
def _coerce_value(self, value: Any) -> Any:
"""
Coerce a single value to its native type.
Converts:
- "true"/"false" strings to boolean
- Numeric strings to int/float
- Preserves other values as-is
"""
if isinstance(value, str):
# Boolean conversion
if value.lower() == "true":
return True
if value.lower() == "false":
return False
# Integer conversion (only for pure numeric strings)
if value.lstrip("-").isdigit():
try:
return int(value)
except ValueError:
pass
# Float conversion
try:
if "." in value and value.replace(".", "", 1).lstrip("-").isdigit():
return float(value)
except ValueError:
pass
return value
# =====================
# DATABASES
# =====================
async def list_databases(
self, queries: list[str] | None = None, search: str | None = None
) -> dict[str, Any]:
"""List all databases."""
params = {}
if queries:
params["queries[]"] = queries
if search:
params["search"] = search
return await self.request("GET", "/databases", params=params)
async def get_database(self, database_id: str) -> dict[str, Any]:
"""Get database by ID."""
return await self.request("GET", f"/databases/{database_id}")
async def create_database(
self, database_id: str, name: str, enabled: bool = True
) -> dict[str, Any]:
"""Create a new database."""
data = {"databaseId": database_id, "name": name, "enabled": enabled}
return await self.request("POST", "/databases", json_data=data)
async def update_database(
self, database_id: str, name: str, enabled: bool | None = None
) -> dict[str, Any]:
"""Update database."""
data = {"name": name}
if enabled is not None:
data["enabled"] = enabled
return await self.request("PUT", f"/databases/{database_id}", json_data=data)
async def delete_database(self, database_id: str) -> dict[str, Any]:
"""Delete database."""
return await self.request("DELETE", f"/databases/{database_id}")
# =====================
# COLLECTIONS
# =====================
async def list_collections(
self, database_id: str, queries: list[str] | None = None, search: str | None = None
) -> dict[str, Any]:
"""List collections in a database."""
params = {}
if queries:
params["queries[]"] = queries
if search:
params["search"] = search
return await self.request("GET", f"/databases/{database_id}/collections", params=params)
async def get_collection(self, database_id: str, collection_id: str) -> dict[str, Any]:
"""Get collection by ID."""
return await self.request("GET", f"/databases/{database_id}/collections/{collection_id}")
async def create_collection(
self,
database_id: str,
collection_id: str,
name: str,
permissions: list[str] | None = None,
document_security: bool = True,
enabled: bool = True,
) -> dict[str, Any]:
"""Create a new collection."""
data = {
"collectionId": collection_id,
"name": name,
"documentSecurity": document_security,
"enabled": enabled,
}
if permissions:
data["permissions"] = permissions
return await self.request("POST", f"/databases/{database_id}/collections", json_data=data)
async def update_collection(
self,
database_id: str,
collection_id: str,
name: str,
permissions: list[str] | None = None,
document_security: bool | None = None,
enabled: bool | None = None,
) -> dict[str, Any]:
"""Update collection."""
data = {"name": name}
if permissions is not None:
data["permissions"] = permissions
if document_security is not None:
data["documentSecurity"] = document_security
if enabled is not None:
data["enabled"] = enabled
return await self.request(
"PUT", f"/databases/{database_id}/collections/{collection_id}", json_data=data
)
async def delete_collection(self, database_id: str, collection_id: str) -> dict[str, Any]:
"""Delete collection."""
return await self.request("DELETE", f"/databases/{database_id}/collections/{collection_id}")
# =====================
# ATTRIBUTES
# =====================
async def list_attributes(
self, database_id: str, collection_id: str, queries: list[str] | None = None
) -> dict[str, Any]:
"""List attributes of a collection."""
params = {}
if queries:
params["queries[]"] = queries
return await self.request(
"GET", f"/databases/{database_id}/collections/{collection_id}/attributes", params=params
)
async def create_string_attribute(
self,
database_id: str,
collection_id: str,
key: str,
size: int,
required: bool = False,
default: str | None = None,
array: bool = False,
encrypt: bool = False,
) -> dict[str, Any]:
"""Create a string attribute."""
data = {"key": key, "size": size, "required": required, "array": array, "encrypt": encrypt}
if default is not None:
data["default"] = default
return await self.request(
"POST",
f"/databases/{database_id}/collections/{collection_id}/attributes/string",
json_data=data,
)
async def create_integer_attribute(
self,
database_id: str,
collection_id: str,
key: str,
required: bool = False,
min: int | None = None,
max: int | None = None,
default: int | None = None,
array: bool = False,
) -> dict[str, Any]:
"""Create an integer attribute."""
data = {"key": key, "required": required, "array": array}
if min is not None:
data["min"] = min
if max is not None:
data["max"] = max
if default is not None:
data["default"] = default
return await self.request(
"POST",
f"/databases/{database_id}/collections/{collection_id}/attributes/integer",
json_data=data,
)
async def create_float_attribute(
self,
database_id: str,
collection_id: str,
key: str,
required: bool = False,
min: float | None = None,
max: float | None = None,
default: float | None = None,
array: bool = False,
) -> dict[str, Any]:
"""Create a float attribute."""
data = {"key": key, "required": required, "array": array}
if min is not None:
data["min"] = min
if max is not None:
data["max"] = max
if default is not None:
data["default"] = default
return await self.request(
"POST",
f"/databases/{database_id}/collections/{collection_id}/attributes/float",
json_data=data,
)
async def create_boolean_attribute(
self,
database_id: str,
collection_id: str,
key: str,
required: bool = False,
default: bool | None = None,
array: bool = False,
) -> dict[str, Any]:
"""Create a boolean attribute."""
data = {"key": key, "required": required, "array": array}
if default is not None:
data["default"] = default
return await self.request(
"POST",
f"/databases/{database_id}/collections/{collection_id}/attributes/boolean",
json_data=data,
)
async def create_datetime_attribute(
self,
database_id: str,
collection_id: str,
key: str,
required: bool = False,
default: str | None = None,
array: bool = False,
) -> dict[str, Any]:
"""Create a datetime attribute."""
data = {"key": key, "required": required, "array": array}
if default is not None:
data["default"] = default
return await self.request(
"POST",
f"/databases/{database_id}/collections/{collection_id}/attributes/datetime",
json_data=data,
)
async def create_email_attribute(
self,
database_id: str,
collection_id: str,
key: str,
required: bool = False,
default: str | None = None,
array: bool = False,
) -> dict[str, Any]:
"""Create an email attribute."""
data = {"key": key, "required": required, "array": array}
if default is not None:
data["default"] = default
return await self.request(
"POST",
f"/databases/{database_id}/collections/{collection_id}/attributes/email",
json_data=data,
)
async def create_url_attribute(
self,
database_id: str,
collection_id: str,
key: str,
required: bool = False,
default: str | None = None,
array: bool = False,
) -> dict[str, Any]:
"""Create a URL attribute."""
data = {"key": key, "required": required, "array": array}
if default is not None:
data["default"] = default
return await self.request(
"POST",
f"/databases/{database_id}/collections/{collection_id}/attributes/url",
json_data=data,
)
async def create_ip_attribute(
self,
database_id: str,
collection_id: str,
key: str,
required: bool = False,
default: str | None = None,
array: bool = False,
) -> dict[str, Any]:
"""Create an IP address attribute."""
data = {"key": key, "required": required, "array": array}
if default is not None:
data["default"] = default
return await self.request(
"POST",
f"/databases/{database_id}/collections/{collection_id}/attributes/ip",
json_data=data,
)
async def create_enum_attribute(
self,
database_id: str,
collection_id: str,
key: str,
elements: list[str],
required: bool = False,
default: str | None = None,
array: bool = False,
) -> dict[str, Any]:
"""Create an enum attribute."""
data = {"key": key, "elements": elements, "required": required, "array": array}
if default is not None:
data["default"] = default
return await self.request(
"POST",
f"/databases/{database_id}/collections/{collection_id}/attributes/enum",
json_data=data,
)
async def create_relationship_attribute(
self,
database_id: str,
collection_id: str,
related_collection_id: str,
type: str,
two_way: bool = False,
key: str | None = None,
two_way_key: str | None = None,
on_delete: str = "setNull",
) -> dict[str, Any]:
"""Create a relationship attribute."""
data = {
"relatedCollectionId": related_collection_id,
"type": type, # oneToOne, oneToMany, manyToOne, manyToMany
"twoWay": two_way,
"onDelete": on_delete, # setNull, cascade, restrict
}
if key:
data["key"] = key
if two_way_key:
data["twoWayKey"] = two_way_key
return await self.request(
"POST",
f"/databases/{database_id}/collections/{collection_id}/attributes/relationship",
json_data=data,
)
async def get_attribute(self, database_id: str, collection_id: str, key: str) -> dict[str, Any]:
"""Get attribute by key."""
return await self.request(
"GET", f"/databases/{database_id}/collections/{collection_id}/attributes/{key}"
)
async def delete_attribute(
self, database_id: str, collection_id: str, key: str
) -> dict[str, Any]:
"""Delete attribute."""
return await self.request(
"DELETE", f"/databases/{database_id}/collections/{collection_id}/attributes/{key}"
)
# =====================
# INDEXES
# =====================
async def list_indexes(
self, database_id: str, collection_id: str, queries: list[str] | None = None
) -> dict[str, Any]:
"""List indexes of a collection."""
params = {}
if queries:
params["queries[]"] = queries
return await self.request(
"GET", f"/databases/{database_id}/collections/{collection_id}/indexes", params=params
)
async def create_index(
self,
database_id: str,
collection_id: str,
key: str,
type: str,
attributes: list[str],
orders: list[str] | None = None,
) -> dict[str, Any]:
"""Create an index."""
data = {"key": key, "type": type, "attributes": attributes} # key, unique, fulltext
if orders:
data["orders"] = orders
return await self.request(
"POST", f"/databases/{database_id}/collections/{collection_id}/indexes", json_data=data
)
async def get_index(self, database_id: str, collection_id: str, key: str) -> dict[str, Any]:
"""Get index by key."""
return await self.request(
"GET", f"/databases/{database_id}/collections/{collection_id}/indexes/{key}"
)
async def delete_index(self, database_id: str, collection_id: str, key: str) -> dict[str, Any]:
"""Delete index."""
return await self.request(
"DELETE", f"/databases/{database_id}/collections/{collection_id}/indexes/{key}"
)
# =====================
# DOCUMENTS
# =====================
async def list_documents(
self, database_id: str, collection_id: str, queries: list[str] | None = None
) -> dict[str, Any]:
"""List documents in a collection."""
params = {}
if queries:
# Send each query as separate queries[] param - Appwrite REST API format
params["queries[]"] = queries
return await self.request(
"GET", f"/databases/{database_id}/collections/{collection_id}/documents", params=params
)
async def get_document(
self,
database_id: str,
collection_id: str,
document_id: str,
queries: list[str] | None = None,
) -> dict[str, Any]:
"""Get document by ID."""
params = {}
if queries:
params["queries[]"] = queries
return await self.request(
"GET",
f"/databases/{database_id}/collections/{collection_id}/documents/{document_id}",
params=params,
)
async def create_document(
self,
database_id: str,
collection_id: str,
document_id: str,
data: dict[str, Any],
permissions: list[str] | None = None,
) -> dict[str, Any]:
"""Create a new document."""
body = {"documentId": document_id, "data": data}
if permissions:
body["permissions"] = permissions
return await self.request(
"POST",
f"/databases/{database_id}/collections/{collection_id}/documents",
json_data=body,
)
async def update_document(
self,
database_id: str,
collection_id: str,
document_id: str,
data: dict[str, Any] | None = None,
permissions: list[str] | None = None,
) -> dict[str, Any]:
"""Update document."""
body = {}
if data:
body["data"] = data
if permissions is not None:
body["permissions"] = permissions
return await self.request(
"PATCH",
f"/databases/{database_id}/collections/{collection_id}/documents/{document_id}",
json_data=body,
)
async def delete_document(
self, database_id: str, collection_id: str, document_id: str
) -> dict[str, Any]:
"""Delete document."""
return await self.request(
"DELETE",
f"/databases/{database_id}/collections/{collection_id}/documents/{document_id}",
)
# =====================
# USERS
# =====================
async def list_users(
self, queries: list[str] | None = None, search: str | None = None
) -> dict[str, Any]:
"""List users."""
params = {}
if queries:
for i, q in enumerate(queries):
params[f"queries[{i}]"] = q
if search:
params["search"] = search
return await self.request("GET", "/users", params=params)
async def get_user(self, user_id: str) -> dict[str, Any]:
"""Get user by ID."""
return await self.request("GET", f"/users/{user_id}")
async def create_user(
self,
user_id: str,
email: str | None = None,
phone: str | None = None,
password: str | None = None,
name: str | None = None,
) -> dict[str, Any]:
"""Create a new user."""
data = {"userId": user_id}
if email:
data["email"] = email
if phone:
data["phone"] = phone
if password:
data["password"] = password
if name:
data["name"] = name
return await self.request("POST", "/users", json_data=data)
async def update_user_name(self, user_id: str, name: str) -> dict[str, Any]:
"""Update user name."""
return await self.request("PATCH", f"/users/{user_id}/name", json_data={"name": name})
async def update_user_email(self, user_id: str, email: str) -> dict[str, Any]:
"""Update user email."""
return await self.request("PATCH", f"/users/{user_id}/email", json_data={"email": email})
async def update_user_phone(self, user_id: str, number: str) -> dict[str, Any]:
"""Update user phone."""
return await self.request("PATCH", f"/users/{user_id}/phone", json_data={"number": number})
async def update_user_password(self, user_id: str, password: str) -> dict[str, Any]:
"""Update user password."""
return await self.request(
"PATCH", f"/users/{user_id}/password", json_data={"password": password}
)
async def update_user_status(self, user_id: str, status: bool) -> dict[str, Any]:
"""Update user status (enable/disable)."""
return await self.request("PATCH", f"/users/{user_id}/status", json_data={"status": status})
async def update_user_labels(self, user_id: str, labels: list[str]) -> dict[str, Any]:
"""Update user labels."""
return await self.request("PUT", f"/users/{user_id}/labels", json_data={"labels": labels})
async def update_user_prefs(self, user_id: str, prefs: dict[str, Any]) -> dict[str, Any]:
"""Update user preferences."""
return await self.request("PATCH", f"/users/{user_id}/prefs", json_data={"prefs": prefs})
async def delete_user(self, user_id: str) -> dict[str, Any]:
"""Delete user."""
return await self.request("DELETE", f"/users/{user_id}")
async def list_user_sessions(self, user_id: str) -> dict[str, Any]:
"""List user sessions."""
return await self.request("GET", f"/users/{user_id}/sessions")
async def delete_user_sessions(self, user_id: str) -> dict[str, Any]:
"""Delete all user sessions."""
return await self.request("DELETE", f"/users/{user_id}/sessions")
async def delete_user_session(self, user_id: str, session_id: str) -> dict[str, Any]:
"""Delete a specific user session."""
return await self.request("DELETE", f"/users/{user_id}/sessions/{session_id}")
# =====================
# TEAMS
# =====================
async def list_teams(
self, queries: list[str] | None = None, search: str | None = None
) -> dict[str, Any]:
"""List teams."""
params = {}
if queries:
for i, q in enumerate(queries):
params[f"queries[{i}]"] = q
if search:
params["search"] = search
return await self.request("GET", "/teams", params=params)
async def get_team(self, team_id: str) -> dict[str, Any]:
"""Get team by ID."""
return await self.request("GET", f"/teams/{team_id}")
async def create_team(
self, team_id: str, name: str, roles: list[str] | None = None
) -> dict[str, Any]:
"""Create a new team."""
data = {"teamId": team_id, "name": name}
if roles:
data["roles"] = roles
return await self.request("POST", "/teams", json_data=data)
async def update_team(self, team_id: str, name: str) -> dict[str, Any]:
"""Update team name."""
return await self.request("PUT", f"/teams/{team_id}", json_data={"name": name})
async def delete_team(self, team_id: str) -> dict[str, Any]:
"""Delete team."""
return await self.request("DELETE", f"/teams/{team_id}")
async def list_team_memberships(
self, team_id: str, queries: list[str] | None = None, search: str | None = None
) -> dict[str, Any]:
"""List team memberships."""
params = {}
if queries:
for i, q in enumerate(queries):
params[f"queries[{i}]"] = q
if search:
params["search"] = search
return await self.request("GET", f"/teams/{team_id}/memberships", params=params)
async def create_team_membership(
self,
team_id: str,
roles: list[str],
email: str | None = None,
user_id: str | None = None,
phone: str | None = None,
url: str | None = None,
name: str | None = None,
) -> dict[str, Any]:
"""Create team membership (invite)."""
data = {"roles": roles}
if email:
data["email"] = email
if user_id:
data["userId"] = user_id
if phone:
data["phone"] = phone
if url:
data["url"] = url
if name:
data["name"] = name
return await self.request("POST", f"/teams/{team_id}/memberships", json_data=data)
async def update_membership(
self, team_id: str, membership_id: str, roles: list[str]
) -> dict[str, Any]:
"""Update membership roles."""
return await self.request(
"PATCH", f"/teams/{team_id}/memberships/{membership_id}", json_data={"roles": roles}
)
async def delete_membership(self, team_id: str, membership_id: str) -> dict[str, Any]:
"""Delete team membership."""
return await self.request("DELETE", f"/teams/{team_id}/memberships/{membership_id}")
# =====================
# STORAGE
# =====================
async def list_buckets(
self, queries: list[str] | None = None, search: str | None = None
) -> dict[str, Any]:
"""List storage buckets."""
params = {}
if queries:
for i, q in enumerate(queries):
params[f"queries[{i}]"] = q
if search:
params["search"] = search
return await self.request("GET", "/storage/buckets", params=params)
async def get_bucket(self, bucket_id: str) -> dict[str, Any]:
"""Get bucket by ID."""
return await self.request("GET", f"/storage/buckets/{bucket_id}")
async def create_bucket(
self,
bucket_id: str,
name: str,
permissions: list[str] | None = None,
file_security: bool = True,
enabled: bool = True,
maximum_file_size: int | None = None,
allowed_file_extensions: list[str] | None = None,
compression: str = "none",
encryption: bool = True,
antivirus: bool = True,
) -> dict[str, Any]:
"""Create a new bucket."""
data = {
"bucketId": bucket_id,
"name": name,
"fileSecurity": file_security,
"enabled": enabled,
"compression": compression,
"encryption": encryption,
"antivirus": antivirus,
}
if permissions:
data["permissions"] = permissions
if maximum_file_size:
data["maximumFileSize"] = maximum_file_size
if allowed_file_extensions:
data["allowedFileExtensions"] = allowed_file_extensions
return await self.request("POST", "/storage/buckets", json_data=data)
async def update_bucket(
self,
bucket_id: str,
name: str,
permissions: list[str] | None = None,
file_security: bool | None = None,
enabled: bool | None = None,
maximum_file_size: int | None = None,
allowed_file_extensions: list[str] | None = None,
compression: str | None = None,
encryption: bool | None = None,
antivirus: bool | None = None,
) -> dict[str, Any]:
"""Update bucket."""
data = {"name": name}
if permissions is not None:
data["permissions"] = permissions
if file_security is not None:
data["fileSecurity"] = file_security
if enabled is not None:
data["enabled"] = enabled
if maximum_file_size is not None:
data["maximumFileSize"] = maximum_file_size
if allowed_file_extensions is not None:
data["allowedFileExtensions"] = allowed_file_extensions
if compression is not None:
data["compression"] = compression
if encryption is not None:
data["encryption"] = encryption
if antivirus is not None:
data["antivirus"] = antivirus
return await self.request("PUT", f"/storage/buckets/{bucket_id}", json_data=data)
async def delete_bucket(self, bucket_id: str) -> dict[str, Any]:
"""Delete bucket."""
return await self.request("DELETE", f"/storage/buckets/{bucket_id}")
async def list_files(
self, bucket_id: str, queries: list[str] | None = None, search: str | None = None
) -> dict[str, Any]:
"""List files in a bucket."""
params = {}
if queries:
for i, q in enumerate(queries):
params[f"queries[{i}]"] = q
if search:
params["search"] = search
return await self.request("GET", f"/storage/buckets/{bucket_id}/files", params=params)
async def get_file(self, bucket_id: str, file_id: str) -> dict[str, Any]:
"""Get file metadata."""
return await self.request("GET", f"/storage/buckets/{bucket_id}/files/{file_id}")
async def delete_file(self, bucket_id: str, file_id: str) -> dict[str, Any]:
"""Delete file."""
return await self.request("DELETE", f"/storage/buckets/{bucket_id}/files/{file_id}")
async def get_file_download(self, bucket_id: str, file_id: str) -> dict[str, Any]:
"""Download file (returns base64 content)."""
return await self.request("GET", f"/storage/buckets/{bucket_id}/files/{file_id}/download")
async def get_file_preview(
self,
bucket_id: str,
file_id: str,
width: int | None = None,
height: int | None = None,
gravity: str | None = None,
quality: int | None = None,
border_width: int | None = None,
border_color: str | None = None,
border_radius: int | None = None,
opacity: float | None = None,
rotation: int | None = None,
background: str | None = None,
output: str | None = None,
) -> dict[str, Any]:
"""Get file preview (image transformation)."""
params = {}
if width:
params["width"] = width
if height:
params["height"] = height
if gravity:
params["gravity"] = gravity
if quality:
params["quality"] = quality
if border_width:
params["borderWidth"] = border_width
if border_color:
params["borderColor"] = border_color
if border_radius:
params["borderRadius"] = border_radius
if opacity:
params["opacity"] = opacity
if rotation:
params["rotation"] = rotation
if background:
params["background"] = background
if output:
params["output"] = output
return await self.request(
"GET", f"/storage/buckets/{bucket_id}/files/{file_id}/preview", params=params
)
async def get_file_view(self, bucket_id: str, file_id: str) -> dict[str, Any]:
"""Get file for viewing in browser."""
return await self.request("GET", f"/storage/buckets/{bucket_id}/files/{file_id}/view")
# =====================
# FUNCTIONS
# =====================
async def list_functions(
self, queries: list[str] | None = None, search: str | None = None
) -> dict[str, Any]:
"""List functions."""
params = {}
if queries:
for i, q in enumerate(queries):
params[f"queries[{i}]"] = q
if search:
params["search"] = search
return await self.request("GET", "/functions", params=params)
async def get_function(self, function_id: str) -> dict[str, Any]:
"""Get function by ID."""
return await self.request("GET", f"/functions/{function_id}")
async def create_function(
self,
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,
scopes: list[str] | None = None,
installation_id: str | None = None,
provider_repository_id: str | None = None,
provider_branch: str | None = None,
provider_silent_mode: bool = False,
provider_root_directory: str | None = None,
) -> dict[str, Any]:
"""Create a new function."""
data = {
"functionId": function_id,
"name": name,
"runtime": runtime,
"timeout": timeout,
"enabled": enabled,
"logging": logging,
"providerSilentMode": provider_silent_mode,
}
if execute:
data["execute"] = execute
if events:
data["events"] = events
if schedule:
data["schedule"] = schedule
if entrypoint:
data["entrypoint"] = entrypoint
if commands:
data["commands"] = commands
if scopes:
data["scopes"] = scopes
if installation_id:
data["installationId"] = installation_id
if provider_repository_id:
data["providerRepositoryId"] = provider_repository_id
if provider_branch:
data["providerBranch"] = provider_branch
if provider_root_directory:
data["providerRootDirectory"] = provider_root_directory
return await self.request("POST", "/functions", json_data=data)
async def update_function(
self,
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,
logging: bool | None = None,
entrypoint: str | None = None,
commands: str | None = None,
scopes: list[str] | None = None,
) -> dict[str, Any]:
"""Update function."""
data = {"name": name}
if runtime:
data["runtime"] = runtime
if execute is not None:
data["execute"] = execute
if events is not None:
data["events"] = events
if schedule is not None:
data["schedule"] = schedule
if timeout is not None:
data["timeout"] = timeout
if enabled is not None:
data["enabled"] = enabled
if logging is not None:
data["logging"] = logging
if entrypoint:
data["entrypoint"] = entrypoint
if commands:
data["commands"] = commands
if scopes is not None:
data["scopes"] = scopes
return await self.request("PUT", f"/functions/{function_id}", json_data=data)
async def delete_function(self, function_id: str) -> dict[str, Any]:
"""Delete function."""
return await self.request("DELETE", f"/functions/{function_id}")
async def list_deployments(
self, function_id: str, queries: list[str] | None = None, search: str | None = None
) -> dict[str, Any]:
"""List function deployments."""
params = {}
if queries:
for i, q in enumerate(queries):
params[f"queries[{i}]"] = q
if search:
params["search"] = search
return await self.request("GET", f"/functions/{function_id}/deployments", params=params)
async def get_deployment(self, function_id: str, deployment_id: str) -> dict[str, Any]:
"""Get deployment by ID."""
return await self.request("GET", f"/functions/{function_id}/deployments/{deployment_id}")
async def delete_deployment(self, function_id: str, deployment_id: str) -> dict[str, Any]:
"""Delete deployment."""
return await self.request("DELETE", f"/functions/{function_id}/deployments/{deployment_id}")
async def update_deployment(self, function_id: str, deployment_id: str) -> dict[str, Any]:
"""Activate deployment (set as active)."""
return await self.request("PATCH", f"/functions/{function_id}/deployments/{deployment_id}")
async def list_executions(
self, function_id: str, queries: list[str] | None = None, search: str | None = None
) -> dict[str, Any]:
"""List function executions."""
params = {}
if queries:
for i, q in enumerate(queries):
params[f"queries[{i}]"] = q
if search:
params["search"] = search
return await self.request("GET", f"/functions/{function_id}/executions", params=params)
async def get_execution(self, function_id: str, execution_id: str) -> dict[str, Any]:
"""Get execution by ID."""
return await self.request("GET", f"/functions/{function_id}/executions/{execution_id}")
async def create_execution(
self,
function_id: str,
body: str | None = None,
async_execution: bool = False,
path: str | None = None,
method: str = "POST",
headers: dict[str, str] | None = None,
scheduled_at: str | None = None,
) -> dict[str, Any]:
"""Execute a function."""
data = {"async": async_execution, "method": method}
if body:
data["body"] = body
if path:
data["path"] = path
if headers:
data["headers"] = headers
if scheduled_at:
data["scheduledAt"] = scheduled_at
return await self.request("POST", f"/functions/{function_id}/executions", json_data=data)
async def delete_execution(self, function_id: str, execution_id: str) -> dict[str, Any]:
"""Delete execution."""
return await self.request("DELETE", f"/functions/{function_id}/executions/{execution_id}")
# =====================
# HEALTH
# =====================
async def health(self) -> dict[str, Any]:
"""Get general health status."""
return await self.request("GET", "/health")
async def health_db(self) -> dict[str, Any]:
"""Get database health status."""
return await self.request("GET", "/health/db")
async def health_cache(self) -> dict[str, Any]:
"""Get cache health status."""
return await self.request("GET", "/health/cache")
async def health_pubsub(self) -> dict[str, Any]:
"""Get pub/sub health status."""
return await self.request("GET", "/health/pubsub")
async def health_queue(self) -> dict[str, Any]:
"""Get queue health status."""
return await self.request("GET", "/health/queue")
async def health_storage_local(self) -> dict[str, Any]:
"""Get local storage health status."""
return await self.request("GET", "/health/storage/local")
async def health_time(self) -> dict[str, Any]:
"""Get time sync health status."""
return await self.request("GET", "/health/time")
async def health_certificate(self, domain: str | None = None) -> dict[str, Any]:
"""Get SSL certificate health status."""
params = {}
if domain:
params["domain"] = domain
return await self.request("GET", "/health/certificate", params=params)
# =====================
# AVATARS
# =====================
async def get_avatar_initials(
self,
name: str | None = None,
width: int = 100,
height: int = 100,
background: str | None = None,
) -> dict[str, Any]:
"""Get avatar image from initials."""
params = {"width": width, "height": height}
if name:
params["name"] = name
if background:
params["background"] = background
return await self.request("GET", "/avatars/initials", params=params)
async def get_avatar_image(
self, url: str, width: int = 400, height: int = 400
) -> dict[str, Any]:
"""Get avatar from URL."""
params = {"url": url, "width": width, "height": height}
return await self.request("GET", "/avatars/image", params=params)
async def get_favicon(self, url: str) -> dict[str, Any]:
"""Get website favicon."""
return await self.request("GET", "/avatars/favicon", params={"url": url})
async def get_qr_code(
self, text: str, size: int = 400, margin: int = 1, download: bool = False
) -> dict[str, Any]:
"""Generate QR code."""
params = {"text": text, "size": size, "margin": margin, "download": download}
return await self.request("GET", "/avatars/qr", params=params)
# =====================
# MESSAGING (Server SDK)
# =====================
async def list_providers(
self, queries: list[str] | None = None, search: str | None = None
) -> dict[str, Any]:
"""List messaging providers."""
params = {}
if queries:
for i, q in enumerate(queries):
params[f"queries[{i}]"] = q
if search:
params["search"] = search
return await self.request("GET", "/messaging/providers", params=params)
async def list_topics(
self, queries: list[str] | None = None, search: str | None = None
) -> dict[str, Any]:
"""List messaging topics."""
params = {}
if queries:
for i, q in enumerate(queries):
params[f"queries[{i}]"] = q
if search:
params["search"] = search
return await self.request("GET", "/messaging/topics", params=params)
async def get_topic(self, topic_id: str) -> dict[str, Any]:
"""Get topic by ID."""
return await self.request("GET", f"/messaging/topics/{topic_id}")
async def create_topic(
self, topic_id: str, name: str, subscribe: list[str] | None = None
) -> dict[str, Any]:
"""Create a messaging topic."""
data = {"topicId": topic_id, "name": name}
if subscribe:
data["subscribe"] = subscribe
return await self.request("POST", "/messaging/topics", json_data=data)
async def update_topic(
self, topic_id: str, name: str | None = None, subscribe: list[str] | None = None
) -> dict[str, Any]:
"""Update topic."""
data = {}
if name:
data["name"] = name
if subscribe is not None:
data["subscribe"] = subscribe
return await self.request("PATCH", f"/messaging/topics/{topic_id}", json_data=data)
async def delete_topic(self, topic_id: str) -> dict[str, Any]:
"""Delete topic."""
return await self.request("DELETE", f"/messaging/topics/{topic_id}")
async def create_subscriber(
self, topic_id: str, subscriber_id: str, target_id: str
) -> dict[str, Any]:
"""Add subscriber to topic."""
data = {"subscriberId": subscriber_id, "targetId": target_id}
return await self.request(
"POST", f"/messaging/topics/{topic_id}/subscribers", json_data=data
)
async def delete_subscriber(self, topic_id: str, subscriber_id: str) -> dict[str, Any]:
"""Remove subscriber from topic."""
return await self.request(
"DELETE", f"/messaging/topics/{topic_id}/subscribers/{subscriber_id}"
)
async def list_messages(
self, queries: list[str] | None = None, search: str | None = None
) -> dict[str, Any]:
"""List messages."""
params = {}
if queries:
for i, q in enumerate(queries):
params[f"queries[{i}]"] = q
if search:
params["search"] = search
return await self.request("GET", "/messaging/messages", params=params)
async def get_message(self, message_id: str) -> dict[str, Any]:
"""Get message by ID."""
return await self.request("GET", f"/messaging/messages/{message_id}")
async def create_email(
self,
message_id: str,
subject: str,
content: str,
topics: list[str] | None = None,
users: list[str] | None = None,
targets: list[str] | None = None,
cc: list[str] | None = None,
bcc: list[str] | None = None,
attachments: list[str] | None = None,
draft: bool = False,
html: bool = True,
scheduled_at: str | None = None,
) -> dict[str, Any]:
"""Create/send email message."""
data = {
"messageId": message_id,
"subject": subject,
"content": content,
"draft": draft,
"html": html,
}
if topics:
data["topics"] = topics
if users:
data["users"] = users
if targets:
data["targets"] = targets
if cc:
data["cc"] = cc
if bcc:
data["bcc"] = bcc
if attachments:
data["attachments"] = attachments
if scheduled_at:
data["scheduledAt"] = scheduled_at
return await self.request("POST", "/messaging/messages/email", json_data=data)
async def create_sms(
self,
message_id: str,
content: str,
topics: list[str] | None = None,
users: list[str] | None = None,
targets: list[str] | None = None,
draft: bool = False,
scheduled_at: str | None = None,
) -> dict[str, Any]:
"""Create/send SMS message."""
data = {"messageId": message_id, "content": content, "draft": draft}
if topics:
data["topics"] = topics
if users:
data["users"] = users
if targets:
data["targets"] = targets
if scheduled_at:
data["scheduledAt"] = scheduled_at
return await self.request("POST", "/messaging/messages/sms", json_data=data)
async def create_push(
self,
message_id: str,
title: str,
body: str,
topics: list[str] | None = None,
users: list[str] | None = None,
targets: list[str] | None = None,
data_payload: dict[str, Any] | None = None,
action: str | None = None,
image: str | None = None,
icon: str | None = None,
sound: str | None = None,
color: str | None = None,
tag: str | None = None,
badge: int | None = None,
draft: bool = False,
scheduled_at: str | None = None,
) -> dict[str, Any]:
"""Create/send push notification."""
data = {"messageId": message_id, "title": title, "body": body, "draft": draft}
if topics:
data["topics"] = topics
if users:
data["users"] = users
if targets:
data["targets"] = targets
if data_payload:
data["data"] = data_payload
if action:
data["action"] = action
if image:
data["image"] = image
if icon:
data["icon"] = icon
if sound:
data["sound"] = sound
if color:
data["color"] = color
if tag:
data["tag"] = tag
if badge is not None:
data["badge"] = badge
if scheduled_at:
data["scheduledAt"] = scheduled_at
return await self.request("POST", "/messaging/messages/push", json_data=data)
async def delete_message(self, message_id: str) -> dict[str, Any]:
"""Delete message."""
return await self.request("DELETE", f"/messaging/messages/{message_id}")
# =====================
# COMPREHENSIVE HEALTH CHECK
# =====================
async def health_check(self) -> dict[str, Any]:
"""
Check Appwrite instance health.
Returns comprehensive health status of all services.
"""
results = {"healthy": True, "services": {}}
# Check general health
try:
health = await self.health()
results["services"]["general"] = health.get("status", "ok")
except Exception as e:
results["services"]["general"] = f"error: {str(e)}"
results["healthy"] = False
# Check database
try:
db_health = await self.health_db()
results["services"]["database"] = db_health.get("status", "ok")
except Exception as e:
results["services"]["database"] = f"error: {str(e)}"
results["healthy"] = False
# Check cache
try:
cache_health = await self.health_cache()
results["services"]["cache"] = cache_health.get("status", "ok")
except Exception as e:
results["services"]["cache"] = f"error: {str(e)}"
# Cache error might not be critical
if "error" not in str(e).lower():
results["services"]["cache"] = "warning"
# Check storage
try:
storage_health = await self.health_storage_local()
results["services"]["storage"] = storage_health.get("status", "ok")
except Exception as e:
results["services"]["storage"] = f"error: {str(e)}"
results["healthy"] = False
return results