← AppwriteCONTENT HISTORY

Update to Appwrite

Snapshot Sep 30, 2026 · 23:07 UTC · version 1.0.0

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "name": "appwrite-python",
  "description": "Appwrite Python SDK skill. Use when building server-side Python applications with Appwrite, including Django, Flask, and FastAPI integrations. Covers user management, database/table CRUD, file storage, and functions via API keys.",
  "included_files": [],
  "skill_md_contents": "---\r\nname: appwrite-python\r\ndescription: Appwrite Python SDK skill. Use when building server-side Python applications with Appwrite, including Django, Flask, and FastAPI integrations. Covers user management, database/table CRUD, file storage, and functions via API keys.\r\n---\r\n\r\n\r\n# Appwrite Python SDK\r\n\r\n## Installation\r\n\r\n```bash\r\npip install appwrite\r\n```\r\n\r\n## Setting Up the Client\r\n\r\n```python\r\nfrom appwrite.client import Client\r\nfrom appwrite.id import ID\r\nfrom appwrite.query import Query\r\nfrom appwrite.services.users import Users\r\nfrom appwrite.services.tables_db import TablesDB\r\nfrom appwrite.services.storage import Storage\r\nfrom appwrite.services.functions import Functions\r\nfrom appwrite.enums.o_auth_provider import OAuthProvider\r\n\r\nimport os\r\n\r\nclient = (Client()\r\n    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')\r\n    .set_project(os.environ['APPWRITE_PROJECT_ID'])\r\n    .set_key(os.environ['APPWRITE_API_KEY']))\r\n```\r\n\r\n## Code Examples\r\n\r\n### User Management\r\n\r\n```python\r\nusers = Users(client)\r\n\r\n# Create user\r\nuser = users.create(ID.unique(), 'user@example.com', None, 'password123', 'User Name')\r\n\r\n# List users\r\nresult = users.list([Query.limit(25)])\r\n\r\n# Get user\r\nfetched = users.get('[USER_ID]')\r\n\r\n# Delete user\r\nusers.delete('[USER_ID]')\r\n```\r\n\r\n### Database Operations\r\n\r\n> **Note:** Use `TablesDB` (not the deprecated `Databases` class) for all new code. Only use `Databases` if the existing codebase already relies on it or the user explicitly requests it.\r\n>\r\n> **Tip:** Prefer keyword arguments (e.g., `database_id='...'`) over positional arguments for all SDK method calls. Only use positional style if the existing codebase already uses it or the user explicitly requests it.\r\n\r\n```python\r\ntables_db = TablesDB(client)\r\n\r\n# Create database\r\ndb = tables_db.create(ID.unique(), 'My Database')\r\n\r\n# Create row\r\ndoc = tables_db.create_row('[DATABASE_ID]', '[TABLE_ID]', ID.unique(), {\r\n    'title': 'Hello World'\r\n})\r\n\r\n# Query rows\r\nresults = tables_db.list_rows('[DATABASE_ID]', '[TABLE_ID]', [\r\n    Query.equal('title', 'Hello World'),\r\n    Query.limit(10)\r\n])\r\n\r\n# Get row\r\nrow = tables_db.get_row('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]')\r\n\r\n# Update row\r\ntables_db.update_row('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]', {\r\n    'title': 'Updated'\r\n})\r\n\r\n# Delete row\r\ntables_db.delete_row('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]')\r\n```\r\n\r\n#### String Column Types\r\n\r\n> **Note:** The legacy `string` type is deprecated. Use explicit column types for all new columns.\r\n\r\n| Type | Max characters | Indexing | Storage |\r\n|------|---------------|----------|---------|\r\n| `varchar` | 16,383 | Full index (if size ≤ 768) | Inline in row |\r\n| `text` | 16,383 | Prefix only | Off-page |\r\n| `mediumtext` | 4,194,303 | Prefix only | Off-page |\r\n| `longtext` | 1,073,741,823 | Prefix only | Off-page |\r\n\r\n- `varchar` is stored inline and counts towards the 64 KB row size limit. Prefer for short, indexed fields like names, slugs, or identifiers.\r\n- `text`, `mediumtext`, and `longtext` are stored off-page (only a 20-byte pointer lives in the row), so they don't consume the row size budget. `size` is not required for these types.\r\n\r\n```python\r\n# Create table with explicit string column types\r\ntables_db.create_table(\r\n    database_id='[DATABASE_ID]',\r\n    table_id=ID.unique(),\r\n    name='articles',\r\n    columns=[\r\n        {'key': 'title',    'type': 'varchar',    'size': 255, 'required': True},   # inline, fully indexable\r\n        {'key': 'summary',  'type': 'text',                    'required': False},  # off-page, prefix index only\r\n        {'key': 'body',     'type': 'mediumtext',              'required': False},  # up to ~4 M chars\r\n        {'key': 'raw_data', 'type': 'longtext',                'required': False},  # up to ~1 B chars\r\n    ]\r\n)\r\n```\r\n\r\n### Query Methods\r\n\r\n```python\r\n# Filtering\r\nQuery.equal('field', 'value')             # == (or pass list for IN)\r\nQuery.not_equal('field', 'value')         # !=\r\nQuery.less_than('field', 100)             # <\r\nQuery.less_than_equal('field', 100)       # <=\r\nQuery.greater_than('field', 100)          # >\r\nQuery.greater_than_equal('field', 100)    # >=\r\nQuery.between('field', 1, 100)            # 1 <= field <= 100\r\nQuery.is_null('field')                    # is null\r\nQuery.is_not_null('field')                # is not null\r\nQuery.starts_with('field', 'prefix')      # starts with\r\nQuery.ends_with('field', 'suffix')        # ends with\r\nQuery.contains('field', 'sub')            # contains (string or array)\r\nQuery.search('field', 'keywords')         # full-text search (requires index)\r\n\r\n# Sorting\r\nQuery.order_asc('field')\r\nQuery.order_desc('field')\r\n\r\n# Pagination\r\nQuery.limit(25)                           # max rows (default 25, max 100)\r\nQuery.offset(0)                           # skip N rows\r\nQuery.cursor_after('[ROW_ID]')            # cursor pagination (preferred)\r\nQuery.cursor_before('[ROW_ID]')\r\n\r\n# Selection & Logic\r\nQuery.select(['field1', 'field2'])        # return only specified fields\r\nQuery.or_queries([Query.equal('a', 1), Query.equal('b', 2)])   # OR\r\nQuery.and_queries([Query.greater_than('age', 18), Query.less_than('age', 65)])  # AND (default)\r\n```\r\n\r\n### File Storage\r\n\r\n```python\r\nfrom appwrite.input_file import InputFile\r\n\r\nstorage = Storage(client)\r\n\r\n# Upload file\r\nfile = storage.create_file('[BUCKET_ID]', ID.unique(), InputFile.from_path('/path/to/file.png'))\r\n\r\n# List files\r\nfiles = storage.list_files('[BUCKET_ID]')\r\n\r\n# Delete file\r\nstorage.delete_file('[BUCKET_ID]', '[FILE_ID]')\r\n```\r\n\r\n#### InputFile Factory Methods\r\n\r\n```python\r\nfrom appwrite.input_file import InputFile\r\n\r\nInputFile.from_path('/path/to/file.png')             # from filesystem path\r\nInputFile.from_bytes(byte_data, 'file.png')          # from bytes\r\nInputFile.from_string('Hello world', 'hello.txt')    # from string content\r\n```\r\n\r\n### Teams\r\n\r\n```python\r\nfrom appwrite.services.teams import Teams\r\n\r\nteams = Teams(client)\r\n\r\n# Create team\r\nteam = teams.create(ID.unique(), 'Engineering')\r\n\r\n# List teams\r\nteam_list = teams.list()\r\n\r\n# Create membership (invite user by email)\r\nmembership = teams.create_membership('[TEAM_ID]', roles=['editor'], email='user@example.com')\r\n\r\n# List memberships\r\nmembers = teams.list_memberships('[TEAM_ID]')\r\n\r\n# Update membership roles\r\nteams.update_membership('[TEAM_ID]', '[MEMBERSHIP_ID]', roles=['admin'])\r\n\r\n# Delete team\r\nteams.delete('[TEAM_ID]')\r\n```\r\n\r\n> **Role-based access:** Use `Role.team('[TEAM_ID]')` for all team members or `Role.team('[TEAM_ID]', 'editor')` for a specific team role when setting permissions.\r\n\r\n### Serverless Functions\r\n\r\n```python\r\nfunctions = Functions(client)\r\n\r\n# Execute function\r\nexecution = functions.create_execution('[FUNCTION_ID]', body='{\"key\": \"value\"}')\r\n\r\n# List executions\r\nexecutions = functions.list_executions('[FUNCTION_ID]')\r\n```\r\n\r\n#### Writing a Function Handler (Python runtime)\r\n\r\n```python\r\n# src/main.py — Appwrite Function entry point\r\ndef main(context):\r\n    # context.req — request object\r\n    #   .body        — raw request body (string)\r\n    #   .body_json   — parsed JSON body (dict, or None if not JSON)\r\n    #   .headers     — request headers (dict)\r\n    #   .method      — HTTP method (GET, POST, etc.)\r\n    #   .path        — URL path\r\n    #   .query       — parsed query parameters (dict)\r\n    #   .query_string — raw query string\r\n\r\n    context.log('Processing: ' + context.req.method + ' ' + context.req.path)\r\n\r\n    if context.req.method == 'GET':\r\n        return context.res.json({'message': 'Hello from Appwrite Function!'})\r\n\r\n    data = context.req.body_json or {}\r\n    if 'name' not in data:\r\n        context.error('Missing name field')\r\n        return context.res.json({'error': 'Name is required'}, 400)\r\n\r\n    # Response methods\r\n    return context.res.json({'success': True})                    # JSON response\r\n    # return context.res.text('Hello')                           # plain text\r\n    # return context.res.empty()                                 # 204 No Content\r\n    # return context.res.redirect('https://example.com')         # 302 Redirect\r\n    # return context.res.send('data', 200, {'X-Custom': '1'})   # custom response\r\n```\r\n\r\n### Server-Side Rendering (SSR) Authentication\r\n\r\nSSR apps (Flask, Django, FastAPI, etc.) use the **server SDK** to handle auth. You need two clients:\r\n\r\n- **Admin client** — uses an API key, creates sessions, bypasses rate limits (reusable singleton)\r\n- **Session client** — uses a session cookie, acts on behalf of a user (create per-request, never share)\r\n\r\n```python\r\nfrom appwrite.client import Client\r\nfrom appwrite.services.account import Account\r\nfrom flask import request, jsonify, make_response, redirect\r\n\r\n# Admin client (reusable)\r\nadmin_client = (Client()\r\n    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')\r\n    .set_project('[PROJECT_ID]')\r\n    .set_key(os.environ['APPWRITE_API_KEY']))\r\n\r\n# Session client (create per-request)\r\nsession_client = (Client()\r\n    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')\r\n    .set_project('[PROJECT_ID]'))\r\n\r\nsession = request.cookies.get('a_session_[PROJECT_ID]')\r\nif session:\r\n    session_client.set_session(session)\r\n```\r\n\r\n#### Email/Password Login\r\n\r\n```python\r\n@app.post('/login')\r\ndef login():\r\n    account = Account(admin_client)\r\n    session = account.create_email_password_session(\r\n        request.json['email'], request.json['password']\r\n    )\r\n\r\n    # Cookie name must be a_session_<PROJECT_ID>\r\n    resp = make_response(jsonify({'success': True}))\r\n    resp.set_cookie('a_session_[PROJECT_ID]', session['secret'],\r\n                    httponly=True, secure=True, samesite='Strict',\r\n                    expires=session['expire'], path='/')\r\n    return resp\r\n```\r\n\r\n#### Authenticated Requests\r\n\r\n```python\r\n@app.get('/user')\r\ndef get_user():\r\n    session = request.cookies.get('a_session_[PROJECT_ID]')\r\n    if not session:\r\n        return jsonify({'error': 'Unauthorized'}), 401\r\n\r\n    session_client = (Client()\r\n        .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')\r\n        .set_project('[PROJECT_ID]')\r\n        .set_session(session))\r\n\r\n    account = Account(session_client)\r\n    return jsonify(account.get())\r\n```\r\n\r\n#### OAuth2 SSR Flow\r\n\r\n```python\r\n# Step 1: Redirect to OAuth provider\r\n@app.get('/oauth')\r\ndef oauth():\r\n    account = Account(admin_client)\r\n    redirect_url = account.create_o_auth2_token(\r\n        OAuthProvider.Github,\r\n        'https://example.com/oauth/success',\r\n        'https://example.com/oauth/failure',\r\n    )\r\n    return redirect(redirect_url)\r\n\r\n# Step 2: Handle callback — exchange token for session\r\n@app.get('/oauth/success')\r\ndef oauth_success():\r\n    account = Account(admin_client)\r\n    session = account.create_session(request.args['userId'], request.args['secret'])\r\n\r\n    resp = make_response(jsonify({'success': True}))\r\n    resp.set_cookie('a_session_[PROJECT_ID]', session['secret'],\r\n                    httponly=True, secure=True, samesite='Strict',\r\n                    expires=session['expire'], path='/')\r\n    return resp\r\n```\r\n\r\n> **Cookie security:** Always use `httponly`, `secure`, and `samesite='Strict'` to prevent XSS. The cookie name must be `a_session_<PROJECT_ID>`.\r\n\r\n> **Forwarding user agent:** Call `session_client.set_forwarded_user_agent(request.headers.get('user-agent'))` to record the end-user's browser info for debugging and security.\r\n\r\n## Error Handling\r\n\r\n```python\r\nfrom appwrite.exception import AppwriteException\r\n\r\ntry:\r\n    row = tables_db.get_row('[DATABASE_ID]', '[TABLE_ID]', '[ROW_ID]')\r\nexcept AppwriteException as e:\r\n    print(e.message)    # human-readable error message\r\n    print(e.code)       # HTTP status code (int)\r\n    print(e.type)       # Appwrite error type string (e.g. 'document_not_found')\r\n    print(e.response)   # full response body (dict)\r\n```\r\n\r\n**Common error codes:**\r\n\r\n| Code | Meaning |\r\n|------|---------|\r\n| `401` | Unauthorized — missing or invalid session/API key |\r\n| `403` | Forbidden — insufficient permissions for this action |\r\n| `404` | Not found — resource does not exist |\r\n| `409` | Conflict — duplicate ID or unique constraint violation |\r\n| `429` | Rate limited — too many requests, retry after backoff |\r\n\r\n## Permissions & Roles (Critical)\r\n\r\nAppwrite uses permission strings to control access to resources. Each permission pairs an action (`read`, `update`, `delete`, `create`, or `write` which grants create + update + delete) with a role target. By default, **no user has access** unless permissions are explicitly set at the row/file level or inherited from the table/bucket settings. Permissions are arrays of strings built with the `Permission` and `Role` helpers.\r\n\r\n```python\r\nfrom appwrite.permission import Permission\r\nfrom appwrite.role import Role\r\n```\r\n\r\n### Database Row with Permissions\r\n\r\n```python\r\ndoc = tables_db.create_row('[DATABASE_ID]', '[TABLE_ID]', ID.unique(), {\r\n    'title': 'Hello World'\r\n}, [\r\n    Permission.read(Role.user('[USER_ID]')),     # specific user can read\r\n    Permission.update(Role.user('[USER_ID]')),   # specific user can update\r\n    Permission.read(Role.team('[TEAM_ID]')),     # all team members can read\r\n    Permission.read(Role.any()),                 # anyone (including guests) can read\r\n])\r\n```\r\n\r\n### File Upload with Permissions\r\n\r\n```python\r\nfile = storage.create_file('[BUCKET_ID]', ID.unique(), InputFile.from_path('/path/to/file.png'), [\r\n    Permission.read(Role.any()),\r\n    Permission.update(Role.user('[USER_ID]')),\r\n    Permission.delete(Role.user('[USER_ID]')),\r\n])\r\n```\r\n\r\n> **When to set permissions:** Set row/file-level permissions when you need per-resource access control. If all rows in a table share the same rules, configure permissions at the table/bucket level and leave row permissions empty.\r\n\r\n> **Common mistakes:**\r\n> - **Forgetting permissions** — the resource becomes inaccessible to all users (including the creator)\r\n> - **`Role.any()` with `write`/`update`/`delete`** — allows any user, including unauthenticated guests, to modify or remove the resource\r\n> - **`Permission.read(Role.any())` on sensitive data** — makes the resource publicly readable\r\n\r\n"
}

SHA-256: 0884acc1679cd1f26ddc46d9cd95cd3e85f087b34b880dbe0dcb420182b7e968