Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Change Log

## 24.0.0

* Stable release of the dedicated database APIs: `mysql`, `postgresql`, `mongo`, `documentsDB`, and `vectorsDB` services, previously released as release candidates
* Breaking: `tablesDB.cutoverMigration` is renamed to `tablesDB.createCutover`
* Breaking: `Execution.functionId` is replaced by `resourceId` and `resourceType`, covering function and site executions
* Added: Cloudflare, Resend, and Hugging Face OAuth providers
* Added: `usageAggregateOnlyMetrics` on the `BillingPlan` model
* Fixed: `transactionId` is accepted again by `documentsDB` and `vectorsDB` `createDocument` and `createDocuments`
* Updated: `X-Appwrite-Response-Format` is now `2.0.0`
* Updated: `FrameworkAdapter.fallbackFile` is now optional

## 24.0.0rc1

* Breaking: `Execution.functionId` is replaced by `resourceId` and `resourceType`, now that executions cover both functions and sites
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Appwrite Python SDK

![License](https://img.shields.io/github/license/appwrite/sdk-for-python.svg?style=flat-square)
![Version](https://img.shields.io/badge/api%20version-1.9.6-blue.svg?style=flat-square)
![Version](https://img.shields.io/badge/api%20version-2.0.0-blue.svg?style=flat-square)
[![Build Status](https://img.shields.io/travis/com/appwrite/sdk-generator?style=flat-square)](https://travis-ci.com/appwrite/sdk-generator)
[![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite)
[![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord)

**This SDK is compatible with Appwrite server version 1.9.x. For older versions, please check [previous releases](https://github.com/appwrite/sdk-for-python/releases).**
**This SDK targets Appwrite server version 1.9.x as shipped on Appwrite Cloud.** Self-hosted releases can lag behind Cloud — if you run an older self-hosted build, use a matching older SDK from [previous releases](https://github.com/appwrite/sdk-for-python/releases) when APIs differ.

Appwrite is an open-source backend as a service server that abstracts and simplifies complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the Python SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)

Expand Down
6 changes: 3 additions & 3 deletions appwrite/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ def __init__(self):
self._endpoint = 'https://cloud.appwrite.io/v1'
self._global_headers = {
'content-type': '',
'user-agent': f'AppwritePythonSDK/24.0.0rc1 ({platform.uname().system}; {platform.uname().version}; {platform.uname().machine})',
'user-agent': f'AppwritePythonSDK/24.0.0 ({platform.uname().system}; {platform.uname().version}; {platform.uname().machine})',
'x-sdk-name': 'Python',
'x-sdk-platform': 'server',
'x-sdk-language': 'python',
'x-sdk-version': '24.0.0rc1',
'X-Appwrite-Response-Format': '1.9.6',
'x-sdk-version': '24.0.0',
'X-Appwrite-Response-Format': '2.0.0',
}
self._config = {}

Expand Down
2 changes: 2 additions & 0 deletions appwrite/enums/o_auth_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class OAuthProvider(Enum):
BITBUCKET = "bitbucket"
BITLY = "bitly"
BOX = "box"
CLOUDFLARE = "cloudflare"
DAILYMOTION = "dailymotion"
DISCORD = "discord"
DISQUS = "disqus"
Expand All @@ -33,6 +34,7 @@ class OAuthProvider(Enum):
PAYPAL = "paypal"
PAYPALSANDBOX = "paypalSandbox"
PODIO = "podio"
RESEND = "resend"
SALESFORCE = "salesforce"
SLACK = "slack"
SPOTIFY = "spotify"
Expand Down
4 changes: 4 additions & 0 deletions appwrite/enums/project_key_scopes.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,16 @@ class ProjectKeyScopes(Enum):
DOCUMENTSDB_COLLECTIONS_WRITE = "documentsdb.collections.write"
DOCUMENTSDB_DOCUMENTS_READ = "documentsdb.documents.read"
DOCUMENTSDB_DOCUMENTS_WRITE = "documentsdb.documents.write"
DOCUMENTSDB_INDEXES_READ = "documentsdb.indexes.read"
DOCUMENTSDB_INDEXES_WRITE = "documentsdb.indexes.write"
VECTORSDB_READ = "vectorsdb.read"
VECTORSDB_WRITE = "vectorsdb.write"
VECTORSDB_COLLECTIONS_READ = "vectorsdb.collections.read"
VECTORSDB_COLLECTIONS_WRITE = "vectorsdb.collections.write"
VECTORSDB_DOCUMENTS_READ = "vectorsdb.documents.read"
VECTORSDB_DOCUMENTS_WRITE = "vectorsdb.documents.write"
VECTORSDB_INDEXES_READ = "vectorsdb.indexes.read"
VECTORSDB_INDEXES_WRITE = "vectorsdb.indexes.write"
BUCKETS_READ = "buckets.read"
BUCKETS_WRITE = "buckets.write"
FILES_READ = "files.read"
Expand Down
2 changes: 2 additions & 0 deletions appwrite/enums/project_o_auth_provider_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class ProjectOAuthProviderId(Enum):
BITBUCKET = "bitbucket"
BITLY = "bitly"
BOX = "box"
CLOUDFLARE = "cloudflare"
DAILYMOTION = "dailymotion"
DISCORD = "discord"
DISQUS = "disqus"
Expand All @@ -33,6 +34,7 @@ class ProjectOAuthProviderId(Enum):
PAYPAL = "paypal"
PAYPALSANDBOX = "paypalSandbox"
PODIO = "podio"
RESEND = "resend"
SALESFORCE = "salesforce"
SLACK = "slack"
SPOTIFY = "spotify"
Expand Down
4 changes: 4 additions & 0 deletions appwrite/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@
from .o_auth2_notion import OAuth2Notion
from .o_auth2_salesforce import OAuth2Salesforce
from .o_auth2_yahoo import OAuth2Yahoo
from .o_auth2_cloudflare import OAuth2Cloudflare
from .o_auth2_hugging_face import OAuth2HuggingFace
from .o_auth2_linkedin import OAuth2Linkedin
from .o_auth2_disqus import OAuth2Disqus
Expand All @@ -176,6 +177,7 @@
from .o_auth2_kick import OAuth2Kick
from .o_auth2_apple import OAuth2Apple
from .o_auth2_microsoft import OAuth2Microsoft
from .o_auth2_resend import OAuth2Resend
from .o_auth2_provider_list import OAuth2ProviderList
from .policy_password_dictionary import PolicyPasswordDictionary
from .policy_password_history import PolicyPasswordHistory
Expand Down Expand Up @@ -458,6 +460,7 @@
'OAuth2Notion',
'OAuth2Salesforce',
'OAuth2Yahoo',
'OAuth2Cloudflare',
'OAuth2HuggingFace',
'OAuth2Linkedin',
'OAuth2Disqus',
Expand All @@ -477,6 +480,7 @@
'OAuth2Kick',
'OAuth2Apple',
'OAuth2Microsoft',
'OAuth2Resend',
'OAuth2ProviderList',
'PolicyPasswordDictionary',
'PolicyPasswordHistory',
Expand Down
3 changes: 3 additions & 0 deletions appwrite/models/billing_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ class BillingPlan(AppwriteModel):
Usage history days
usagelogsintervals : Optional[List[Any]]
Usage log time intervals allowed for this plan (e.g. 15m, 1h, 1d).
usageaggregateonlymetrics : Optional[List[Any]]
Metrics this plan only records as a total. They cannot be broken down by dimension or filtered, because the stored events cover a fraction of the real traffic.
projectinactivitydays : float
Number of days of console inactivity before a project is paused. 0 means pausing is disabled.
alertlimit : float
Expand Down Expand Up @@ -198,6 +200,7 @@ class BillingPlan(AppwriteModel):
activitylogs: Optional[float] = Field(default=None, alias='activityLogs')
usagelogs: float = Field(..., alias='usageLogs')
usagelogsintervals: Optional[List[Any]] = Field(default=None, alias='usageLogsIntervals')
usageaggregateonlymetrics: Optional[List[Any]] = Field(default=None, alias='usageAggregateOnlyMetrics')
projectinactivitydays: float = Field(..., alias='projectInactivityDays')
alertlimit: float = Field(..., alias='alertLimit')
usage: UsageBillingPlan = Field(..., alias='usage')
Expand Down
5 changes: 4 additions & 1 deletion appwrite/models/dedicated_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class DedicatedDatabase(AppwriteModel):
specification : str
Specification identifier.
backend : str
Database backend provider. Possible values: prisma, edge.
Database backend provider. Possible values: edge.
hostname : str
Database hostname for connections.
connectionport : float
Expand All @@ -38,6 +38,8 @@ class DedicatedDatabase(AppwriteModel):
Database username for connections.
connectionpassword : str
Database password for connections.
credentialgeneration : float
Committed generation of the primary connection credentials. Null until the rotation contract has been initialized.
connectionstring : str
Full database connection string (URI format).
ssl : bool
Expand Down Expand Up @@ -122,6 +124,7 @@ class DedicatedDatabase(AppwriteModel):
connectionport: float = Field(..., alias='connectionPort')
connectionuser: str = Field(..., alias='connectionUser')
connectionpassword: str = Field(..., alias='connectionPassword')
credentialgeneration: float = Field(..., alias='credentialGeneration')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Nullable generation rejects valid responses

When the API returns credentialGeneration as null before the rotation contract is initialized, this required float fails Pydantic validation, causing the SDK to raise AppwriteException for an otherwise successful database response.

Suggested change
credentialgeneration: float = Field(..., alias='credentialGeneration')
credentialgeneration: Optional[float] = Field(default=None, alias='credentialGeneration')

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: appwrite/models/dedicated_database.py
Line: 127

Comment:
**Nullable generation rejects valid responses**

When the API returns `credentialGeneration` as null before the rotation contract is initialized, this required `float` fails Pydantic validation, causing the SDK to raise `AppwriteException` for an otherwise successful database response.

```suggestion
    credentialgeneration: Optional[float] = Field(default=None, alias='credentialGeneration')
```

**Knowledge Base Used:**
- [API data contracts](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/sdk-for-python/-/docs/api-data-contracts.md)
- [Request transport and error handling](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/sdk-for-python/-/docs/request-transport-and-errors.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

connectionstring: str = Field(..., alias='connectionString')
ssl: bool = Field(..., alias='ssl')
status: str = Field(..., alias='status')
Expand Down
2 changes: 1 addition & 1 deletion appwrite/models/dedicated_database_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class DedicatedDatabaseOperation(AppwriteModel):
databaseid : str
Database ID the operation ran against.
type : str
Operation type, such as provision, update, restore, pausing, resuming, failover, backup-create or cross-region-enable.
Operation type, such as provision, update, credentials-update, restore, pausing, resuming, failover, backup-create or cross-region-enable.
status : str
Operation status. Possible values: queued (accepted and waiting to resume), running (in progress), completed (finished successfully), failed (ended in an error).
attempts : float
Expand Down
4 changes: 2 additions & 2 deletions appwrite/models/framework_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ class FrameworkAdapter(AppwriteModel):
Default command to build site into output directory.
outputdirectory : str
Default output directory of build.
fallbackfile : str
fallbackfile : Optional[str]
Name of fallback file to use instead of 404 page. If null, Appwrite 404 page will be displayed.
"""

key: str = Field(..., alias='key')
installcommand: str = Field(..., alias='installCommand')
buildcommand: str = Field(..., alias='buildCommand')
outputdirectory: str = Field(..., alias='outputDirectory')
fallbackfile: str = Field(..., alias='fallbackFile')
fallbackfile: Optional[str] = Field(default=None, alias='fallbackFile')
26 changes: 26 additions & 0 deletions appwrite/models/o_auth2_cloudflare.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from typing import Any, Dict, List, Optional, Union, cast
from pydantic import Field, PrivateAttr

from .base_model import AppwriteModel


class OAuth2Cloudflare(AppwriteModel):
"""
OAuth2Cloudflare

Attributes
----------
id : str
OAuth2 provider ID.
enabled : bool
OAuth2 provider is active and can be used to create sessions.
clientid : str
Cloudflare OAuth2 client ID.
clientsecret : str
Cloudflare OAuth2 client secret.
"""

id: str = Field(..., alias='$id')
enabled: bool = Field(..., alias='enabled')
clientid: str = Field(..., alias='clientId')
clientsecret: str = Field(..., alias='clientSecret')
6 changes: 5 additions & 1 deletion appwrite/models/o_auth2_provider_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
from .o_auth2_kick import OAuth2Kick
from .o_auth2_microsoft import OAuth2Microsoft
from .o_auth2_hugging_face import OAuth2HuggingFace
from .o_auth2_resend import OAuth2Resend
from .o_auth2_cloudflare import OAuth2Cloudflare


class OAuth2ProviderList(AppwriteModel):
Expand All @@ -54,7 +56,7 @@ class OAuth2ProviderList(AppwriteModel):
----------
total : float
Total number of OAuth2 providers in the given project.
providers : List[Union[OAuth2Github, OAuth2Discord, OAuth2Figma, OAuth2Dropbox, OAuth2Dailymotion, OAuth2Bitbucket, OAuth2Bitly, OAuth2Box, OAuth2Autodesk, OAuth2Google, OAuth2Zoom, OAuth2Zoho, OAuth2Yandex, OAuth2X, OAuth2WordPress, OAuth2Twitch, OAuth2Stripe, OAuth2Spotify, OAuth2Slack, OAuth2Podio, OAuth2Notion, OAuth2Salesforce, OAuth2Yahoo, OAuth2Linkedin, OAuth2Disqus, OAuth2Amazon, OAuth2Etsy, OAuth2Facebook, OAuth2Tradeshift, OAuth2Paypal, OAuth2Gitlab, OAuth2Appwrite, OAuth2Authentik, OAuth2Auth0, OAuth2FusionAuth, OAuth2Keycloak, OAuth2Oidc, OAuth2Apple, OAuth2Okta, OAuth2Kick, OAuth2Microsoft, OAuth2HuggingFace]]
providers : List[Union[OAuth2Github, OAuth2Discord, OAuth2Figma, OAuth2Dropbox, OAuth2Dailymotion, OAuth2Bitbucket, OAuth2Bitly, OAuth2Box, OAuth2Autodesk, OAuth2Google, OAuth2Zoom, OAuth2Zoho, OAuth2Yandex, OAuth2X, OAuth2WordPress, OAuth2Twitch, OAuth2Stripe, OAuth2Spotify, OAuth2Slack, OAuth2Podio, OAuth2Notion, OAuth2Salesforce, OAuth2Yahoo, OAuth2Linkedin, OAuth2Disqus, OAuth2Amazon, OAuth2Etsy, OAuth2Facebook, OAuth2Tradeshift, OAuth2Paypal, OAuth2Gitlab, OAuth2Appwrite, OAuth2Authentik, OAuth2Auth0, OAuth2FusionAuth, OAuth2Keycloak, OAuth2Oidc, OAuth2Apple, OAuth2Okta, OAuth2Kick, OAuth2Microsoft, OAuth2HuggingFace, OAuth2Resend, OAuth2Cloudflare]]
List of OAuth2 providers.
"""

Expand Down Expand Up @@ -103,5 +105,7 @@ class OAuth2ProviderList(AppwriteModel):
OAuth2Kick,
OAuth2Microsoft,
OAuth2HuggingFace,
OAuth2Resend,
OAuth2Cloudflare,
Comment on lines +108 to +109

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Provider union loses nominal type

When list_o_auth2_providers parses a Resend or Cloudflare response with the common provider fields, an earlier structurally identical member of this untagged union wins validation, causing callers to receive the wrong provider model and breaking isinstance-based dispatch.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: appwrite/models/o_auth2_provider_list.py
Line: 108-109

Comment:
**Provider union loses nominal type**

When `list_o_auth2_providers` parses a Resend or Cloudflare response with the common provider fields, an earlier structurally identical member of this untagged union wins validation, causing callers to receive the wrong provider model and breaking `isinstance`-based dispatch.

**Knowledge Base Used:**
- [API data contracts](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/sdk-for-python/-/docs/api-data-contracts.md)
- [Project and platform services](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/sdk-for-python/-/docs/project-and-platform-services.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

]
] = Field(..., alias='providers')
26 changes: 26 additions & 0 deletions appwrite/models/o_auth2_resend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from typing import Any, Dict, List, Optional, Union, cast
from pydantic import Field, PrivateAttr

from .base_model import AppwriteModel


class OAuth2Resend(AppwriteModel):
"""
OAuth2Resend

Attributes
----------
id : str
OAuth2 provider ID.
enabled : bool
OAuth2 provider is active and can be used to create sessions.
clientid : str
Resend OAuth2 client ID.
clientsecret : str
Resend OAuth2 client secret.
"""

id: str = Field(..., alias='$id')
enabled: bool = Field(..., alias='enabled')
clientid: str = Field(..., alias='clientId')
clientsecret: str = Field(..., alias='clientSecret')
2 changes: 1 addition & 1 deletion appwrite/services/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -1880,7 +1880,7 @@ def create_o_auth2_token(
Parameters
----------
provider : OAuthProvider
OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom.
OAuth2 Provider. Currently, supported providers are: amazon, apple, appwrite, auth0, authentik, autodesk, bitbucket, bitly, box, cloudflare, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, huggingface, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, resend, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, x, yahoo, yammer, yandex, zoho, zoom.
success : Optional[str]
URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.
failure : Optional[str]
Expand Down
8 changes: 4 additions & 4 deletions appwrite/services/avatars.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ def get_photo(
"""
Returns the best available profile photo for a user. The endpoint tries each source in priority order and returns the first successful result: OAuth2 identity photo, Gravatar, Libravatar, Appwrite Initials, built-in static fallback.

The photo resolves for the currently authenticated user unless `userId` points at another user. Passing `emailHash` and/or `name` resolves the avatar from those values alone: the hash is looked up on Gravatar and Libravatar, the name is rendered as initials, and the user's own identity photos, email, and name leave the chain so they never shadow the avatar being asked for. Emails are only ever accepted pre-hashed, so no address ends up in a URL.
Passing `userId` — `current()` for the authenticated user — resolves the photo from everything known about that user: identity photos, email, and name. An explicit `emailHash` or `name` then overrides just that value, and the user's remaining sources stay in the chain. Without `userId`, passing `emailHash` and/or `name` resolves the avatar from those values alone: the hash is looked up on Gravatar and Libravatar, the name is rendered as initials, and the session user stays out of the chain so their own photo never shadows the avatar being asked for. When nothing is passed, the photo resolves for the currently authenticated user. Emails are only ever accepted pre-hashed, so no address ends up in a URL.

Parameters
----------
Expand All @@ -371,11 +371,11 @@ def get_photo(
rating : Optional[str]
Maximum image rating to fetch from Gravatar/Libravatar. Defaults to 'g'.
user_id : Optional[str]
User ID to resolve the photo for. Defaults to 'current()' for the currently authenticated user.
User ID to resolve the photo for. Pass 'current()' for the currently authenticated user. When omitted, the session user is used only if no emailHash and no name is passed.
email_hash : Optional[str]
SHA256 hash of the lowercase, trimmed email address to look up on Gravatar and Libravatar instead of the user's own photo sources. Pass the hash, never the address itself.
SHA256 hash of the lowercase, trimmed email address to look up on Gravatar and Libravatar instead of the user's own email. Pass the hash, never the address itself.
name : Optional[str]
Name to render initials from instead of the user's own photo sources. Max length: 128 chars.
Name to render initials from instead of the user's own name. Max length: 128 chars.
Returns
-------
bytes
Expand Down
10 changes: 10 additions & 0 deletions appwrite/services/documents_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,7 @@ def create_document(
document_id: str,
data: Dict[str, Any],
permissions: Optional[List[str]] = None,
transaction_id: Optional[str] = None,
model_type: Type[T] = dict,
Comment on lines +952 to 953

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Positional model type is misbound

When an existing caller passes model_type positionally, the new parameter order binds that class to transaction_id; the class is then placed in the JSON payload, causing serialization to raise TypeError before the request is sent. The same incompatible ordering was added to both creation methods in DocumentsDB and VectorsDB.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: appwrite/services/documents_db.py
Line: 952-953

Comment:
**Positional model type is misbound**

When an existing caller passes `model_type` positionally, the new parameter order binds that class to `transaction_id`; the class is then placed in the JSON payload, causing serialization to raise `TypeError` before the request is sent. The same incompatible ordering was added to both creation methods in DocumentsDB and VectorsDB.

**Knowledge Base Used:**
- [Document and vector database services](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/sdk-for-python/-/docs/document-and-vector-database-services.md)
- [API data contracts](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/sdk-for-python/-/docs/api-data-contracts.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

) -> Document[T]:
"""
Expand All @@ -966,6 +967,8 @@ def create_document(
Document data as JSON object.
permissions : Optional[List[str]]
An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).
transaction_id : Optional[str]
Transaction ID for staging the operation.
model_type : Type[T], optional
Pydantic model class for the user-defined data. Defaults to dict for backward compatibility.

Expand Down Expand Up @@ -996,6 +999,8 @@ def create_document(
api_params['data'] = self._normalize_value(data)
if permissions is not None:
api_params['permissions'] = self._normalize_value(permissions)
if transaction_id is not None:
api_params['transactionId'] = self._normalize_value(transaction_id)

response = self.client.call(
'post',
Expand All @@ -1015,6 +1020,7 @@ def create_documents(
database_id: str,
collection_id: str,
documents: List[Dict[str, Any]],
transaction_id: Optional[str] = None,
model_type: Type[T] = dict,
) -> DocumentList[T]:
"""
Expand All @@ -1028,6 +1034,8 @@ def create_documents(
Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.
documents : List[Dict[str, Any]]
Array of documents data as JSON objects.
transaction_id : Optional[str]
Transaction ID for staging the operation.
model_type : Type[T], optional
Pydantic model class for the user-defined data. Defaults to dict for backward compatibility.

Expand All @@ -1053,6 +1061,8 @@ def create_documents(
api_path = api_path.replace('{databaseId}', str(self._normalize_value(database_id)))
api_path = api_path.replace('{collectionId}', str(self._normalize_value(collection_id)))
api_params['documents'] = self._normalize_value(documents)
if transaction_id is not None:
api_params['transactionId'] = self._normalize_value(transaction_id)

response = self.client.call(
'post',
Expand Down
Loading
Loading