Skip to content

feat: Python SDK update for version 18.0.0#140

Open
ChiragAgg5k wants to merge 4 commits intomainfrom
dev
Open

feat: Python SDK update for version 18.0.0#140
ChiragAgg5k wants to merge 4 commits intomainfrom
dev

Conversation

@ChiragAgg5k
Copy link
Copy Markdown
Member

@ChiragAgg5k ChiragAgg5k commented Apr 15, 2026

This PR contains updates to the Python SDK for version 18.0.0.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 15, 2026

Greptile Summary

This PR updates the Python SDK to version 18.0.0 targeting Appwrite API 1.9.1. It adds a new Project service with platform/key CRUD and protocol/service status management, introduces several new models (Key, DevKey, Project, platform variants, BillingLimits, Block, AuthProvider), and renames webhook fields (security→tls, httpUser→authUsername, etc.).

Several P1 issues flagged in prior review threads remain open: expire on Key and DevKey must be Optional[str] to handle unlimited-expiry keys; secret on Webhook must be Optional[str] since it is only returned at creation/rotation; billinglimits on Project must be Optional[BillingLimits] for free-tier projects; and secret in Webhooks.create and update_secret is sent unconditionally (null instead of omitted).

Confidence Score: 3/5

Not safe to merge — multiple P1 nullable-field issues from prior threads remain unaddressed and will raise ValidationError in common real-world scenarios

Four distinct P1 bugs identified in prior review rounds are still present: Key/DevKey expire nullability, Webhook secret nullability, Project billingLimits nullability, and unconditional null serialisation of the webhook secret param. Any of these will produce runtime ValidationError or send unexpected null values for a non-trivial subset of users.

appwrite/models/key.py, appwrite/models/dev_key.py, appwrite/models/webhook.py, appwrite/models/project.py, appwrite/services/webhooks.py

Important Files Changed

Filename Overview
appwrite/models/key.py New Key model; expire: str = Field(...) is required/non-nullable — unlimited-expiry keys (expire: null) will raise ValidationError when parsed
appwrite/models/dev_key.py New DevKey model; expire: str = Field(...) has the same nullable issue as Key — dev keys without expiry will fail to parse
appwrite/models/webhook.py Renames fields per v18 breaking changes; secret: str = Field(...) is required/non-nullable but is only returned on creation and secret rotation, so get/list calls will raise ValidationError
appwrite/models/project.py New Project model; billinglimits: BillingLimits = Field(...) is required/non-nullable and will fail for free-tier projects; also contains HTML-encoded apostrophes in docstrings
appwrite/services/webhooks.py Adds secret param to create/update_secret; secret is unconditionally added to api_params (sending null) in both create and update_secret, unlike all other optional params
appwrite/services/project.py Adds comprehensive platform CRUD, key CRUD, protocol/service status methods; new methods follow correct optional-param guarding patterns
appwrite/models/platform_list.py New PlatformList with Union type; relies on field-absence elimination for left-to-right union resolution — no discriminator on the type field
appwrite/models/billing_limits.py New BillingLimits model with all required float fields — correctness depends on whether billingLimits is always present in project responses
appwrite/models/block.py New Block model; nullable fields (reason, expiredAt) correctly typed as Optional[str]
appwrite/models/auth_provider.py New AuthProvider model with all required fields; unconfigured providers typically return empty strings rather than null
appwrite/client.py Bumps version to 18.0.0 / 1.9.1 and adds get_headers() convenience method returning a safe copy of headers
appwrite/models/init.py Adds exports for all new models (Key, DevKey, Project, platform types, BillingLimits, Block, etc.)
appwrite/models/log.py Adds required userType field to Log model; correct for target API 1.9.1

Reviews (4): Last reviewed commit: "chore: update Python SDK to 18.0.0" | Re-trigger Greptile

api_params['authUsername'] = self._normalize_value(auth_username)
if auth_password is not None:
api_params['authPassword'] = self._normalize_value(auth_password)
api_params['secret'] = self._normalize_value(secret)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 secret unconditionally sent in request body

Unlike every other optional parameter in this function (enabled, tls, auth_username, auth_password), secret is always added to api_params without an if secret is not None guard. When secret defaults to None, this serialises as "secret": null in the JSON body. If the intent is to let the server auto-generate the key when the caller omits this argument, omitting the key entirely (consistent with the other optional params) is the safer approach.

Suggested change
api_params['secret'] = self._normalize_value(secret)
if secret is not None:
api_params['secret'] = self._normalize_value(secret)

Whether or not to check the user password for similarity with their personal data.
authdisposableemails : bool
Whether or not to disallow disposable email addresses during signup and email updates.
authcanonicalemails : bool
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 HTML entities in docstrings

Several docstring entries contain raw HTML entities (') instead of plain apostrophes. This makes help() output and generated API docs harder to read. The same pattern appears on line 240 (Used with plan's). The entities should be decoded during SDK generation or replaced here.

