关于 appwrite-python
appwrite-python 是用于 Python 应用的 Appwrite SDK,涵盖服务端身份验证、用户管理、TablesDB、文件存储、团队和函数等 API。它还提供查询、资源权限及角色控制功能。
主要功能
- 01连接 Appwrite 项目
- 02处理身份验证与用户管理
- 03操作 TablesDB 和查询
- 04上传文件并设置权限
- 05管理团队与角色
- 06执行函数并处理服务端请求
适用场景
- 01在 Python Web 应用中接入 Appwrite
- 02通过服务端 SDK 管理用户和会话
- 03查询数据库行并控制资源访问
- 04从 Python 应用执行 Appwrite 函数
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ appwrite/ skills --skill appwrite-python安装后在 Claude Code 中输入/appwrite-python确认已加载。
详细介绍
Appwrite Python SDK
安装
pip install appwrite
设置客户端
from appwrite.client import Client
from appwrite.id import ID
from appwrite.query import Query
from appwrite.services.users import Users
from appwrite.services.tables_db import TablesDB
from appwrite.services.storage import Storage
from appwrite.services.functions import Functions
from appwrite.enums.o_auth_provider import OAuthProvider
import os
client = (Client()
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
.set_project(os.environ['APPWRITE_PROJECT_ID'])
.set_key(os.environ['APPWRITE_API_KEY']))
代码示例
用户管理
users = Users(client)
# Create user
user = users.create(ID.unique(), '[email protected]', None, 'password123', 'User Name')
# List users
result = users.list([Query.limit(25)])
# Get user
fetched = users.get('[USER_ID]')
# Delete user
users.delete('[USER_ID]')
数据库操作
注意: 所有新代码都应使用
TablesDB(而非已弃用的Databases类)。只有当现有代码库已依赖Databases,或用户明确要求时,才使用Databases。提示: 所有 SDK 方法调用都应优先使用关键字参数(例如
database_id='...'),而不是位置参数。只有当现有代码库已使用位置参数,或用户明确要求时,才使用位置参数。
tables_db = TablesDB(client)
# Create database
db = tables_db.create(ID.unique(), 'My Database')
# Create row
doc = tables_db.create_row('[DATABASE_ID]', '[TABLE_ID]', ID.unique(), {
'title': 'Hello World'
})
# Query rows
results = tables_db.list_rows('[DATABASE_ID]', '[TABLE_ID]', [
Query.equal('title', 'Hello World'),
Query.limit(10)
])
# Get row
row = tables_db.get_row('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]')
# Update row
tables_db.update_row('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]', {
'title': 'Updated'
})
# Delete row
tables_db.delete_row('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]')
字符串列类型
注意: 旧版
string类型已弃用。所有新列都应使用明确指定的列类型。
| Type | Max characters | Indexing | Storage |
|---|---|---|---|
varchar | 16,383 | Full index (if size ≤ 768) | Inline in row |
text | 16,383 | Prefix only | Off-page |
mediumtext | 4,194,303 | Prefix only | Off-page |
longtext | 1,073,741,823 | Prefix only | Off-page |
varchar会内联存储,并计入 64 KB 的行大小限制。适用于名称、slug 或标识符等较短且需要索引的字段。text、mediumtext和longtext会存储在页外(行中仅保留一个 20 字节的指针),因此不会占用行大小额度。这些类型不需要设置size。
# Create table with explicit string column types
tables_db.create_table(
database_id='[DATABASE_ID]',
table_id=ID.unique(),
name='articles',
columns=[
{'key': 'title', 'type': 'varchar', 'size': 255, 'required': True}, # inline, fully indexable
{'key': 'summary', 'type': 'text', 'required': False}, # off-page, prefix index only
{'key': 'body', 'type': 'mediumtext', 'required': False}, # up to ~4 M chars
{'key': 'raw_data', 'type': 'longtext', 'required': False}, # up to ~1 B chars
]
)
查询方法
# Filtering
Query.equal('field', 'value') # == (or pass list for IN)
Query.not_equal('field', 'value') # !=
Query.less_than('field', 100) # <
Query.less_than_equal('field', 100) # <=
Query.greater_than('field', 100) # >
Query.greater_than_equal('field', 100) # >=
Query.between('field', 1, 100) # 1 <= field <= 100
Query.is_null('field') # is null
Query.is_not_null('field') # is not null
Query.starts_with('field', 'prefix') # starts with
Query.ends_with('field', 'suffix') # ends with
Query.contains('field', 'sub') # contains (string or array)
Query.search('field', 'keywords') # full-text search (requires index)
# Sorting
Query.order_asc('field')
Query.order_desc('field')
# Pagination
Query.limit(25) # max rows (default 25, max 100)
Query.offset(0) # skip N rows
Query.cursor_after('[ROW_ID]') # cursor pagination (preferred)
Query.cursor_before('[ROW_ID]')
# Selection & Logic
Query.select(['field1', 'field2']) # return only specified fields
Query.or_queries([Query.equal('a', 1), Query.equal('b', 2)]) # OR
Query.and_queries([Query.greater_than('age', 18), Query.less_than('age', 65)]) # AND (default)
Query.relationship('comments', [Query.equal('approved', True), Query.limit(25)]) # related documents
# Aggregations (optional alias)
Query.count('*', 'total')
Query.sum('amount', 'revenue')
Query.avg('score', 'avg_score')
Query.min('price', 'min_price')
Query.max('price', 'max_price')
# Grouping
Query.group_by(['status', 'year'])
Query.having([Query.greater_than('total', 1)])
Query.distinct()
# Joins (table id, left key, right key, operator, alias)
Query.join('orders', '$id', 'customerId')
Query.left_join('orders', '$id', 'customerId', '=', 'ord')
Query.left_join('orders', 'ord', [
Query.on('$id', 'customerId'),
Query.equal('ord.status', 'paid'),
])
# Fluent builder. Pass build() into list methods.
queries = (
Query.builder()
.equal('status', 'published')
.left_join('orders', '$id', 'customerId', '=', 'ord')
.count('*', 'total')
.group_by(['status'])
.limit(25)
.build()
)
tables_db.list_rows('[DATABASE_ID]', '[TABLE_ID]', queries)
文件存储
from appwrite.input_file import InputFile
storage = Storage(client)
# Upload file
file = storage.create_file('[BUCKET_ID]', ID.unique(), InputFile.from_path('/path/to/file.png'))
# List files
files = storage.list_files('[BUCKET_ID]')
# Delete file
storage.delete_file('[BUCKET_ID]', '[FILE_ID]')
InputFile 工厂方法
from appwrite.input_file import InputFile
InputFile.from_path('/path/to/file.png') # from filesystem path
InputFile.from_bytes(byte_data, 'file.png') # from bytes
InputFile.from_string('Hello world', 'hello.txt') # from string content
团队
from appwrite.services.teams import Teams
teams = Teams(client)
# Create team
team = teams.create(ID.unique(), 'Engineering')
# List teams
team_list = teams.list()
# Create membership (invite user by email)
membership = teams.create_membership('[TEAM_ID]', roles=['editor'], email='[email protected]')
# List memberships
members = teams.list_memberships('[TEAM_ID]')
# Update membership roles
teams.update_membership('[TEAM_ID]', '[MEMBERSHIP_ID]', roles=['admin'])
# Delete team
teams.delete('[TEAM_ID]')
基于角色的访问: 设置权限时,使用
Role.team('[TEAM_ID]')指定所有团队成员,或使用Role.team('[TEAM_ID]', 'editor')指定特定团队角色。
无服务器函数
functions = Functions(client)
# Execute function
execution = functions.create_execution('[FUNCTION_ID]', body='{"key": "value"}')
# List executions
executions = functions.list_executions('[FUNCTION_ID]')
编写函数处理程序(Python 运行时)
# src/main.py — Appwrite Function entry point
def main(context):
# context.req — request object
# .body — raw request body (string)
# .body_json — parsed JSON body (dict, or None if not JSON)
# .headers — request headers (dict)
# .method — HTTP method (GET, POST, etc.)
# .path — URL path
# .query — parsed query parameters (dict)
# .query_string — raw query string
context.log('Processing: ' + context.req.method + ' ' + context.req.path)
if context.req.method == 'GET':
return context.res.json({'message': 'Hello from Appwrite Function!'})
data = context.req.body_json or {}
if 'name' not in data:
context.error('Missing name field')
return context.res.json({'error': 'Name is required'}, 400)
# Response methods
return context.res.json({'success': True}) # JSON response
# return context.res.text('Hello') # plain text
# return context.res.empty() # 204 No Content
# return context.res.redirect('https://example.com') # 302 Redirect
# return context.res.send('data', 200, {'X-Custom': '1'}) # custom response
服务端渲染(SSR)身份验证
SSR 应用(Flask、Django、FastAPI 等)使用服务端 SDK处理身份验证。你需要两个客户端:
- 管理员客户端 — 使用 API 密钥创建会话,并绕过速率限制(可复用的单例)
- 会话客户端 — 使用会话 Cookie 代表用户执行操作(每个请求创建一个,切勿共享)
from appwrite.client import Client
from appwrite.services.account import Account
from flask import request, jsonify, make_response, redirect
# Admin client (reusable)
admin_client = (Client()
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
.set_project('[PROJECT_ID]')
.set_key(os.environ['APPWRITE_API_KEY']))
# Session client (create per-request)
session_client = (Client()
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
.set_project('[PROJECT_ID]'))
session = request.cookies.get('a_session_[PROJECT_ID]')
if session:
session_client.set_session(session)
电子邮件/密码登录
@app.post('/login')
def login():
account = Account(admin_client)
session = account.create_email_password_session(
request.json['email'], request.json['password']
)
# Cookie name must be a_session_<PROJECT_ID>
resp = make_response(jsonify({'success': True}))
resp.set_cookie('a_session_[PROJECT_ID]', session['secret'],
httponly=True, secure=True, samesite='Strict',
expires=session['expire'], path='/')
return resp
已验证身份的请求
@app.get('/user')
def get_user():
session = request.cookies.get('a_session_[PROJECT_ID]')
if not session:
return jsonify({'error': 'Unauthorized'}), 401
session_client = (Client()
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
.set_project('[PROJECT_ID]')
.set_session(session))
account = Account(session_client)
return jsonify(account.get())
OAuth2 SSR 流程
# Step 1: Redirect to OAuth provider
@app.get('/oauth')
def oauth():
account = Account(admin_client)
redirect_url = account.create_o_auth2_token(
OAuthProvider.Github,
'https://example.com/oauth/success',
'https://example.com/oauth/failure',
)
return redirect(redirect_url)
# Step 2: Handle callback — exchange token for session
@app.get('/oauth/success')
def oauth_success():
account = Account(admin_client)
session = account.create_session(request.args['userId'], request.args['secret'])
resp = make_response(jsonify({'success': True}))
resp.set_cookie('a_session_[PROJECT_ID]', session['secret'],
httponly=True, secure=True, samesite='Strict',
expires=session['expire'], path='/')
return resp
Cookie 安全: 始终使用
httponly、secure和samesite='Strict'来防止 XSS。Cookie 名称必须为a_session_<PROJECT_ID>。
转发用户代理: 调用
session_client.set_forwarded_user_agent(request.headers.get('user-agent')),以记录最终用户的浏览器信息,便于调试和保障安全。
错误处理
from appwrite.exception import AppwriteException
try:
row = tables_db.get_row('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]')
except AppwriteException as e:
print(e.message) # human-readable error message
print(e.code) # HTTP status code (int)
print(e.type) # Appwrite error type string (e.g. 'document_not_found')
print(e.response) # full response body (dict)
常见错误代码:
| Code | Meaning |
|---|---|
401 | 未授权 — 缺少或无效的会话/API 密钥 |
403 | 禁止访问 — 此操作的权限不足 |
404 | 未找到 — 资源不存在 |
409 | 冲突 — ID 重复或违反唯一性约束 |
429 | 请求过多 — 请求频率过高,请等待退避时间后重试 |
权限与角色(重要)
Appwrite 使用权限字符串控制对资源的访问。每个权限由一个操作(read、update、delete、create 或授予创建、更新和删除权限的 write)和一个角色目标组成。默认情况下,除非在行/文件级别明确设置权限,或从表/存储桶设置继承权限,否则任何用户都无法访问。权限是由 Permission 和 Role 辅助工具构建的字符串数组。
from appwrite.permission import Permission
from appwrite.role import Role
带权限的数据库行
doc = tables_db.create_row('[DATABASE_ID]', '[TABLE_ID]', ID.unique(), {
'title': 'Hello World'
}, [
Permission.read(Role.user('[USER_ID]')), # specific user can read
Permission.update(Role.user('[USER_ID]')), # specific user can update
Permission.read(Role.team('[TEAM_ID]')), # all team members can read
Permission.read(Role.any()), # anyone (including guests) can read
])
带权限的文件上传
file = storage.create_file('[BUCKET_ID]', ID.unique(), InputFile.from_path('/path/to/file.png'), [
Permission.read(Role.any()),
Permission.update(Role.user('[USER_ID]')),
Permission.delete(Role.user('[USER_ID]')),
])
何时设置权限: 需要按资源控制访问权限时,请设置行级/文件级权限。如果表中的所有行都使用相同的规则,请在表/存储桶级别配置权限,并将行权限留空。
常见错误:
- 忘记设置权限——资源将对所有用户(包括创建者)不可访问
- 对
write/update/delete使用Role.any()——允许任何用户(包括未通过身份验证的访客)修改或删除资源- 对敏感数据使用
Permission.read(Role.any())——资源将公开可读