mcpField
A
appwrite-python

appwrite-python

github.com/appwrite开发281.1k 次安装1 周前更新
代码仓库

关于 appwrite-python

appwrite-python 是用于 Python 应用的 Appwrite SDK,涵盖服务端身份验证、用户管理、TablesDB、文件存储、团队和函数等 API。它还提供查询、资源权限及角色控制功能。

主要功能

  1. 01连接 Appwrite 项目
  2. 02处理身份验证与用户管理
  3. 03操作 TablesDB 和查询
  4. 04上传文件并设置权限
  5. 05管理团队与角色
  6. 06执行函数并处理服务端请求

适用场景

  1. 01在 Python Web 应用中接入 Appwrite
  2. 02通过服务端 SDK 管理用户和会话
  3. 03查询数据库行并控制资源访问
  4. 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 类型已弃用。所有新列都应使用明确指定的列类型。

TypeMax charactersIndexingStorage
varchar16,383Full index (if size ≤ 768)Inline in row
text16,383Prefix onlyOff-page
mediumtext4,194,303Prefix onlyOff-page
longtext1,073,741,823Prefix onlyOff-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)

常见错误代码:

CodeMeaning
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())——资源将公开可读

评论

支持 Markdown