Suggested change
authcanonicalemails : bool
authpassworddictionary : bool
Whether or not to check user's password against most commonly used passwords.

List of platforms.
"""
total: float = Field(..., alias='total')
platforms: List[Union[PlatformWeb, PlatformApple, PlatformAndroid, PlatformWindows, PlatformLinux]] = Field(..., alias='platforms')
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Union without discriminator — relies on field-absence elimination

Union[PlatformWeb, PlatformApple, PlatformAndroid, PlatformWindows, PlatformLinux] has no Pydantic discriminator. Pydantic v2 tries each type left-to-right until one validates, which works here because each subtype has a unique required field (hostname, bundleIdentifier, applicationId, etc.). However, it performs up to five validation attempts per item and fails silently if the server ever omits the type-specific field.

Since every platform already carries a type field backed by the PlatformType enum, adding a Literal constraint to each model and using Field(discriminator='type') would make parsing O(1), more explicit, and resilient to API schema drift. The same applies to the platforms field in appwrite/models/project.py.


api_path = api_path.replace('{webhookId}', str(self._normalize_value(webhook_id)))

api_params['secret'] = self._normalize_value(secret)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Same unconditional secret assignment in update_secret

update_secret has the same pattern: api_params['secret'] = self._normalize_value(secret) always runs, sending null when the caller omits the argument. Applying the same if secret is not None guard as other optional params would make the behaviour consistent.

Suggested change
api_params['secret'] = self._normalize_value(secret)
if secret is not None:
api_params['secret'] = self._normalize_value(secret)

@ChiragAgg5k ChiragAgg5k changed the title feat: Python SDK update for version 17.1.0 feat: Python SDK update for version 18.0.0 Apr 15, 2026
@ChiragAgg5k ChiragAgg5k changed the title feat: Python SDK update for version 18.0.0 feat: SDK update for version 18.0.0 Apr 15, 2026
createdat: str = Field(..., alias='$createdAt')
updatedat: str = Field(..., alias='$updatedAt')
name: str = Field(..., alias='name')
expire: str = Field(..., alias='expire')
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 expire must be Optional[str] — keys with unlimited expiration will fail to parse

create_key and update_key both unconditionally send expire: null when no expiry is specified, meaning any key created without an explicit expiry date will have expire: null in the server's response. With expire: str = Field(...) (required, non-nullable), Pydantic will raise a ValidationError when parsing the response for such a key, breaking create_key, get_key, and list_keys for all unlimited-expiry keys. The same applies to DevKey.expire.

Suggested change
expire: str = Field(..., alias='expire')
expire: Optional[str] = Field(default=None, alias='expire')

updatedat: str = Field(..., alias='$updatedAt')
name: str = Field(..., alias='name')
expire: str = Field(..., alias='expire')
secret: str = Field(..., alias='secret')
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 expire must be Optional[str] for the same reason as Key.expire

Dev keys can also be created without an expiration (expire: null). The field must be nullable or Project parsing (which embeds a List[DevKey]) will fail whenever any dev key has unlimited expiration.

Suggested change
secret: str = Field(..., alias='secret')
expire: Optional[str] = Field(default=None, alias='expire')

@ChiragAgg5k ChiragAgg5k changed the title feat: SDK update for version 18.0.0 feat: Python SDK update for version 18.0.0 Apr 15, 2026
@ChiragAgg5k ChiragAgg5k changed the title feat: Python SDK update for version 18.0.0 feat: SDK update for version 18.0.0 Apr 15, 2026
protocolstatusforgraphql: bool = Field(..., alias='protocolStatusForGraphql')
protocolstatusforwebsocket: bool = Field(..., alias='protocolStatusForWebsocket')
region: str = Field(..., alias='region')
billinglimits: BillingLimits = Field(..., alias='billingLimits')
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 billinglimits should be Optional[BillingLimits]

Projects on free-tier plans or without billing configured will likely return null for this field. With the current Field(...) (required, non-nullable) declaration, parsing any such project response will raise a ValidationError, breaking get_project-style calls for those users.

Suggested change
billinglimits: BillingLimits = Field(..., alias='billingLimits')
billinglimits: Optional[BillingLimits] = Field(default=None, alias='billingLimits')

tls: bool = Field(..., alias='tls')
authusername: str = Field(..., alias='authUsername')
authpassword: str = Field(..., alias='authPassword')
secret: str = Field(..., alias='secret')
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 secret field must be nullable — regular reads will raise ValidationError

The docstring for this field explicitly says "Only returned on creation and secret rotation." Any get() or list() call that doesn't include this field in the response will cause Pydantic to raise a ValidationError because it is declared as required (Field(...)). It should be Optional[str] with a None default, matching the same pattern used for other conditionally-returned fields in this SDK.

@ChiragAgg5k ChiragAgg5k changed the title feat: SDK update for version 18.0.0 feat: Python SDK update for version 18.0.0 Apr 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant